Skip to content

Commit

Permalink
Merge pull request #28693 from storybookjs/sidebar-bottom-filters
Browse files Browse the repository at this point in the history
UI: Replace `experimental_SIDEBAR_BOTTOM` API with a status filter UI
  • Loading branch information
yannbf committed Jul 30, 2024
2 parents df101c4 + 257f53d commit 7a7944a
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 71 deletions.
14 changes: 12 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<h1>Migration</h1>

- [From version 8.2.x to 8.3.x](#from-version-82x-to-83x)
- [Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types](#removed-experimental_sidebar_bottom-and-deprecated-experimental_sidebar_top-addon-types)
- [From version 8.1.x to 8.2.x](#from-version-81x-to-82x)
- [Failed to resolve import "@storybook/X" error](#failed-to-resolve-import-storybookx-error)
- [Preview.js globals renamed to initialGlobals](#previewjs-globals-renamed-to-initialglobals)
Expand Down Expand Up @@ -414,6 +416,14 @@
- [Packages renaming](#packages-renaming)
- [Deprecated embedded addons](#deprecated-embedded-addons)

## From version 8.2.x to 8.3.x

### Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types

The experimental SIDEBAR_BOTTOM addon type was removed in favor of a built-in filter UI. The enum type definition will remain available until Storybook 9.0 but will be ignored. Similarly the experimental SIDEBAR_TOP addon type is deprecated and will be removed in a future version.

These APIs allowed addons to render arbitrary content in the Storybook sidebar. Due to potential conflicts between addons and challenges regarding styling, these APIs are/will be removed. In the future, Storybook will provide declarative API hooks to allow addons to add content to the sidebar without risk of conflicts or UI inconsistencies. One such API is `experimental_updateStatus` which allow addons to set a status for stories. The SIDEBAR_BOTTOM slot is now used to allow filtering stories with a given status.

## From version 8.1.x to 8.2.x

### Failed to resolve import "@storybook/X" error
Expand Down Expand Up @@ -2324,8 +2334,8 @@ export default config;

#### Vite builder uses Vite config automatically

When using a [Vite-based framework](#framework-field-mandatory), Storybook will automatically use your `vite.config.(ctm)js` config file starting in 7.0.
Some settings will be overridden by Storybook so that it can function properly, and the merged settings can be modified using `viteFinal` in `.storybook/main.js` (see the [Storybook Vite configuration docs](https://storybook.js.org/docs/react/builders/vite#configuration)).
When using a [Vite-based framework](#framework-field-mandatory), Storybook will automatically use your `vite.config.(ctm)js` config file starting in 7.0.
Some settings will be overridden by Storybook so that it can function properly, and the merged settings can be modified using `viteFinal` in `.storybook/main.js` (see the [Storybook Vite configuration docs](https://storybook.js.org/docs/react/builders/vite#configuration)).
If you were using `viteFinal` in 6.5 to simply merge in your project's standard Vite config, you can now remove it.

For Svelte projects this means that the `svelteOptions` property in the `main.js` config should be omitted, as it will be loaded automatically via the project's `vite.config.js`.
Expand Down
3 changes: 3 additions & 0 deletions code/core/src/core-events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ enum events {
STORY_ARGS_UPDATED = 'storyArgsUpdated',
// Reset either a single arg of a story all args of a story
RESET_STORY_ARGS = 'resetStoryArgs',
// Emitted after a filter is set
SET_FILTER = 'setFilter',
// Emitted by the preview at startup once it knows the initial set of globals+globalTypes
SET_GLOBALS = 'setGlobals',
// Tell the preview to update the value of a global
Expand Down Expand Up @@ -114,6 +116,7 @@ export const {
SELECT_STORY,
SET_CONFIG,
SET_CURRENT_STORY,
SET_FILTER,
SET_GLOBALS,
SET_INDEX,
SET_STORIES,
Expand Down
3 changes: 3 additions & 0 deletions code/core/src/manager-api/modules/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
DOCS_PREPARED,
SET_CURRENT_STORY,
SET_CONFIG,
SET_FILTER,
} from '@storybook/core/core-events';
import { logger } from '@storybook/core/client-logger';

Expand Down Expand Up @@ -662,6 +663,8 @@ export const init: ModuleFn<SubAPI, SubState> = ({
Object.entries(refs).forEach(([refId, { internal_index, ...ref }]) => {
fullAPI.setRef(refId, { ...ref, storyIndex: internal_index }, true);
});

provider.channel?.emit(SET_FILTER, { id });
},
};

Expand Down
40 changes: 40 additions & 0 deletions code/core/src/manager/components/sidebar/FilterToggle.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { fn } from '@storybook/test';
import { FilterToggle } from './FilterToggle';

export default {
component: FilterToggle,
args: {
active: false,
onClick: fn(),
},
};

export const Errors = {
args: {
count: 2,
label: 'Error',
status: 'critical',
},
};

export const ErrorsActive = {
args: {
...Errors.args,
active: true,
},
};

export const Warning = {
args: {
count: 12,
label: 'Warning',
status: 'warning',
},
};

export const WarningActive = {
args: {
...Warning.args,
active: true,
},
};
59 changes: 59 additions & 0 deletions code/core/src/manager/components/sidebar/FilterToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Badge as BaseBadge, IconButton } from '@storybook/components';
import { css, styled } from '@storybook/theming';
import React, { type ComponentProps } from 'react';

const Badge = styled(BaseBadge)(({ theme }) => ({
padding: '4px 8px',
fontSize: theme.typography.size.s1,
}));

const Button = styled(IconButton)(
({ theme }) => ({
fontSize: theme.typography.size.s2,
'&:hover [data-badge][data-status=warning], [data-badge=true][data-status=warning]': {
background: '#E3F3FF',
borderColor: 'rgba(2, 113, 182, 0.1)',
color: '#0271B6',
},
'&:hover [data-badge][data-status=critical], [data-badge=true][data-status=critical]': {
background: theme.background.negative,
boxShadow: `inset 0 0 0 1px rgba(182, 2, 2, 0.1)`,
color: theme.color.negativeText,
},
}),
({ active, theme }) =>
!active &&
css({
'&:hover': {
color: theme.base === 'light' ? theme.color.defaultText : theme.color.light,
},
})
);

const Label = styled.span(({ theme }) => ({
color: theme.base === 'light' ? theme.color.defaultText : theme.color.light,
}));

interface FilterToggleProps {
active: boolean;
count: number;
label: string;
status: ComponentProps<typeof Badge>['status'];
}

export const FilterToggle = ({
active,
count,
label,
status,
...props
}: FilterToggleProps & Omit<ComponentProps<typeof Button>, 'status'>) => {
return (
<Button active={active} {...props}>
<Badge status={status} data-badge={active} data-status={status}>
{count}
</Badge>
<Label>{`${label}${count === 1 ? '' : 's'}`}</Label>
</Button>
);
};
103 changes: 45 additions & 58 deletions code/core/src/manager/components/sidebar/Sidebar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { IndexHash, State } from '@storybook/core/manager-api';
import { ManagerContext, types } from '@storybook/core/manager-api';
import type { StoryObj, Meta } from '@storybook/react';
import { within, userEvent, expect, fn } from '@storybook/test';
import type { Addon_SidebarTopType } from '@storybook/core/types';
import type { Addon_SidebarTopType, API_StatusState } from '@storybook/core/types';
import { Button, IconButton } from '@storybook/core/components';
import { FaceHappyIcon } from '@storybook/icons';
import { Sidebar, DEFAULT_REF_ID } from './Sidebar';
Expand All @@ -26,6 +26,26 @@ const storyId = 'root-1-child-a2--grandchild-a1-1';
export const simpleData = { menu, index, storyId };
export const loadingData = { menu };

const managerContext: any = {
state: {
docsOptions: {
defaultName: 'Docs',
autodocs: 'tag',
docsMode: false,
},
},
api: {
emit: fn().mockName('api::emit'),
on: fn().mockName('api::on'),
off: fn().mockName('api::off'),
getShortcutKeys: fn(() => ({ search: ['control', 'shift', 's'] })).mockName(
'api::getShortcutKeys'
),
selectStory: fn().mockName('api::selectStory'),
experimental_setFilter: fn().mockName('api::experimental_setFilter'),
},
};

const meta = {
component: Sidebar,
title: 'Sidebar/Sidebar',
Expand All @@ -44,28 +64,7 @@ const meta = {
},
decorators: [
(storyFn) => (
<ManagerContext.Provider
value={
{
state: {
docsOptions: {
defaultName: 'Docs',
autodocs: 'tag',
docsMode: false,
},
},
api: {
emit: fn().mockName('api::emit'),
on: fn().mockName('api::on'),
off: fn().mockName('api::off'),
getShortcutKeys: fn(() => ({ search: ['control', 'shift', 's'] })).mockName(
'api::getShortcutKeys'
),
selectStory: fn().mockName('api::selectStory'),
},
} as any
}
>
<ManagerContext.Provider value={managerContext}>
<LayoutProvider>
<IconSymbols />
{storyFn()}
Expand Down Expand Up @@ -218,41 +217,29 @@ export const Searching: Story = {
};

export const Bottom: Story = {
args: {
bottom: [
{
id: '1',
type: types.experimental_SIDEBAR_BOTTOM,
render: () => (
<Button>
<FaceHappyIcon />
Custom addon A
</Button>
),
},
{
id: '2',
type: types.experimental_SIDEBAR_BOTTOM,
render: () => (
<Button>
{' '}
<FaceHappyIcon />
Custom addon B
</Button>
),
},
{
id: '3',
type: types.experimental_SIDEBAR_BOTTOM,
render: () => (
<IconButton>
{' '}
<FaceHappyIcon />
</IconButton>
),
},
],
},
decorators: [
(storyFn) => (
<ManagerContext.Provider
value={{
...managerContext,
state: {
...managerContext.state,
status: {
[storyId]: {
vitest: { status: 'warn', title: '', description: '' },
vta: { status: 'error', title: '', description: '' },
},
'root-1-child-a2--grandchild-a1-2': {
vitest: { status: 'warn', title: '', description: '' },
},
} satisfies API_StatusState,
},
}}
>
{storyFn()}
</ManagerContext.Provider>
),
],
};

/**
Expand Down
7 changes: 2 additions & 5 deletions code/core/src/manager/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { SearchResults } from './SearchResults';
import type { CombinedDataset, Selection } from './types';
import { useLastViewed } from './useLastViewed';
import { MEDIA_DESKTOP_BREAKPOINT } from '../../constants';
import { SidebarBottom } from './SidebarBottom';

export const DEFAULT_REF_ID = 'storybook_internal';

Expand Down Expand Up @@ -109,7 +110,6 @@ export interface SidebarProps extends API_LoadedRefData {
status: State['status'];
menu: any[];
extra: Addon_SidebarTopType[];
bottom?: Addon_SidebarBottomType[];
storyId?: string;
refId?: string;
menuHighlighted?: boolean;
Expand All @@ -128,7 +128,6 @@ export const Sidebar = React.memo(function Sidebar({
previewInitialized,
menu,
extra,
bottom = [],
menuHighlighted = false,
enableShortcuts = true,
refs = {},
Expand Down Expand Up @@ -194,9 +193,7 @@ export const Sidebar = React.memo(function Sidebar({
</ScrollArea>
{isLoading ? null : (
<Bottom className="sb-bar">
{bottom.map(({ id, render: Render }) => (
<Render key={id} />
))}
<SidebarBottom />
</Bottom>
)}
</Container>
Expand Down
42 changes: 42 additions & 0 deletions code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { fn } from '@storybook/test';

import { SidebarBottomBase } from './SidebarBottom';

export default {
component: SidebarBottomBase,
args: {
api: {
experimental_setFilter: fn(),
emit: fn(),
},
},
};

export const Errors = {
args: {
status: {
one: { 'sidebar-bottom-filter': { status: 'error' } },
two: { 'sidebar-bottom-filter': { status: 'error' } },
},
},
};

export const Warnings = {
args: {
status: {
one: { 'sidebar-bottom-filter': { status: 'warn' } },
two: { 'sidebar-bottom-filter': { status: 'warn' } },
},
},
};

export const Both = {
args: {
status: {
one: { 'sidebar-bottom-filter': { status: 'warn' } },
two: { 'sidebar-bottom-filter': { status: 'warn' } },
three: { 'sidebar-bottom-filter': { status: 'error' } },
four: { 'sidebar-bottom-filter': { status: 'error' } },
},
},
};
Loading

0 comments on commit 7a7944a

Please sign in to comment.