mirror of
https://github.com/HackPlan/polaris-react.git
synced 2026-04-29 17:55:57 +08:00
Remove Navigation.UserMenu
This commit is contained in:
@@ -10,7 +10,6 @@ import EventListener from '../EventListener';
|
||||
import {withAppProvider, WithAppProviderProps} from '../AppProvider';
|
||||
import Backdrop from '../Backdrop';
|
||||
import TrapFocus from '../TrapFocus';
|
||||
import {UserMenuProvider} from '../TopBar';
|
||||
import {dataPolarisTopBar, layer} from '../shared';
|
||||
import {setRootProperty} from '../../utilities/setRootProperty';
|
||||
import {ContextualSaveBarProps, ToastID, ToastPropsWithID} from './types';
|
||||
@@ -239,10 +238,8 @@ export class Frame extends React.PureComponent<CombinedProps, State> {
|
||||
{...navigationAttributes}
|
||||
>
|
||||
{skipMarkup}
|
||||
<UserMenuProvider mobileView={mobileView || false}>
|
||||
{topBarMarkup}
|
||||
{navigationMarkup}
|
||||
</UserMenuProvider>
|
||||
{topBarMarkup}
|
||||
{navigationMarkup}
|
||||
{contextualSaveBarMarkup}
|
||||
{loadingMarkup}
|
||||
{navigationOverlayMarkup}
|
||||
|
||||
@@ -93,15 +93,6 @@ class FrameExample extends React.Component {
|
||||
},
|
||||
];
|
||||
|
||||
const navigationUserMenuMarkup = (
|
||||
<Navigation.UserMenu
|
||||
actions={userMenuActions}
|
||||
name="Dharma"
|
||||
detail={storeName}
|
||||
avatarInitials="D"
|
||||
/>
|
||||
);
|
||||
|
||||
const contextualSaveBarMarkup = isDirty ? (
|
||||
<ContextualSaveBar
|
||||
message="Unsaved changes"
|
||||
@@ -157,7 +148,7 @@ class FrameExample extends React.Component {
|
||||
);
|
||||
|
||||
const navigationMarkup = (
|
||||
<Navigation location="/" userMenu={navigationUserMenuMarkup}>
|
||||
<Navigation location="/">
|
||||
<Navigation.Section
|
||||
items={[
|
||||
{
|
||||
|
||||
@@ -13,8 +13,6 @@ import {
|
||||
ContextualSaveBar as FrameContextualSavebar,
|
||||
Loading as FrameLoading,
|
||||
} from '../components';
|
||||
import TopBar, {UserMenuProvider} from '../../TopBar';
|
||||
import Navigation from '../../Navigation';
|
||||
|
||||
window.matchMedia =
|
||||
window.matchMedia ||
|
||||
@@ -218,26 +216,4 @@ describe('<Frame />', () => {
|
||||
);
|
||||
expect(frame.find(FrameLoading).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('<UserMenuProvider />', () => {
|
||||
it('renders', () => {
|
||||
const frame = mountWithAppProvider(<Frame />);
|
||||
expect(frame.find(UserMenuProvider).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('receives a mobileView boolean', () => {
|
||||
const frame = mountWithAppProvider(<Frame />);
|
||||
expect(frame.find(UserMenuProvider).prop('mobileView')).toBe(false);
|
||||
});
|
||||
|
||||
it('receives the given top bar and navigation as its children', () => {
|
||||
const topBar = <TopBar />;
|
||||
const navigation = <Navigation location="" />;
|
||||
const frame = mountWithAppProvider(
|
||||
<Frame topBar={topBar} navigation={navigation} />,
|
||||
);
|
||||
expect(frame.find(UserMenuProvider).contains(topBar)).toBe(true);
|
||||
expect(frame.find(UserMenuProvider).contains(navigation)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import Scrollable from '../Scrollable';
|
||||
|
||||
import WithinContentContext from '../WithinContentContext';
|
||||
import NavigationContext from './context';
|
||||
import {UserMenu, Section, Item} from './components';
|
||||
import {Section, Item} from './components';
|
||||
import {SectionType} from './types';
|
||||
|
||||
import styles from './Navigation.scss';
|
||||
@@ -13,36 +13,16 @@ export interface Props {
|
||||
location: string;
|
||||
sections?: SectionType[];
|
||||
children?: React.ReactNode;
|
||||
/** @deprecated Pass a user menu into <TopBar /> instead. */
|
||||
userMenu?: React.ReactNode;
|
||||
contextControl?: React.ReactNode;
|
||||
onDismiss?(): void;
|
||||
}
|
||||
|
||||
export default class Navigation extends React.Component<Props, never> {
|
||||
static Item = Item;
|
||||
static UserMenu = UserMenu;
|
||||
static Section = Section;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
if (props.userMenu) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'Deprecation: the `userMenu` prop is deprecated and will be removed in the next major version. Pass a user menu into <TopBar /> instead.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
userMenu,
|
||||
contextControl,
|
||||
location,
|
||||
onDismiss,
|
||||
} = this.props;
|
||||
const {children, contextControl, location, onDismiss} = this.props;
|
||||
|
||||
const contextControlMarkup = contextControl && (
|
||||
<div className={styles.ContextControl}>{contextControl}</div>
|
||||
@@ -58,7 +38,6 @@ export default class Navigation extends React.Component<Props, never> {
|
||||
<WithinContentContext.Provider value={{withinContentContainer: true}}>
|
||||
<nav className={styles.Navigation}>
|
||||
{contextControlMarkup}
|
||||
<div className={styles.UserMenu}>{userMenu}</div>
|
||||
<Scrollable className={styles.PrimaryNavigation}>
|
||||
{children}
|
||||
</Scrollable>
|
||||
|
||||
@@ -165,18 +165,6 @@ Action allows a complementary icon-only action to render next to the section tit
|
||||
|
||||
---
|
||||
|
||||
<a name="subcomponent-usermenu"></a>
|
||||
|
||||
## Navigation user menu
|
||||
|
||||
The user menu component displays the current user’s avatar and name, and actions that are related to the current logged in user. The menu is displayed at the top of the navigation sidebar at small screen sizes. The user menu can display any messages that the current user has available to read.
|
||||
|
||||
### Deprecation rationale
|
||||
|
||||
As of release 3.6.0 `Navigation.UserMenu` is deprecated in favor of [`TopBar.UserMenu`](https://polaris.shopify.com/components/structure/top-bar#top-bar-menu) which will stay visible on mobile.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic navigation
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import React from 'react';
|
||||
import {UserMenuModifier} from '../../../TopBar';
|
||||
import {IconableAction} from '../../../../types';
|
||||
import {Props as MessageProps} from '../Message';
|
||||
import {Props as AvatarProps} from '../../../Avatar';
|
||||
|
||||
interface UserActionSection {
|
||||
id: string;
|
||||
items: IconableAction[];
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
name?: string;
|
||||
detail?: string;
|
||||
actions?: UserActionSection[];
|
||||
message?: MessageProps;
|
||||
avatarInitials: AvatarProps['initials'];
|
||||
avatarSource?: AvatarProps['source'];
|
||||
}
|
||||
|
||||
interface State {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
/** @deprecated Use <TopBar.UserMenu /> instead. */
|
||||
class UserMenu extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'Deprecation: <Navigation.UserMenu /> is deprecated and will be removed in the next major version. Use <TopBar.UserMenu /> instead.',
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
detail,
|
||||
actions,
|
||||
message,
|
||||
avatarInitials,
|
||||
avatarSource,
|
||||
} = this.props;
|
||||
const {open} = this.state;
|
||||
|
||||
const userMenuProps = {
|
||||
actions: actions || [],
|
||||
message,
|
||||
name: name || '',
|
||||
detail,
|
||||
initials: avatarInitials,
|
||||
avatar: avatarSource,
|
||||
onToggle: this.handleToggle,
|
||||
open,
|
||||
};
|
||||
|
||||
return <UserMenuModifier userMenuProps={userMenuProps} />;
|
||||
}
|
||||
|
||||
private handleToggle = () => {
|
||||
const {open} = this.state;
|
||||
this.setState({open: !open});
|
||||
};
|
||||
}
|
||||
|
||||
export default UserMenu;
|
||||
@@ -1,5 +0,0 @@
|
||||
import UserMenu from './UserMenu';
|
||||
|
||||
export {Props} from './UserMenu';
|
||||
|
||||
export default UserMenu;
|
||||
@@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
import {mountWithAppProvider, trigger} from 'test-utilities';
|
||||
import {UserMenuModifier} from '../../../../TopBar';
|
||||
import UserMenu, {Props as UserMenuProps} from '../UserMenu';
|
||||
|
||||
describe('<UserMenu />', () => {
|
||||
const mockProps = {
|
||||
avatarInitials: '',
|
||||
avatarSource: '',
|
||||
};
|
||||
|
||||
describe('avatarInitials', () => {
|
||||
it('gets passed into the modifier', () => {
|
||||
const avatarInitials = 'JD';
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenu {...mockProps} avatarInitials={avatarInitials} />,
|
||||
);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({initials: avatarInitials}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('avatarSource', () => {
|
||||
it('gets passed into the modifier', () => {
|
||||
const avatarSource = '';
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenu {...mockProps} avatarSource={avatarSource} />,
|
||||
);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({avatar: avatarSource}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('message', () => {
|
||||
it('gets passed into the modifier', () => {
|
||||
const message = {} as UserMenuProps['message'];
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenu {...mockProps} message={message} />,
|
||||
);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({message}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('gets passed into the modifier', () => {
|
||||
const actions = [] as UserMenuProps['actions'];
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenu {...mockProps} actions={actions} />,
|
||||
);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({actions}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('detail', () => {
|
||||
it('gets passed into the modifier', () => {
|
||||
const detail = 'Little Victories CA';
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenu {...mockProps} detail={detail} />,
|
||||
);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({detail}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('name', () => {
|
||||
it('gets passed into the modifier', () => {
|
||||
const name = 'John Doe';
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenu {...mockProps} name={name} />,
|
||||
);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({name}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('<UserMenuModifier />', () => {
|
||||
it('passes in an open prop which is false by default', () => {
|
||||
const userMenu = mountWithAppProvider(<UserMenu {...mockProps} />);
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({open: false}));
|
||||
});
|
||||
|
||||
it('toggles the open prop when the user menu is toggled', () => {
|
||||
const userMenu = mountWithAppProvider(<UserMenu {...mockProps} />);
|
||||
trigger(userMenu.find(UserMenuModifier), 'userMenuProps.onToggle');
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({open: true}));
|
||||
trigger(userMenu.find(UserMenuModifier), 'userMenuProps.onToggle');
|
||||
expect(
|
||||
userMenu.find(UserMenuModifier).prop('userMenuProps'),
|
||||
).toStrictEqual(expect.objectContaining({open: false}));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
export {default as Section, Props as SectionProps} from './Section';
|
||||
export {default as Item, Props as ItemProps} from './Item';
|
||||
export {default as Message, Props as MessageProps} from './Message';
|
||||
export {default as UserMenu, Props as UserMenuProps} from './UserMenu';
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import {mountWithAppProvider} from 'test-utilities';
|
||||
import Navigation from '../Navigation';
|
||||
import NavigationContext from '../context';
|
||||
import {UserMenu} from '../components';
|
||||
import WithinContentContext from '../../WithinContentContext';
|
||||
|
||||
describe('<Navigation />', () => {
|
||||
@@ -58,16 +57,6 @@ describe('<Navigation />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('userMenu', () => {
|
||||
it('renders the given user menu', () => {
|
||||
const userMenu = <UserMenu avatarInitials="" />;
|
||||
const navigation = mountWithAppProvider(
|
||||
<Navigation location="/" userMenu={userMenu} />,
|
||||
);
|
||||
expect(navigation.contains(userMenu)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('contextControl', () => {
|
||||
it('doesn’t render by default', () => {
|
||||
const contextControl = <div />;
|
||||
|
||||
@@ -1,21 +1,68 @@
|
||||
import React from 'react';
|
||||
import withContext from '../../../WithContext';
|
||||
import {WithContextTypes} from '../../../../types';
|
||||
import {Consumer as UserMenuConsumer, UserMenuContextTypes} from './context';
|
||||
import {UserMenu as UserMenuComponent, UserMenuProps} from './components';
|
||||
import {IconableAction} from '../../../../types';
|
||||
import Avatar, {Props as AvatarProps} from '../../../Avatar';
|
||||
import MessageIndicator from '../../../MessageIndicator';
|
||||
import Menu, {MessageProps} from '../Menu';
|
||||
|
||||
type ComposedProps = UserMenuProps & WithContextTypes<UserMenuContextTypes>;
|
||||
import styles from './UserMenu.scss';
|
||||
|
||||
function UserMenu({
|
||||
context: {mobileUserMenuProps, mobileView},
|
||||
...userMenuProps
|
||||
}: ComposedProps) {
|
||||
if (mobileUserMenuProps && mobileView) {
|
||||
return <UserMenuComponent {...mobileUserMenuProps} />;
|
||||
}
|
||||
return <UserMenuComponent {...userMenuProps} />;
|
||||
export interface Props {
|
||||
/** An array of action objects that are rendered inside of a popover triggered by this menu */
|
||||
actions: {items: IconableAction[]}[];
|
||||
/** Accepts a message that facilitates direct, urgent communication with the merchant through the user menu */
|
||||
message?: MessageProps;
|
||||
/** A string detailing the merchant’s full name to be displayed in the user menu */
|
||||
name: string;
|
||||
/** A string allowing further details on the merchant’s name displayed in the user menu */
|
||||
detail?: string;
|
||||
/** The merchant’s initials, rendered in place of an avatar image when not provided */
|
||||
initials: AvatarProps['initials'];
|
||||
/** An avatar image representing the merchant */
|
||||
avatar?: AvatarProps['source'];
|
||||
/** A boolean property indicating whether the user menu is currently open */
|
||||
open: boolean;
|
||||
/** A callback function to handle opening and closing the user menu */
|
||||
onToggle(): void;
|
||||
}
|
||||
|
||||
export default withContext<UserMenuProps, {}, UserMenuContextTypes>(
|
||||
UserMenuConsumer,
|
||||
)(UserMenu);
|
||||
function UserMenu({
|
||||
name,
|
||||
detail,
|
||||
avatar,
|
||||
initials,
|
||||
actions,
|
||||
message,
|
||||
onToggle,
|
||||
open,
|
||||
}: Props) {
|
||||
const showIndicator = Boolean(message);
|
||||
|
||||
const activatorContentMarkup = (
|
||||
<React.Fragment>
|
||||
<MessageIndicator active={showIndicator}>
|
||||
<Avatar
|
||||
size="small"
|
||||
source={avatar}
|
||||
initials={initials && initials.replace(' ', '')}
|
||||
/>
|
||||
</MessageIndicator>
|
||||
<span className={styles.Details}>
|
||||
<p className={styles.Name}>{name}</p>
|
||||
<p className={styles.Detail}>{detail}</p>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
activatorContent={activatorContentMarkup}
|
||||
open={open}
|
||||
onOpen={onToggle}
|
||||
onClose={onToggle}
|
||||
actions={actions}
|
||||
message={message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserMenu;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
import {IconableAction} from '../../../../../../types';
|
||||
import Avatar, {Props as AvatarProps} from '../../../../../Avatar';
|
||||
import MessageIndicator from '../../../../../MessageIndicator';
|
||||
import Menu, {MessageProps} from '../../../Menu';
|
||||
import styles from './UserMenu.scss';
|
||||
|
||||
export interface Props {
|
||||
/** An array of action objects that are rendered inside of a popover triggered by this menu */
|
||||
actions: {items: IconableAction[]}[];
|
||||
/** Accepts a message that facilitates direct, urgent communication with the merchant through the user menu */
|
||||
message?: MessageProps;
|
||||
/** A string detailing the merchant’s full name to be displayed in the user menu */
|
||||
name: string;
|
||||
/** A string allowing further details on the merchant’s name displayed in the user menu */
|
||||
detail?: string;
|
||||
/** The merchant’s initials, rendered in place of an avatar image when not provided */
|
||||
initials: AvatarProps['initials'];
|
||||
/** An avatar image representing the merchant */
|
||||
avatar?: AvatarProps['source'];
|
||||
/** A boolean property indicating whether the user menu is currently open */
|
||||
open: boolean;
|
||||
/** A callback function to handle opening and closing the user menu */
|
||||
onToggle(): void;
|
||||
}
|
||||
|
||||
function UserMenu({
|
||||
name,
|
||||
detail,
|
||||
avatar,
|
||||
initials,
|
||||
actions,
|
||||
message,
|
||||
onToggle,
|
||||
open,
|
||||
}: Props) {
|
||||
const showIndicator = Boolean(message);
|
||||
|
||||
const activatorContentMarkup = (
|
||||
<React.Fragment>
|
||||
<MessageIndicator active={showIndicator}>
|
||||
<Avatar
|
||||
size="small"
|
||||
source={avatar}
|
||||
initials={initials && initials.replace(' ', '')}
|
||||
/>
|
||||
</MessageIndicator>
|
||||
<span className={styles.Details}>
|
||||
<p className={styles.Name}>{name}</p>
|
||||
<p className={styles.Detail}>{detail}</p>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
activatorContent={activatorContentMarkup}
|
||||
open={open}
|
||||
onOpen={onToggle}
|
||||
onClose={onToggle}
|
||||
actions={actions}
|
||||
message={message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserMenu;
|
||||
@@ -1 +0,0 @@
|
||||
export {default, Props} from './UserMenu';
|
||||
@@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import {NotificationMajorMonotone} from '@shopify/polaris-icons';
|
||||
|
||||
import {ReactWrapper} from 'enzyme';
|
||||
import {mountWithAppProvider} from 'test-utilities';
|
||||
import Menu from '../../../../Menu';
|
||||
import UserMenu from '../UserMenu';
|
||||
|
||||
const actions = [{items: [{icon: NotificationMajorMonotone, onAction: noop}]}];
|
||||
const message = {
|
||||
title: 'test message',
|
||||
description: 'test description',
|
||||
link: {to: '/', content: 'Home'},
|
||||
action: {
|
||||
onClick: noop,
|
||||
content: 'New',
|
||||
},
|
||||
badge: {
|
||||
content: 'flashy new home card',
|
||||
status: 'new' as 'new',
|
||||
},
|
||||
};
|
||||
const userProps = {
|
||||
actions,
|
||||
name: 'Andrew Musgrave',
|
||||
detail: 'FED',
|
||||
initials: 'am',
|
||||
open: true,
|
||||
onToggle: noop,
|
||||
message,
|
||||
};
|
||||
|
||||
describe('<UserMenu />', () => {
|
||||
it('mounts', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(user).toBeTruthy();
|
||||
});
|
||||
|
||||
it('constructs activatorContent and passes it down to menu', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(returnMenuProp(user, 'activatorContent')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('passes the open prop down to menu', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(returnMenuProp(user, 'open')).toBe(true);
|
||||
});
|
||||
|
||||
it('passes the actions prop down to menu', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(returnMenuProp(user, 'actions')).toBe(actions);
|
||||
});
|
||||
|
||||
it('passes the message prop down to menu', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(returnMenuProp(user, 'message')).toBe(message);
|
||||
});
|
||||
|
||||
describe('onToggle', () => {
|
||||
it('passes the onToggle prop down to menu as onOpen', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(returnMenuProp(user, 'onOpen')).toBe(noop);
|
||||
});
|
||||
|
||||
it('passes the onToggle prop down to menu as onClose', () => {
|
||||
const user = mountWithAppProvider(<UserMenu {...userProps} />);
|
||||
expect(returnMenuProp(user, 'onClose')).toBe(noop);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function noop() {}
|
||||
|
||||
function returnMenuProp(wrapper: ReactWrapper, prop: string) {
|
||||
return wrapper.find(Menu).prop(prop);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export {default as UserMenu, Props as UserMenuProps} from './UserMenu';
|
||||
@@ -1,6 +0,0 @@
|
||||
// Variable prefixed with underscore to prevent unused variable error.
|
||||
// More info here: https://github.com/Shopify/polaris-react/pull/938
|
||||
import * as _React from 'react';
|
||||
import UserMenuContext from './context';
|
||||
|
||||
export default UserMenuContext.Consumer;
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import withContext from '../../../../WithContext';
|
||||
import {WithContextTypes} from '../../../../../types';
|
||||
import {UserMenuProps} from '../components';
|
||||
import Consumer from './Consumer';
|
||||
import {UserMenuContextTypes} from './context';
|
||||
|
||||
interface Props {
|
||||
userMenuProps: UserMenuProps;
|
||||
}
|
||||
|
||||
type ComposedProps = Props & WithContextTypes<UserMenuContextTypes>;
|
||||
|
||||
class Modifier extends React.Component<ComposedProps, {}> {
|
||||
static getDerivedStateFromProps({
|
||||
context: {setMobileUserMenuProps},
|
||||
userMenuProps,
|
||||
}: ComposedProps) {
|
||||
if (setMobileUserMenuProps) {
|
||||
setMobileUserMenuProps(userMenuProps);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
state: {} = {};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default withContext<Props, {}, UserMenuContextTypes>(Consumer)(Modifier);
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import {UserMenuProps} from '../components';
|
||||
import UserMenuContext, {UserMenuContextTypes} from './context';
|
||||
|
||||
interface Props {
|
||||
mobileView: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
class Provider extends React.Component<Props, UserMenuContextTypes> {
|
||||
static getDerivedStateFromProps(
|
||||
{mobileView: nextMobileView}: Props,
|
||||
{mobileView}: UserMenuContextTypes,
|
||||
) {
|
||||
if (nextMobileView !== mobileView) {
|
||||
return {mobileView: nextMobileView};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.setMobileUserMenuProps = this.setMobileUserMenuProps.bind(this);
|
||||
|
||||
this.state = {
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
mobileView: props.mobileView,
|
||||
mobileUserMenuProps: undefined,
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
setMobileUserMenuProps: this.setMobileUserMenuProps,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {state} = this;
|
||||
const {children} = this.props;
|
||||
return (
|
||||
<UserMenuContext.Provider value={state}>
|
||||
{children}
|
||||
</UserMenuContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
private setMobileUserMenuProps(mobileUserMenuProps: UserMenuProps) {
|
||||
const {mobileUserMenuProps: prevMobileUserMenuProps} = this.state;
|
||||
if (isEqual(mobileUserMenuProps, prevMobileUserMenuProps)) {
|
||||
return;
|
||||
}
|
||||
this.setState({mobileUserMenuProps});
|
||||
}
|
||||
}
|
||||
|
||||
export default Provider;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import {UserMenuProps} from '../components';
|
||||
|
||||
export interface UserMenuContextTypes {
|
||||
mobileView?: boolean;
|
||||
mobileUserMenuProps?: UserMenuProps;
|
||||
setMobileUserMenuProps?(props: UserMenuProps): void;
|
||||
}
|
||||
|
||||
export default React.createContext<UserMenuContextTypes>({});
|
||||
@@ -1,4 +0,0 @@
|
||||
export {default as Provider} from './Provider';
|
||||
export {default as Consumer} from './Consumer';
|
||||
export {default as Modifier} from './Modifier';
|
||||
export {default as UserMenuContext, UserMenuContextTypes} from './context';
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import {ViewMinor} from '@shopify/polaris-icons';
|
||||
import {mountWithAppProvider} from 'test-utilities';
|
||||
import {UserMenuProps} from '../../components';
|
||||
import UserMenuContext from '../context';
|
||||
import Modifier from '../Modifier';
|
||||
|
||||
describe('<Modifier />', () => {
|
||||
const userMenuProps: UserMenuProps = {
|
||||
actions: [{items: [{icon: ViewMinor}]}],
|
||||
name: '',
|
||||
initials: '',
|
||||
open: false,
|
||||
onToggle: noop,
|
||||
};
|
||||
|
||||
describe('userMenuProps', () => {
|
||||
it('sets the mobile user menu props', () => {
|
||||
const setMobileUserMenuPropsSpy = jest.fn();
|
||||
mountWithAppProvider(
|
||||
<UserMenuContext.Provider
|
||||
value={{
|
||||
setMobileUserMenuProps: setMobileUserMenuPropsSpy,
|
||||
}}
|
||||
>
|
||||
<Modifier userMenuProps={userMenuProps} />
|
||||
</UserMenuContext.Provider>,
|
||||
);
|
||||
expect(setMobileUserMenuPropsSpy).toHaveBeenCalledWith(userMenuProps);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function noop() {}
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from 'react';
|
||||
import {mountWithAppProvider, trigger} from 'test-utilities';
|
||||
import UserMenuContext from '../context';
|
||||
import Provider from '../Provider';
|
||||
|
||||
jest.mock('../context', () => ({
|
||||
Provider: ({children}: {children: React.ReactNode}) => children,
|
||||
}));
|
||||
|
||||
describe('<Provider />', () => {
|
||||
const mockProps = {
|
||||
mobileView: false,
|
||||
children: null,
|
||||
};
|
||||
|
||||
describe('mobileView', () => {
|
||||
it('gets passed into the context provider', () => {
|
||||
const mobileView = true;
|
||||
const provider = mountWithAppProvider(
|
||||
<Provider {...mockProps} mobileView={mobileView} />,
|
||||
);
|
||||
expect(
|
||||
provider.find(UserMenuContext.Provider).prop('value'),
|
||||
).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
mobileView,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('updates the context provider when it changes', () => {
|
||||
const mobileView = true;
|
||||
const newMobileView = false;
|
||||
const provider = mountWithAppProvider(
|
||||
<Provider {...mockProps} mobileView={mobileView} />,
|
||||
);
|
||||
provider.setProps({mobileView: newMobileView});
|
||||
expect(
|
||||
provider.find(UserMenuContext.Provider).prop('value'),
|
||||
).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
mobileView: newMobileView,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('children', () => {
|
||||
it('get passed into the context provider', () => {
|
||||
const children = <div />;
|
||||
const provider = mountWithAppProvider(
|
||||
<Provider {...mockProps}>{children}</Provider>,
|
||||
);
|
||||
expect(
|
||||
provider.find(UserMenuContext.Provider).contains(children),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('<UserMenuContext.Provider />', () => {
|
||||
it('receives updated menu props when setMobileUserMenuProps is called', () => {
|
||||
const newUserMenuProps = {initials: 'JD'};
|
||||
const provider = mountWithAppProvider(<Provider {...mockProps} />);
|
||||
trigger(
|
||||
provider.find(UserMenuContext.Provider),
|
||||
'value.setMobileUserMenuProps',
|
||||
newUserMenuProps,
|
||||
);
|
||||
expect(
|
||||
provider.find(UserMenuContext.Provider).prop('value'),
|
||||
).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
mobileUserMenuProps: newUserMenuProps,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import UserMenu from './UserMenu';
|
||||
|
||||
export {UserMenuProps as Props} from './components';
|
||||
export {Provider, Modifier} from './context';
|
||||
export {Props} from './UserMenu';
|
||||
export default UserMenu;
|
||||
|
||||
@@ -1,85 +1,25 @@
|
||||
import React from 'react';
|
||||
import {ViewMinor} from '@shopify/polaris-icons';
|
||||
import {mountWithAppProvider} from 'test-utilities';
|
||||
import {UserMenuContext} from '../context';
|
||||
import {UserMenu as UserMenuComponent, UserMenuProps} from '../components';
|
||||
import UserMenu from '../UserMenu';
|
||||
import UserMenu, {Props} from '../UserMenu';
|
||||
|
||||
describe('<UserMenu />', () => {
|
||||
describe('<UserMenuConsumer />', () => {
|
||||
const userMenuProps: UserMenuProps = {
|
||||
actions: [{items: [{icon: ViewMinor}]}],
|
||||
name: '',
|
||||
initials: '',
|
||||
open: false,
|
||||
onToggle: noop,
|
||||
};
|
||||
const userMenuProps: Props = {
|
||||
actions: [{items: [{icon: ViewMinor}]}],
|
||||
name: '',
|
||||
initials: '',
|
||||
open: false,
|
||||
onToggle: noop,
|
||||
};
|
||||
|
||||
it('renders with the given props', () => {
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenuContext.Provider
|
||||
value={{
|
||||
mobileView: false,
|
||||
mobileUserMenuProps: undefined,
|
||||
}}
|
||||
>
|
||||
<UserMenu {...userMenuProps} />
|
||||
</UserMenuContext.Provider>,
|
||||
);
|
||||
expect(userMenu.find(UserMenuComponent).props()).toStrictEqual(
|
||||
userMenuProps,
|
||||
);
|
||||
});
|
||||
it('renders with the given props', () => {
|
||||
const userMenu = mountWithAppProvider(<UserMenu {...userMenuProps} />);
|
||||
expect(userMenu.find(UserMenu).props()).toStrictEqual(userMenuProps);
|
||||
});
|
||||
|
||||
it('renders with the given props when in mobile view but no mobile props are available', () => {
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenuContext.Provider
|
||||
value={{
|
||||
mobileView: true,
|
||||
mobileUserMenuProps: undefined,
|
||||
}}
|
||||
>
|
||||
<UserMenu {...userMenuProps} />
|
||||
</UserMenuContext.Provider>,
|
||||
);
|
||||
expect(userMenu.find(UserMenuComponent).props()).toStrictEqual(
|
||||
userMenuProps,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders with the given props when mobile props are available but not in mobile view', () => {
|
||||
const mobileUserMenuProps = {...userMenuProps, initials: 'JD'};
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenuContext.Provider
|
||||
value={{
|
||||
mobileView: false,
|
||||
mobileUserMenuProps,
|
||||
}}
|
||||
>
|
||||
<UserMenu {...userMenuProps} />
|
||||
</UserMenuContext.Provider>,
|
||||
);
|
||||
expect(userMenu.find(UserMenuComponent).props()).toStrictEqual(
|
||||
userMenuProps,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders with the mobile props when available and in mobile view', () => {
|
||||
const mobileUserMenuProps = {...userMenuProps, initials: 'JD'};
|
||||
const userMenu = mountWithAppProvider(
|
||||
<UserMenuContext.Provider
|
||||
value={{
|
||||
mobileView: true,
|
||||
mobileUserMenuProps,
|
||||
}}
|
||||
>
|
||||
<UserMenu {...userMenuProps} />
|
||||
</UserMenuContext.Provider>,
|
||||
);
|
||||
expect(userMenu.find(UserMenuComponent).props()).toStrictEqual(
|
||||
mobileUserMenuProps,
|
||||
);
|
||||
});
|
||||
it('renders with the given props when in mobile view but no mobile props are available', () => {
|
||||
const userMenu = mountWithAppProvider(<UserMenu {...userMenuProps} />);
|
||||
expect(userMenu.find(UserMenu).props()).toStrictEqual(userMenuProps);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
export {default as Search, Props as SearchProps} from './Search';
|
||||
export {default as SearchField, Props as SearchFieldProps} from './SearchField';
|
||||
export {
|
||||
default as UserMenu,
|
||||
Props as UserProps,
|
||||
Provider as UserMenuProvider,
|
||||
Modifier as UserMenuModifier,
|
||||
} from './UserMenu';
|
||||
export {default as UserMenu, Props as UserProps} from './UserMenu';
|
||||
export {default as Menu, Props as MenuProps} from './Menu';
|
||||
export {MessageProps} from './Menu/components';
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import TopBar from './TopBar';
|
||||
|
||||
export {Props} from './TopBar';
|
||||
export {
|
||||
UserProps,
|
||||
SearchFieldProps,
|
||||
MessageProps,
|
||||
UserMenuModifier,
|
||||
UserMenuProvider,
|
||||
} from './components';
|
||||
export {UserProps, SearchFieldProps, MessageProps} from './components';
|
||||
export default TopBar;
|
||||
|
||||
Reference in New Issue
Block a user