Skip to content

Instantly share code, notes, and snippets.

@donaldpipowitch
Last active November 24, 2022 07:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donaldpipowitch/0e70bdcd7b5fae821fefbfe22b070926 to your computer and use it in GitHub Desktop.
Save donaldpipowitch/0e70bdcd7b5fae821fefbfe22b070926 to your computer and use it in GitHub Desktop.
Storybook Addon

This example shows how you can easily add a custom addon with TypeScript in an existing Storybook project.

This addon renders data on the manager side of Storybook, but what will be rendered can be influenced by the preview side of Storybook. We can easily interact with the addon within any Story.

import { useCounter } from './counter-addon';

// just use this hook in your stories like every other hook
const { add } = useCounter();
add(17);
import * as addons from '@storybook/addons';
import { types } from '@storybook/addons';
import type { DecoratorFunction } from '@storybook/addons';
import * as api from '@storybook/api';
import { AddonPanel } from '@storybook/components';
import React, {
createContext,
FC,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
// CONFIG
const ADDON_ID = 'custom-addon';
const PANEL_ID = `${ADDON_ID}/panel`;
const EVENTS = {
ADD: `${ADDON_ID}/add`,
RESET: `${ADDON_ID}/reset`,
};
// PANEL (MANAGER SIDE)
const CounterPanel: FC = () => {
const [count, setCount] = useState<number>(0);
api.useChannel({
[EVENTS.ADD]: (value: number) => setCount((oldValue) => oldValue + value),
[EVENTS.RESET]: () => setCount(0),
});
return <p>Current count: {count}.</p>;
};
// HOOK AND DECORATOR (PREVIEW SIDE)
type CounterHook = {
add: (value: number) => void;
};
const CounterContext = createContext<CounterHook>({ add: () => {} });
export function useCounter() {
return useContext(CounterContext);
}
export const WithCounterDecorator: DecoratorFunction = (storyFn) => {
const emit = addons.useChannel({});
useEffect(() => {
emit(EVENTS.RESET);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const value = useMemo(
() => ({
add: (value: number) => emit(EVENTS.ADD, value),
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return (
<CounterContext.Provider value={value}>
{storyFn() as any}
</CounterContext.Provider>
);
};
// SETUP
export const registerAddonCounter = (addons: addons.AddonStore) =>
addons.register(ADDON_ID, (api) => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'Counter',
render: ({ active = false, key }) => (
<AddonPanel key={key} active={active}>
<CounterPanel />
</AddonPanel>
),
});
});
import { addons } from '@storybook/addons';
import { registerAddonCounter } from './counter-addon';
registerAddonCounter(addons);
import { WithCounterDecorator } from './counter-addon';
export const decorators = [WithCounterDecorator];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment