Skip to content

Commit

Permalink
Menu design upgrades
Browse files Browse the repository at this point in the history
1. Icons are always on the left. If there is one icon in a menu item, every other menu item will have an indent to match the space of the icon as seen in the Storybook menu.
2. If there are no icons, the item label is not indented.
3. There is a node on the right of each menu item that can be populated with keyboard shortcuts, illustrations, etc.
4. Active items (denoted with a checkmark) will be both bold and blue.
5. There will be an optional `description` text node that will be below the item label.
6. Keyboard shortcuts will list all commands in one container.
7. The decorative menu tooltip arrows are no longer shown.
  • Loading branch information
valentinpalkovic committed Feb 3, 2023
1 parent 2ef671e commit 58399ba
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 176 deletions.
40 changes: 8 additions & 32 deletions code/addons/toolbars/src/components/ToolbarMenuListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import type { ReactNode } from 'react';
import type { ComponentProps } from 'react';
import React from 'react';
import type { TooltipLinkList } from '@storybook/components';
import { Icons } from '@storybook/components';
import type { ToolbarItem } from '../types';

interface ListItem {
id: string;
left?: ReactNode;
title?: ReactNode;
right?: ReactNode;
active?: boolean;
onClick?: () => void;
}

type ToolbarMenuListItemProps = {
export type ToolbarMenuListItemProps = {
currentValue: string;
onClick: () => void;
} & ToolbarItem;
Expand All @@ -28,34 +20,18 @@ export const ToolbarMenuListItem = ({
currentValue,
}: ToolbarMenuListItemProps) => {
const Icon = icon && <Icons style={{ opacity: 1 }} icon={icon} />;
const hasContent = left || right || title;

const Item: ListItem = {
const Item: ComponentProps<typeof TooltipLinkList>['links'][0] = {
id: value || currentValue,
active: currentValue === value,
right,
title,
left,
onClick,
};

if (left) {
Item.left = left;
}

if (right) {
Item.right = right;
}

if (title) {
Item.title = title;
}

if (icon && !hideIcon) {
if (hasContent && !right) {
Item.right = Icon;
} else if (hasContent && !left) {
Item.left = Icon;
} else if (!hasContent) {
Item.right = Icon;
}
Item.left = Icon;
}

return Item;
Expand Down
2 changes: 2 additions & 0 deletions code/ui/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export { WithTooltip, WithTooltipPure } from './tooltip/lazy-WithTooltip';
export { TooltipMessage } from './tooltip/TooltipMessage';
export { TooltipNote } from './tooltip/TooltipNote';
export { TooltipLinkList } from './tooltip/TooltipLinkList';
export { default as ListItem } from './tooltip/ListItem';

// Toolbar and subcomponents
export { Tabs, TabsState, TabBar, TabWrapper } from './tabs/tabs';
Expand All @@ -68,6 +69,7 @@ export { AddonPanel } from './addon-panel/addon-panel';
// Graphics
export type { IconsProps } from './icon/icon';
export { Icons, Symbols } from './icon/icon';
export { icons } from './icon/icons';
export { StorybookLogo } from './brand/StorybookLogo';
export { StorybookIcon } from './brand/StorybookIcon';

Expand Down
1 change: 0 additions & 1 deletion code/ui/components/src/tabs/tabs.hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export function useList(list: ChildrenList) {
<>
<WithTooltip
interactive
withArrows={false}
visible={isTooltipVisible}
onVisibleChange={setTooltipVisible}
placement="bottom"
Expand Down
105 changes: 54 additions & 51 deletions code/ui/components/src/tooltip/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { FC, ReactNode, ComponentProps } from 'react';
import type { FC, ReactNode, ComponentProps, ReactElement } from 'react';
import React from 'react';
import { styled } from '@storybook/theming';
import memoize from 'memoizerific';
import { transparentize } from 'polished';
import { Icons } from '../icon/icon';
import { icons } from '../icon/icons';

export interface TitleProps {
children?: ReactNode;
Expand Down Expand Up @@ -47,53 +49,35 @@ export interface RightProps {
active?: boolean;
}

const Right = styled.span<RightProps>(
{
'& svg': {
transition: 'all 200ms ease-out',
opacity: 0,
height: 12,
width: 12,
margin: '3px 0',
verticalAlign: 'top',
},
'& path': {
fill: 'inherit',
},
const Right = styled.span<RightProps>({
display: 'flex',
'& svg': {
height: 12,
width: 12,
margin: '3px 0',
verticalAlign: 'top',
},
({ active, theme }) =>
active
? {
'& svg': {
opacity: 1,
},
'& path': {
fill: theme.color.secondary,
},
}
: {}
);

const Center = styled.span({
flex: 1,
textAlign: 'left',
display: 'inline-flex',

'& > * + *': {
paddingLeft: 10,
'& path': {
fill: 'inherit',
},
});

const Center = styled.span<{ isIndented: boolean }>(
{
flex: 1,
textAlign: 'left',
display: 'flex',
flexDirection: 'column',
},
({ isIndented }) => (isIndented ? { marginLeft: 24 } : {})
);

export interface CenterTextProps {
active?: boolean;
disabled?: boolean;
}

const CenterText = styled.span<CenterTextProps>(
{
flex: 1,
textAlign: 'center',
},
({ active, theme }) =>
active
? {
Expand All @@ -112,17 +96,22 @@ export interface LeftProps {
active?: boolean;
}

const Left = styled.span<LeftProps>(({ active, theme }) =>
active
? {
'& svg': {
opacity: 1,
},
'& path': {
fill: theme.color.primary,
},
}
: {}
const Left = styled.span<LeftProps>(
({ active, theme }) =>
active
? {
'& svg': {
opacity: 1,
},
'& svg path': {
fill: theme.color.secondary,
},
}
: {},
() => ({
display: 'flex',
maxWidth: 14,
})
);

export interface ItemProps {
Expand Down Expand Up @@ -188,14 +177,20 @@ export type LinkWrapperType = FC<any>;

export interface ListItemProps extends Omit<ComponentProps<typeof Item>, 'href' | 'title'> {
loading?: boolean;
/**
* @deprecated ReactNode elements on the right side of the list item are deprecated.
* Use `icon` property instead.
*/
left?: ReactNode;
title?: ReactNode;
center?: ReactNode;
right?: ReactNode;
icon?: keyof typeof icons | ReactElement;
active?: boolean;
disabled?: boolean;
href?: string;
LinkWrapper?: LinkWrapperType;
isIndented?: boolean;
}

const ListItem: FC<ListItemProps> = ({
Expand All @@ -204,8 +199,10 @@ const ListItem: FC<ListItemProps> = ({
title,
center,
right,
icon,
active,
disabled,
isIndented,
href,
onClick,
LinkWrapper,
Expand All @@ -214,11 +211,17 @@ const ListItem: FC<ListItemProps> = ({
const itemProps = getItemProps(onClick, href, LinkWrapper);
const commonProps = { active, disabled };

const isStorybookIcon = typeof icon === 'string' && icons[icon];

return (
<Item {...commonProps} {...rest} {...itemProps}>
{left && <Left {...commonProps}>{left}</Left>}
{icon ? (
<Left {...commonProps}>{isStorybookIcon ? <Icons icon={icon} /> : icon}</Left>
) : (
left && <Left {...commonProps}>{left}</Left>
)}
{title || center ? (
<Center>
<Center isIndented={!left && !icon && isIndented}>
{title && (
<Title {...commonProps} loading={loading}>
{title}
Expand Down
2 changes: 1 addition & 1 deletion code/ui/components/src/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export interface TooltipProps {

export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
(
{ placement, hasChrome, children, arrowProps, tooltipRef, color, withArrows = true, ...props },
{ placement, hasChrome, children, arrowProps, tooltipRef, color, withArrows, ...props },
ref
) => {
return (
Expand Down
Loading

0 comments on commit 58399ba

Please sign in to comment.