Skip to content

Commit

Permalink
Merge pull request #22975 from storybookjs/norbert/add-jsdoc-preview-api
Browse files Browse the repository at this point in the history
Maintenance: add jsdoc comments to preview-api APIs
  • Loading branch information
ndelangen authored Jun 8, 2023
2 parents 6f0c2fc + 3e12815 commit cfe01a1
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 17 deletions.
170 changes: 158 additions & 12 deletions code/lib/preview-api/src/modules/addons/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,36 @@ function useMemoLike<T>(name: string, nextCreate: () => T, deps: any[] | undefin
return memoizedState;
}

/* Returns a memoized value, see https://reactjs.org/docs/hooks-reference.html#usememo */
/**
* Returns a memoized value.
* @template T The type of the memoized value.
* @param {() => T} nextCreate A function that returns the memoized value.
* @param {any[]} [deps] An optional array of dependencies. If any of the dependencies change, the memoized value will be recomputed.
* @returns {T} The memoized value.
* @example
* const memoizedValue = useMemo(() => {
* return doExpensiveCalculation(a, b);
* }, [a, b]);
*/
export function useMemo<T>(nextCreate: () => T, deps?: any[]): T {
return useMemoLike('useMemo', nextCreate, deps);
}

/** Returns a memoized callback.
*
* @template T The type of the callback function.
* @param {T} callback The callback function to memoize.
* @param {any[]} [deps] An optional array of dependencies. If any of the dependencies change, the memoized callback will be recomputed.
* @returns {T} The memoized callback.
*
* @example
* const memoizedCallback = useCallback(
* () => {
* doSomething(a, b);
* },
* [a, b],
* );
*/
/* Returns a memoized callback, see https://reactjs.org/docs/hooks-reference.html#usecallback */
export function useCallback<T>(callback: T, deps?: any[]): T {
return useMemoLike('useCallback', () => callback, deps);
Expand All @@ -313,6 +338,17 @@ function useRefLike<T>(name: string, initialValue: T): { current: T } {
return useMemoLike(name, () => ({ current: initialValue }), []);
}

/**
* Returns a mutable ref object.
*
* @template T The type of the ref object.
* @param {T} initialValue The initial value of the ref object.
* @returns {{ current: T }} The mutable ref object.
*
* @example
* const ref = useRef(0);
* ref.current = 1;
*/
/* Returns a mutable ref object, see https://reactjs.org/docs/hooks-reference.html#useref */
export function useRef<T>(initialValue: T): { current: T } {
return useRefLike('useRef', initialValue);
Expand Down Expand Up @@ -349,14 +385,58 @@ function useStateLike<S>(
return [stateRef.current, setState];
}

/* Returns a stateful value, and a function to update it, see https://reactjs.org/docs/hooks-reference.html#usestate */
/**
* Returns a stateful value and a function to update it.
*
* @template S The type of the state.
* @param {(() => S) | S} initialState The initial state value or a function that returns the initial state value.
* @returns {[S, (update: ((prevState: S) => S) | S) => void]} An array containing the current state value and a function to update it.
*
* @example
* const [count, setCount] = useState(0);
* setCount(count + 1);
*/
export function useState<S>(
initialState: (() => S) | S
): [S, (update: ((prevState: S) => S) | S) => void] {
return useStateLike('useState', initialState);
}

/* A redux-like alternative to useState, see https://reactjs.org/docs/hooks-reference.html#usereducer */
/**
* A redux-like alternative to useState.
*
* @template S The type of the state.
* @template A The type of the action.
* @param {(state: S, action: A) => S} reducer The reducer function that returns the new state.
* @param {S | I} initialArg The initial state value or the initial argument for the init function.
* @param {(initialArg: I) => S} [init] An optional function that returns the initial state value.
* @returns {[S, (action: A) => void]} An array containing the current state value and a function to dispatch actions.
*
* @example
* const initialState = { count: 0 };
*
* function reducer(state, action) {
* switch (action.type) {
* case 'increment':
* return { count: state.count + 1 };
* case 'decrement':
* return { count: state.count - 1 };
* default:
* throw new Error();
* }
* }
*
* function Counter() {
* const [state, dispatch] = useReducer(reducer, initialState);
* return (
* <>
* Count: {state.count}
* <button onClick={() => dispatch({ type: 'increment' })}>+</button>
* <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
* </>
* );
* }
*/
export function useReducer<S, A>(
reducer: (state: S, action: A) => S,
initialState: S
Expand All @@ -377,10 +457,22 @@ export function useReducer<S, A>(
return [state, dispatch];
}

/*
Triggers a side effect, see https://reactjs.org/docs/hooks-reference.html#usestate
Effects are triggered synchronously after rendering the story
*/
/**
* Triggers a side effect, see https://reactjs.org/docs/hooks-reference.html#usestate
* Effects are triggered synchronously after rendering the story
*
* @param {() => (() => void) | void} create A function that creates the effect. It should return a cleanup function, or nothing.
* @param {any[]} [deps] An optional array of dependencies. If any of the dependencies change, the effect will be re-run.
* @returns {void}
*
* @example
* useEffect(() => {
* // Do something after rendering the story
* return () => {
* // Do something when the component unmounts or the effect is re-run
* };
* }, [dependency1, dependency2]);
*/
export function useEffect(create: () => (() => void) | void, deps?: any[]): void {
const hooks = getHooksContextOrThrow();
const effect = useMemoLike('useEffect', () => ({ create }), deps);
Expand All @@ -397,7 +489,18 @@ export interface EventMap {
[eventId: string]: Listener;
}

/* Accepts a map of Storybook channel event listeners, returns an emit function */
/**
* Subscribes to events emitted by the Storybook channel and returns a function to emit events.
*
* @param {EventMap} eventMap A map of event listeners to subscribe to.
* @param {any[]} [deps=[]] An optional array of dependencies. If any of the dependencies change, the event listeners will be re-subscribed.
* @returns {(...args: any[]) => void} A function to emit events to the Storybook channel.
*
* @example
* // Subscribe to an event and emit it
* const emit = useChannel({ 'my-event': (arg1, arg2) => console.log(arg1, arg2) });
* emit('my-event', 'Hello', 'world!');
*/
export function useChannel(eventMap: EventMap, deps: any[] = []) {
const channel = addons.getChannel();

Expand All @@ -413,7 +516,18 @@ export function useChannel(eventMap: EventMap, deps: any[] = []) {
return useCallback(channel.emit.bind(channel), [channel]);
}

/* Returns current story context */
/**
* Returns the current story context, including the story's ID, parameters, and other metadata.
*
* @template TRenderer The type of the story's renderer.
* @template TArgs The type of the story's args.
* @returns {StoryContext<TRenderer>} The current story context.
*
* @example
* const { id, parameters } = useStoryContext();
* console.log(`Current story ID: ${id}`);
* console.log(`Current story parameters: ${JSON.stringify(parameters)}`);
*/
export function useStoryContext<
TRenderer extends Renderer,
TArgs extends Args = Args
Expand All @@ -426,7 +540,19 @@ export function useStoryContext<
return currentContext;
}

/* Returns current value of a story parameter */
/**
* Returns the value of a specific parameter for the current story, or a default value if the parameter is not set.
*
* @template S The type of the parameter value.
* @param {string} parameterKey The key of the parameter to retrieve.
* @param {S} [defaultValue] An optional default value to return if the parameter is not set.
* @returns {S | undefined} The value of the parameter, or the default value if the parameter is not set.
*
* @example
* // Retrieve the value of a parameter named "myParam"
* const myParamValue = useParameter<string>('myParam', 'default value');
* console.log(`The value of myParam is: ${myParamValue}`);
*/
export function useParameter<S>(parameterKey: string, defaultValue?: S): S | undefined {
const { parameters } = useStoryContext();
if (parameterKey) {
Expand All @@ -435,7 +561,18 @@ export function useParameter<S>(parameterKey: string, defaultValue?: S): S | und
return undefined;
}

/* Returns current value of story args */
/**
* Returns the current args for the story, and functions to update and reset them.
*
* @template TArgs The type of the story's args.
* @returns {[TArgs, (newArgs: Partial<TArgs>) => void, (argNames?: (keyof TArgs)[]) => void]} An array containing the current args, a function to update them, and a function to reset them.
*
* @example
* const [args, updateArgs, resetArgs] = useArgs<{ name: string, age: number }>();
* console.log(`Current args: ${JSON.stringify(args)}`);
* updateArgs({ name: 'John' });
* resetArgs(['name']);
*/
export function useArgs<TArgs extends Args = Args>(): [
TArgs,
(newArgs: Partial<TArgs>) => void,
Expand All @@ -457,7 +594,16 @@ export function useArgs<TArgs extends Args = Args>(): [
return [args as TArgs, updateArgs, resetArgs];
}

/* Returns current value of global args */
/**
* Returns the current global args for the story, and a function to update them.
*
* @returns {[Args, (newGlobals: Args) => void]} An array containing the current global args, and a function to update them.
*
* @example
* const [globals, updateGlobals] = useGlobals();
* console.log(`Current globals: ${JSON.stringify(globals)}`);
* updateGlobals({ theme: 'dark' });
*/
export function useGlobals(): [Args, (newGlobals: Args) => void] {
const channel = addons.getChannel();
const { globals } = useStoryContext();
Expand Down
22 changes: 22 additions & 0 deletions code/lib/preview-api/src/modules/addons/make-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ export interface MakeDecoratorOptions {
wrapper: Addon_StoryWrapper;
}

/**
* Creates a Storybook decorator function that can be used to wrap stories with additional functionality.
*
* @param {MakeDecoratorOptions} options - The options for the decorator.
* @param {string} options.name - The name of the decorator.
* @param {string} options.parameterName - The name of the parameter that will be used to pass options to the decorator.
* @param {Addon_StoryWrapper} options.wrapper - The function that will be used to wrap the story.
* @param {boolean} [options.skipIfNoParametersOrOptions=false] - Whether to skip the decorator if no options or parameters are provided.
* @returns {MakeDecoratorResult} A function that can be used as a Storybook decorator.
*
* @example
* const myDecorator = makeDecorator({
* name: 'My Decorator',
* parameterName: 'myDecorator',
* wrapper: (storyFn, context, { options }) => {
* const { myOption } = options;
* return <div style={{ backgroundColor: myOption }}>{storyFn()}</div>;
* },
* });
*
* export const decorators = [myDecorator];
*/
export const makeDecorator = ({
name,
parameterName,
Expand Down
47 changes: 42 additions & 5 deletions code/lib/preview-api/src/modules/core-client/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,63 @@ const removedApi = (name: string) => () => {
};

interface CoreClient_RendererImplementation<TRenderer extends Renderer> {
/**
* A function that applies decorators to a story.
* @template TRenderer The type of renderer used by the Storybook client API.
* @type {ProjectAnnotations<TRenderer>['applyDecorators']}
*/
decorateStory?: ProjectAnnotations<TRenderer>['applyDecorators'];
/**
* A function that renders a story with args.
* @template TRenderer The type of renderer used by the Storybook client API.
* @type {ArgsStoryFn<TRenderer>}
*/
render?: ArgsStoryFn<TRenderer>;
}

interface CoreClient_ClientAPIFacade {
/* deprecated */
/**
* The old way of adding stories at runtime.
* @deprecated This method is deprecated and will be removed in a future version.
*/
storiesOf: (...args: any[]) => never;
/* deprecated */
/**
* The old way of retrieving the list of stories at runtime.
* @deprecated This method is deprecated and will be removed in a future version.
*/
raw: (...args: any[]) => never;
}

interface CoreClient_StartReturnValue<TRenderer extends Renderer> {
/* deprecated */
/**
* Forces a re-render of all stories in the Storybook preview.
* This function emits the `FORCE_RE_RENDER` event to the Storybook channel.
* @deprecated This method is deprecated and will be removed in a future version.
* @returns {void}
*/
forceReRender: () => void;
/* deprecated */
/**
* The old way of setting up storybook with runtime configuration.
* @deprecated This method is deprecated and will be removed in a future version.
* @returns {void}
*/
configure: any;
/* deprecated */
/**
* @deprecated This property is deprecated and will be removed in a future version.
* @type {ClientApi<TRenderer> | CoreClient_ClientAPIFacade}
*/
clientApi: ClientApi<TRenderer> | CoreClient_ClientAPIFacade;
}

/**
* Initializes the Storybook preview API.
* @template TRenderer The type of renderer used by the Storybook client API.
* @param {ProjectAnnotations<TRenderer>['renderToCanvas']} renderToCanvas A function that renders a story to a canvas.
* @param {CoreClient_RendererImplementation<TRenderer>} [options] Optional configuration options for the renderer implementation.
* @param {ProjectAnnotations<TRenderer>['applyDecorators']} [options.decorateStory] A function that applies decorators to a story.
* @param {ArgsStoryFn<TRenderer>} [options.render] A function that renders a story with arguments.
* @returns {CoreClient_StartReturnValue<TRenderer>} An object containing functions and objects related to the Storybook preview API.
*/
export function start<TRenderer extends Renderer>(
renderToCanvas: ProjectAnnotations<TRenderer>['renderToCanvas'],
{ decorateStory, render }: CoreClient_RendererImplementation<TRenderer> = {}
Expand Down
22 changes: 22 additions & 0 deletions code/lib/preview-api/src/modules/store/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ export {
useGlobals,
};

/**
* @param {string} sharedId - The ID of the shared state.
* @param {S} [defaultState] - The default state of the shared state.
* @deprecated This API might get dropped, if you are using this, please file an issue.
* @returns {[S, (s: S) => void]} - A tuple containing the current state and a function to update the state.
* @example
* const [state, setState] = useSharedState('my-addon', { count: 0 });
* console.log(state); // { count: 0 }
* setState({ count: 1 });
* console.log(state); // { count: 1 }
*/
export function useSharedState<S>(sharedId: string, defaultState?: S): [S, (s: S) => void] {
const channel = addons.getChannel();

Expand Down Expand Up @@ -69,6 +80,17 @@ export function useSharedState<S>(sharedId: string, defaultState?: S): [S, (s: S
];
}

/**
* @param {string} sharedId - The ID of the shared state.
* @param {S} [defaultState] - The default state of the shared state.
* @deprecated This API might get dropped, if you are using this, please file an issue.
* @returns {[S, (s: S) => void]} - A tuple containing the current state and a function to update the state.
* @example
* const [state, setState] = useSharedState('my-addon', { count: 0 });
* console.log(state); // { count: 0 }
* setState({ count: 1 });
* console.log(state); // { count: 1 }
*/
export function useAddonState<S>(addonId: string, defaultState?: S): [S, (s: S) => void] {
return useSharedState<S>(addonId, defaultState);
}

0 comments on commit cfe01a1

Please sign in to comment.