Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Add support for groups to TooltipLinkList and use it in main menu #29507

Merged
merged 3 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/core/src/components/components/tooltip/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const Item = styled.div<ItemProps>(
({ theme }) => ({
width: '100%',
border: 'none',
borderRadius: theme.appBorderRadius,
background: 'none',
fontSize: theme.typography.size.s1,
transition: 'all 150ms ease-out',
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/components/components/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const Wrapper = styled.div<WrapperProps>(
drop-shadow(0px 5px 5px rgba(0,0,0,0.05))
drop-shadow(0 1px 3px rgba(0,0,0,0.1))
`,
borderRadius: theme.appBorderRadius,
borderRadius: theme.appBorderRadius + 2,
fontSize: theme.typography.size.s1,
}
: {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,45 @@ export const WithCustomIcon = {
],
},
} satisfies Story;

export const WithGroups = {
args: {
links: [
[
{
id: '1',
title: 'Link 1',
center: 'This is an addition description',
href: 'http://google.com',
onClick: onLinkClick,
},
],
[
{
id: '1',
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
title: 'Link 1',
center: 'This is an addition description',
icon: <LinkIcon />,
href: 'http://google.com',
onClick: onLinkClick,
},
{
id: '2',
title: 'Link 2',
center: 'This is an addition description',
href: 'http://google.com',
onClick: onLinkClick,
},
],
[
{
id: '2',
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
title: 'Link 2',
center: 'This is an addition description',
href: 'http://google.com',
onClick: onLinkClick,
},
],
],
},
} satisfies Story;
30 changes: 23 additions & 7 deletions code/core/src/components/components/tooltip/TooltipLinkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ const List = styled.div(
minWidth: 180,
overflow: 'hidden',
overflowY: 'auto',
maxHeight: 15.5 * 32, // 11.5 items
maxHeight: 15.5 * 32 + 8, // 15.5 items at 32px each + 8px padding
},
({ theme }) => ({
borderRadius: theme.appBorderRadius,
borderRadius: theme.appBorderRadius + 2,
})
);

const Group = styled.div(({ theme }) => ({
padding: 4,
'& + &': {
borderTop: `1px solid ${theme.appBorderColor}`,
},
}));

export interface Link extends Omit<ListItemProps, 'onClick'> {
id: string;
onClick?: (
Expand All @@ -42,17 +49,26 @@ const Item = ({ id, onClick, ...rest }: ItemProps) => {
};

export interface TooltipLinkListProps extends ComponentProps<typeof List> {
links: Link[];
links: Link[] | Link[][];
LinkWrapper?: LinkWrapperType;
}

export const TooltipLinkList = ({ links, LinkWrapper, ...props }: TooltipLinkListProps) => {
const isIndented = links.some((link) => link.icon);
const groups = Array.isArray(links[0]) ? (links as Link[][]) : [links as Link[]];
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
const isIndented = groups.some((group) => group.some((link) => link.icon));
return (
<List {...props}>
{links.map((link) => (
<Item key={link.id} isIndented={isIndented} LinkWrapper={LinkWrapper} {...link} />
))}
{groups
.filter((group) => group.length)
.map((group, index) => {
return (
<Group key={group.map((link) => link.id).join(`~${index}~`)}>
{group.map((link) => (
<Item key={link.id} isIndented={isIndented} LinkWrapper={LinkWrapper} {...link} />
))}
</Group>
);
})}
</List>
);
};
29 changes: 17 additions & 12 deletions code/core/src/manager/components/sidebar/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { CloseIcon, CogIcon } from '@storybook/icons';

import { transparentize } from 'polished';

import type { useMenu } from '../../container/Menu';
import { useLayout } from '../layout/LayoutProvider';

export type MenuList = ComponentProps<typeof TooltipLinkList>['links'];
export type MenuList = ReturnType<typeof useMenu>;

export const SidebarIconButton: FC<ComponentProps<typeof Button> & { highlighted: boolean }> =
styled(IconButton)<
Expand Down Expand Up @@ -60,17 +61,21 @@ const SidebarMenuList: FC<{
menu: MenuList;
onHide: () => void;
}> = ({ menu, onHide }) => {
const links = useMemo(() => {
return menu.map(({ onClick, ...rest }) => ({
...rest,
onClick: ((event, item) => {
if (onClick) {
onClick(event, item);
}
onHide();
}) as ClickHandler,
}));
}, [menu, onHide]);
const links = useMemo(
() =>
menu.map((group) =>
group.map(({ onClick, ...rest }) => ({
...rest,
onClick: ((event, item) => {
if (onClick) {
onClick(event, item);
}
onHide();
}) as ClickHandler,
}))
),
[menu, onHide]
);
return <TooltipLinkList links={links} />;
};

Expand Down
84 changes: 45 additions & 39 deletions code/core/src/manager/components/sidebar/TagsFilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type { Tag } from '@storybook/types';

import type { API } from '@storybook/core/manager-api';

import type { Link } from '../../../components/components/tooltip/TooltipLinkList';

const BUILT_IN_TAGS_SHOW = new Set(['play-fn']);

const Wrapper = styled.div({
Expand All @@ -29,56 +31,60 @@ export const TagsFilterPanel = ({
toggleTag,
isDevelopment,
}: TagsFilterPanelProps) => {
const theme = useTheme();
const userTags = allTags.filter((tag) => !BUILT_IN_TAGS_SHOW.has(tag));
const docsUrl = api.getDocsUrl({ subpath: 'writing-stories/tags#filtering-by-custom-tags' });
const items = allTags.map((tag) => {
const checked = selectedTags.includes(tag);
const id = `tag-${tag}`;
return {
id,
title: tag,
right: (
<input
type="checkbox"
id={id}
name={id}
value={tag}
checked={checked}
onChange={() => {
// The onClick handler higher up the tree will handle the toggle
// For controlled inputs, a onClick handler is needed, though
// Accessibility-wise this isn't optimal, but I guess that's a limitation
// of the current design of TooltipLinkList
}}
/>
),
onClick: () => toggleTag(tag),
};
}) as any[];

const groups = [
allTags.map((tag) => {
const checked = selectedTags.includes(tag);
const id = `tag-${tag}`;
return {
id,
title: tag,
right: (
<input
type="checkbox"
id={id}
name={id}
value={tag}
checked={checked}
onChange={() => {
// The onClick handler higher up the tree will handle the toggle
// For controlled inputs, a onClick handler is needed, though
// Accessibility-wise this isn't optimal, but I guess that's a limitation
// of the current design of TooltipLinkList
}}
/>
),
onClick: () => toggleTag(tag),
};
}),
] as Link[][];

if (allTags.length === 0) {
items.push({
id: 'no-tags',
title: 'There are no tags. Use tags to organize and filter your Storybook.',
isIndented: false,
});
groups.push([
{
id: 'no-tags',
title: 'There are no tags. Use tags to organize and filter your Storybook.',
isIndented: false,
},
]);
}

if (userTags.length === 0 && isDevelopment) {
items.push({
id: 'tags-docs',
title: 'Learn how to add tags',
icon: <ShareAltIcon />,
href: docsUrl,
style: {
borderTop: `4px solid ${theme.appBorderColor}`,
groups.push([
{
id: 'tags-docs',
title: 'Learn how to add tags',
icon: <ShareAltIcon />,
href: docsUrl,
},
});
]);
}

return (
<Wrapper>
<TooltipLinkList links={items} />
<TooltipLinkList links={groups} />
</Wrapper>
);
};
51 changes: 27 additions & 24 deletions code/core/src/manager/container/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { STORIES_COLLAPSE_ALL } from '@storybook/core/core-events';
import type { API, State } from '@storybook/core/manager-api';
import { shortcutToHumanString } from '@storybook/core/manager-api';

import type { Link } from '../../components/components/tooltip/TooltipLinkList';

const focusableUIElements = {
storySearchField: 'storybook-explorer-searchfield',
storyListMenu: 'storybook-explorer-menu',
Expand Down Expand Up @@ -58,8 +60,7 @@ export const useMenu = (
isPanelShown: boolean,
isNavShown: boolean,
enableShortcuts: boolean
) => {
const theme = useTheme();
): Link[][] => {
const shortcutKeys = api.getShortcutKeys();

const about = useMemo(
Expand Down Expand Up @@ -105,11 +106,8 @@ export const useMenu = (
title: 'Keyboard shortcuts',
onClick: () => api.changeSettingsTab('shortcuts'),
right: enableShortcuts ? <Shortcut keys={shortcutKeys.shortcutsPage} /> : null,
style: {
borderBottom: `4px solid ${theme.appBorderColor}`,
},
}),
[api, enableShortcuts, shortcutKeys.shortcutsPage, theme.appBorderColor]
[api, enableShortcuts, shortcutKeys.shortcutsPage]
);

const sidebarToggle = useMemo(
Expand Down Expand Up @@ -244,24 +242,29 @@ export const useMenu = (
}, [api, enableShortcuts, shortcutKeys]);

return useMemo(
() => [
about,
...(state.whatsNewData?.status === 'SUCCESS' ? [whatsNew] : []),
documentation,
shortcuts,
sidebarToggle,
toolbarToogle,
addonsToggle,
addonsOrientationToggle,
fullscreenToggle,
searchToggle,
up,
down,
prev,
next,
collapse,
...getAddonsShortcuts(),
],
() =>
[
[
about,
...(state.whatsNewData?.status === 'SUCCESS' ? [whatsNew] : []),
documentation,
shortcuts,
],
[
sidebarToggle,
toolbarToogle,
addonsToggle,
addonsOrientationToggle,
fullscreenToggle,
searchToggle,
up,
down,
prev,
next,
collapse,
],
getAddonsShortcuts(),
] satisfies Link[][],
[
about,
state,
Expand Down