Experimental Menu/Toolbar handling API
wokalski opened this issue · 2 comments
I came up with an idea for declarative handling of Menus and Toolbars. The cool thing is that it's completely user space by using hooks, yet provides a very declarative API.
let make = (...) => component(hooks => {
// Menu would be global menu but similarly we could pass/access a global menu object
// if there are cases for multiple menus in apps
let hooks = Menu.render(~menu=SharedMenus.file, [
Item.make("Export", ~handler=saveHandler),
Item.make("Export as...", ~handler=saveAsHandler),
], ~priority=100 /* arbitrary integer, we might provide some built ins too */, hooks);
...
})
Behind the scenes it would be an effect that adds/updates the items in a useEffect
handler on render and call imperative add/remove there.
Two unanswered questions
- There's a more complex case when we want to have some greyed out elements, how do we model that?
- What's the negotiation model if we have some kind of global identity for common elements like File -> Save
Cool idea @wokalski ! I'm glad you're thinking about this.
What would happen if multiple components call Menu.render
- would it be OK for them to call different ~menu
's? I guess this is related to your second bullet point.
For Revery - we might want to render this natively in some platforms (ie, OSX), but have custom rendering for others (Windows) - I guess we would need some sort of pluggable back-end for this.
An alternate model I was thinking about this is a JSX-like approach, like:
App.setApplicationMenu(app, (...some sort of state / context) => {
<Menu>
<Submenu label="Edit">
<MenuItem role="undo" onClick={doUndo()} />
<MenuItem role="redo" onClick={doRedo()} />
<Submenu label="Clipboard">
<MenuItem role="copy" />
<MenuItem role="paste" enabled=false />
</Submenu>
</Submenu>
</Menu>
});
I think I would prefer a functional-style API like this after working with Electron's model (which is clunky! https://electronjs.org/docs/api/menu#new-menu).
We could potentially use a separate reconciler for this - and it could dispatch imperative menu update commands to the backing native API.
@bryphe yes, consider rendering an additive operation not a setter. (something else would probably unmount or maybe we could also put some of the menu items behind a reactive condition, i.e. an If effect.
I'd like to avoid setters. Whether we use JSX or not is secondary, I could've used JSX in my first example but kinda went with normal functions 😄
Menu.render
would be kind of like returning from render but whatever it "renders" would escape the view hierarchy.