Skip to content

Commit

Permalink
Merge pull request #23298 from storybookjs/norbert/improve-more-addons
Browse files Browse the repository at this point in the history
Addons: Improve code quality by using title as `FC` & sharing state via `useAddonState`
  • Loading branch information
ndelangen committed Jul 6, 2023
2 parents 8e1dbba + ca0d813 commit 36abf93
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 46 deletions.
55 changes: 50 additions & 5 deletions code/addons/a11y/src/manager.test.tsx
Expand Up @@ -4,7 +4,7 @@ import './manager';

jest.mock('@storybook/manager-api');
const mockedApi = api as unknown as jest.Mocked<api.API>;
mockedApi.getAddonState = jest.fn();
mockedApi.useAddonState = jest.fn();
const mockedAddons = api.addons as jest.Mocked<typeof api.addons>;
const registrationImpl = mockedAddons.register.mock.calls[0][1];

Expand All @@ -29,25 +29,70 @@ describe('A11yManager', () => {

it('should compute title with no issues', () => {
// given
mockedApi.getAddonState.mockImplementation(() => undefined);
mockedApi.useAddonState.mockImplementation(() => [undefined]);
registrationImpl(api as unknown as api.API);
const title = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === api.types.PANEL)?.title as Function;

// when / then
expect(title()).toBe('Accessibility');
expect(title()).toMatchInlineSnapshot(`
<div>
<Spaced
col={1}
>
<span
style={
Object {
"display": "inline-block",
"verticalAlign": "middle",
}
}
>
Accessibility
</span>
</Spaced>
</div>
`);
});

it('should compute title with issues', () => {
// given
mockedApi.getAddonState.mockImplementation(() => ({ violations: [{}], incomplete: [{}, {}] }));
mockedApi.useAddonState.mockImplementation(() => [
{
violations: [{}],
incomplete: [{}, {}],
},
]);
registrationImpl(mockedApi);
const title = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === api.types.PANEL)?.title as Function;

// when / then
expect(title()).toBe('Accessibility (3)');
expect(title()).toMatchInlineSnapshot(`
<div>
<Spaced
col={1}
>
<span
style={
Object {
"display": "inline-block",
"verticalAlign": "middle",
}
}
>
Accessibility
</span>
<Badge
status="neutral"
>
3
</Badge>
</Spaced>
</div>
`);
});
});
29 changes: 21 additions & 8 deletions code/addons/a11y/src/manager.tsx
@@ -1,11 +1,30 @@
import React from 'react';
import { addons, types } from '@storybook/manager-api';
import { addons, types, useAddonState } from '@storybook/manager-api';
import { Badge, Spaced } from '@storybook/components';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
import { VisionSimulator } from './components/VisionSimulator';
import { A11YPanel } from './components/A11YPanel';
import type { Results } from './components/A11yContext';
import { A11yContextProvider } from './components/A11yContext';

const Title = () => {
const [addonState] = useAddonState<Results>(ADDON_ID);
const violationsNb = addonState?.violations?.length || 0;
const incompleteNb = addonState?.incomplete?.length || 0;
const count = violationsNb + incompleteNb;

const suffix = count === 0 ? '' : <Badge status="neutral">{count}</Badge>;

return (
<div>
<Spaced col={1}>
<span style={{ display: 'inline-block', verticalAlign: 'middle' }}>Accessibility</span>
{suffix}
</Spaced>
</div>
);
};

addons.register(ADDON_ID, (api) => {
addons.add(PANEL_ID, {
title: '',
Expand All @@ -15,13 +34,7 @@ addons.register(ADDON_ID, (api) => {
});

addons.add(PANEL_ID, {
title() {
const addonState: Results = api?.getAddonState(ADDON_ID);
const violationsNb = addonState?.violations?.length || 0;
const incompleteNb = addonState?.incomplete?.length || 0;
const totalNb = violationsNb + incompleteNb;
return totalNb !== 0 ? `Accessibility (${totalNb})` : 'Accessibility';
},
title: Title,
type: types.PANEL,
render: ({ active = true, key }) => (
<A11yContextProvider key={key} active={active}>
Expand Down
45 changes: 19 additions & 26 deletions code/addons/actions/src/manager.tsx
@@ -1,47 +1,40 @@
import React, { useState } from 'react';
import { addons, types, useChannel } from '@storybook/manager-api';
import React from 'react';
import { addons, types, useAddonState, useChannel } from '@storybook/manager-api';
import { STORY_CHANGED } from '@storybook/core-events';
import { Badge, Spaced } from '@storybook/components';
import ActionLogger from './containers/ActionLogger';
import { ADDON_ID, CLEAR_ID, EVENT_ID, PANEL_ID, PARAM_KEY } from './constants';

function Title({ count }: { count: { current: number } }) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const [_, setRerender] = useState(false);
function Title() {
const [{ count }, setCount] = useAddonState(ADDON_ID, { count: 0 });

// Reactivity hack - force re-render on STORY_CHANGED, EVENT_ID and CLEAR_ID events
useChannel({
[EVENT_ID]: () => {
setRerender((r) => !r);
setCount((c) => ({ ...c, count: c.count + 1 }));
},
[STORY_CHANGED]: () => {
setRerender((r) => !r);
setCount((c) => ({ ...c, count: 0 }));
},
[CLEAR_ID]: () => {
setRerender((r) => !r);
setCount((c) => ({ ...c, count: 0 }));
},
});

const suffix = count.current === 0 ? '' : ` (${count.current})`;
return <>Actions{suffix}</>;
const suffix = count === 0 ? '' : <Badge status="neutral">{count}</Badge>;

return (
<div>
<Spaced col={1}>
<span style={{ display: 'inline-block', verticalAlign: 'middle' }}>Actions</span>
{suffix}
</Spaced>
</div>
);
}

addons.register(ADDON_ID, (api) => {
const countRef = { current: 0 };

api.on(STORY_CHANGED, (id) => {
countRef.current = 0;
});

api.on(EVENT_ID, () => {
countRef.current += 1;
});

api.on(CLEAR_ID, () => {
countRef.current = 0;
});

addons.add(PANEL_ID, {
title: <Title count={countRef} />,
title: Title,
type: types.PANEL,
render: ({ active, key }) => <ActionLogger key={key} api={api} active={!!active} />,
paramKey: PARAM_KEY,
Expand Down
15 changes: 11 additions & 4 deletions code/addons/controls/src/manager.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { addons, types, useArgTypes } from '@storybook/manager-api';
import { AddonPanel } from '@storybook/components';
import { AddonPanel, Badge, Spaced } from '@storybook/components';
import { ControlsPanel } from './ControlsPanel';
import { ADDON_ID, PARAM_KEY } from './constants';

Expand All @@ -9,14 +9,21 @@ function Title() {
const controlsCount = Object.values(rows).filter(
(argType) => argType?.control && !argType?.table?.disable
).length;
const suffix = controlsCount === 0 ? '' : ` (${controlsCount})`;
const suffix = controlsCount === 0 ? '' : <Badge status="neutral">{controlsCount}</Badge>;

return <>Controls{suffix}</>;
return (
<div>
<Spaced col={1}>
<span style={{ display: 'inline-block', verticalAlign: 'middle' }}>Controls</span>
{suffix}
</Spaced>
</div>
);
}

addons.register(ADDON_ID, (api) => {
addons.add(ADDON_ID, {
title: <Title />,
title: Title,
type: types.PANEL,
paramKey: PARAM_KEY,
render: ({ key, active }) => {
Expand Down
4 changes: 1 addition & 3 deletions code/addons/links/src/manager.ts
Expand Up @@ -3,9 +3,7 @@ import { addons } from '@storybook/manager-api';
import EVENTS, { ADDON_ID } from './constants';

addons.register(ADDON_ID, (api) => {
const channel = addons.getChannel();

channel.on(EVENTS.REQUEST, ({ kind, name }) => {
api.on(EVENTS.REQUEST, ({ kind, name }) => {
const id = api.storyId(kind, name);
api.emit(EVENTS.RECEIVE, id);
});
Expand Down

0 comments on commit 36abf93

Please sign in to comment.