Skip to content

Commit

Permalink
Mobile menus 2.0. (#2232)
Browse files Browse the repository at this point in the history
* Mobile menu system:
+ Menus and MenuButtons no longer requires a model.
+ Menus and MenuButtons can be provided menu items as props.
+ MenuButton uses popperjs and blueprint-style position system, to position the menu relative to the button.
+ Removed AppMenuModel in favor of passing `extraItems` to AppMenuButton via AppBar. This is consistent with Desktop.

* Update MenuButton to latest syntax

* Update remaining copyright to 2021

* + Move MenuButton to menu package
+ Move Menu to menu/impl package

* Added CHANGELOG entry
  • Loading branch information
TomTirapani authored Jan 28, 2021
1 parent 07baea9 commit 6cd0090
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 276 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ decorators, in favor of a simpler inheritance-based approach to defining models
### 🎁 New Features

* New utility method `getOrCreate` for easy caching of properties on objects.
* The `Menu` system on mobile has been reworked to be more consistent with desktop.
A new `MenuButton` component has been added to the mobile framework, which renders a `Menu`
of `MenuItems` next to the `MenuButton`. This change also includes the removal of
`AppMenuModel` (see Breaking Changes)

### 💥 Breaking Changes

Expand All @@ -45,6 +49,8 @@ decorators, in favor of a simpler inheritance-based approach to defining models
wish to select for your component. `Uses` will throw if given any string other than "*", making
the need for any updates clear.
* The `Ref` class, deprecated in v26, has now been removed. Use `createObservableRef` instead.
* `AppMenuModel` has been removed. The `AppMenuButton` is now configured via `AppBar.appMenuButtonProps`.
As with desktop, menu items can be added with `AppBar.appMenuButtonProps.extraItems[]`

### ⚙️ Technical

Expand Down
3 changes: 2 additions & 1 deletion mobile/cmp/button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const [Button, button] = hoistCmp.withFactory({
model: false,
className: 'xh-button',

render(props) {
render(props, ref) {
const [layoutProps, {icon, className, text, modifier, active, onClick, style, ...rest}] = splitLayoutProps(props),
items = [];

Expand All @@ -34,6 +34,7 @@ export const [Button, button] = hoistCmp.withFactory({
}

return onsenButton({
ref,
items,
modifier,
onClick,
Expand Down
28 changes: 0 additions & 28 deletions mobile/cmp/button/MenuButton.js

This file was deleted.

1 change: 0 additions & 1 deletion mobile/cmp/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ export * from './ColChooserButton';
export * from './FeedbackButton';
export * from './ThemeToggleButton';
export * from './LogoutButton';
export * from './MenuButton';
export * from './RefreshButton';
export * from './NavigatorBackButton';
34 changes: 10 additions & 24 deletions mobile/cmp/header/AppBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
*/
import {div} from '@xh/hoist/cmp/layout';
import {hoistCmp, useContextModel, XH} from '@xh/hoist/core';
import {button, menuButton, navigatorBackButton, refreshButton} from '@xh/hoist/mobile/cmp/button';
import {menu} from '@xh/hoist/mobile/cmp/menu';
import {button, navigatorBackButton, refreshButton} from '@xh/hoist/mobile/cmp/button';
import {NavigatorModel} from '@xh/hoist/mobile/cmp/navigator';
import {toolbar} from '@xh/hoist/mobile/cmp/toolbar';
import PT from 'prop-types';

import './AppBar.scss';
import {appMenuButton} from './AppMenuButton';

/**
* A standard application navigation bar which displays the current page title and a standard set of
Expand All @@ -36,11 +37,10 @@ export const [AppBar, appBar] = hoistCmp.withFactory({
hideBackButton,
hideRefreshButton,
appMenuButtonProps = {},
appMenuButtonPosition = 'right',
backButtonProps = {},
refreshButtonProps = {},
appMenuButtonPosition = 'right'
refreshButtonProps = {}
}) {

const navigatorModel = useContextModel(NavigatorModel);

return toolbar({
Expand All @@ -53,8 +53,9 @@ export const [AppBar, appBar] = hoistCmp.withFactory({
omit: hideBackButton,
...backButtonProps
}),
menuButton({
appMenuButton({
omit: hideAppMenuButton || appMenuButtonPosition != 'left',
menuPosition: 'bottom-right',
...appMenuButtonProps
}),
...leftItems || []
Expand Down Expand Up @@ -82,33 +83,18 @@ export const [AppBar, appBar] = hoistCmp.withFactory({
disabled: navigatorModel?.disableAppRefreshButton,
...refreshButtonProps
}),
menuButton({
appMenuButton({
omit: hideAppMenuButton || appMenuButtonPosition != 'right',
menuPosition: 'bottom-left',
...appMenuButtonProps
})
]
}),
appMenu({align: appMenuButtonPosition})
})
]
});
}
});

const appMenu = hoistCmp.factory({
displayName: 'AppMenu',

render({align}) {
const menuModel = XH.appModel.appMenuModel;
if (!menuModel) return null;
return menu({
model: menuModel,
width: 260,
align
});
}
});


AppBar.propTypes = {
/** App icon to display to the left of the title. */
icon: PT.element,
Expand Down
123 changes: 123 additions & 0 deletions mobile/cmp/header/AppMenuButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* This file belongs to Hoist, an application development toolkit
* developed by Extremely Heavy Industries (www.xh.io | [email protected])
*
* Copyright © 2021 Extremely Heavy Industries Inc.
*/
import {hoistCmp, XH} from '@xh/hoist/core';
import {menuButton} from '@xh/hoist/mobile/cmp/menu';
import {Button} from '@xh/hoist/mobile/cmp/button';
import {Icon} from '@xh/hoist/icon';
import {withDefault} from '@xh/hoist/utils/js';
import PT from 'prop-types';

/**
* An top-level application drop down menu, which installs a standard set of menu items for common
* application actions. Application specific items can be displayed before these standard items.
*
* The standard items which are visible will be based on user roles and application configuration,
* or they can each be explicitly hidden.
*/
export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory({
displayName: 'AppMenuButton',
model: false,
className: 'xh-app-menu',

render(props) {
const {className, extraItems, hideImpersonateItem, hideFeedbackItem, hideLogoutItem, hideOptionsItem, hideThemeItem, hideAboutItem, ...rest} = props;

return menuButton({
className,
menuItems: buildMenuItems(props),
...rest
});
}
});

AppMenuButton.propTypes = {
...Button.propTypes,

/** Array of app-specific MenuItems or configs to create them */
extraItems: PT.array,

/**
* True to hide the Impersonate Item.
* Always hidden for users w/o HOIST_ADMIN role or if impersonation is disabled.
*/
hideImpersonateItem: PT.bool,

/** True to hide the Feedback Item. */
hideFeedbackItem: PT.bool,

/** True to hide the Logout button. Defaulted to appSpec.isSSO. */
hideLogoutItem: PT.bool,

/** True to hide the Options button. */
hideOptionsItem: PT.bool,

/** True to hide the Theme Toggle button. */
hideThemeItem: PT.bool,

/** True to hide the About button */
hideAboutItem: PT.bool
};

//---------------------------
// Implementation
//---------------------------
function buildMenuItems({
hideOptionsItem,
hideFeedbackItem,
hideThemeItem,
hideImpersonateItem,
hideLogoutItem,
hideAboutItem,
extraItems = []
}) {
hideOptionsItem = hideOptionsItem || !XH.acm.optionsDialogModel.hasOptions;
hideImpersonateItem = hideImpersonateItem || !XH.identityService.canImpersonate;
hideLogoutItem = withDefault(hideLogoutItem, XH.appSpec.isSSO);

const defaultItems = [
{
omit: hideOptionsItem,
text: 'Options',
icon: Icon.options(),
actionFn: () => XH.showOptionsDialog()
},
{
omit: hideFeedbackItem,
text: 'Feedback',
icon: Icon.comment({className: 'fa-flip-horizontal'}),
actionFn: () => XH.showFeedbackDialog()
},
{
omit: hideThemeItem,
actionFn: () => XH.toggleTheme(),
prepareFn: (item) => {
item.text = XH.darkTheme ? 'Light Theme' : 'Dark Theme';
item.icon = XH.darkTheme ? Icon.sun() : Icon.moon();
}
},
{
omit: hideImpersonateItem,
text: 'Impersonate',
icon: Icon.impersonate(),
actionFn: () => XH.showImpersonationBar()
},
{
omit: hideAboutItem,
icon: Icon.info(),
text: `About ${XH.clientAppName}`,
actionFn: () => XH.showAboutDialog()
},
{
omit: hideLogoutItem,
text: 'Logout',
icon: Icon.logout(),
actionFn: () => XH.identityService.logoutAsync()
}
];

return [...extraItems, ...defaultItems];
}
86 changes: 0 additions & 86 deletions mobile/cmp/header/AppMenuModel.js

This file was deleted.

2 changes: 1 addition & 1 deletion mobile/cmp/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
*/

export * from './AppBar';
export * from './AppMenuModel';
export * from './AppMenuButton';
Loading

0 comments on commit 6cd0090

Please sign in to comment.