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

Core: Add JSDoc comments to preview-api APIs #22975

Merged
merged 4 commits into from
Jun 8, 2023
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
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);
}