ui-core package, alongside the component files you will find the majority of *.stories.tsx files. There are also in other ui packages as well as the dispatch app.
If you are unfamiliar with writing stories, please review this doc in Storybook for an introduction.
Here we will review a few guidelines to keep stories standardized, no matter who writes them.
Coming soon: Link to Cursor prompt to help write stories
Defining argTypes in Storybook will specify the controls used for each arg (prop) that a story will take. Using the Meta config to define argTypes allows us to set all the controls a story will have available. In the individual story you can decide to list them all, include or exclude args to make the story simpler with a smaller subset of interactive controls.
Here's an example:
id, defaultValue, disabled and indicatorPosition args. The id control will be shown as required with an asterisk. The disabled arg will have a switch control for booleans.indicatorPosition since its a prop that takes IndicatorPositionEnum values, uses enumToArgType utilargType object that renders a select control with appropriate values and labels that correspond to the enum.
All args will be sorted in alphabetical order.
From here, subsequent stories will inherit the same controls list.
Start with a general or "Default" story of a basic example.
This default story should list all the props as args that the component accepts, so the engineer can get an idea of the component's API.
If you define all the argTypes in the Meta object, then you should get this list in the Default story.
Try to have a story for each variant or state that the component can be in. In these stories, args edits should impact the story - e.g. when the viewer uses a control to set or change a value, the story should change. In order to do this, the component rendered in the story should accept the args as props (args passed to the render function).
include and exclude are described in the Storybook docs on argTypes. Gotcha: If you use include or exclude, be sure to pass args (or _args if not using them) to the render function or it will be ignored.
Sometimes it makes sense to group a bunch of variations in one story and show them side by side, like when they are colors, icons or positions for example. If you have a story that shows all, the controls will probably not be used to change props for all the rendered components.
You can still list all the args to show the API, but use the util disableArgControls to disable their controls, since changing values shouldn't have any effect.
WithSnapshot helper in order to Chromatic snapshots for that story. Local publishes use pnpm chromatic from the repo root.
useFragment has no data of its own: a route's query supplies it (see GraphQL Fragments). A story for it needs that parent query's result in an Apollo cache, with data masking on, so the component sees only its own fragment's fields, as it does in the app.
withApollo from @prepared911/util-storybook-helpers/apollo does this. It builds a real client with createApolloClient, seeds the parent operation into its cache with cache.writeQuery, and wraps the story in ApolloProvider. There is no network and no mock server: the client's only link fails any request, so a story that asks for something it was not seeded with errors instead of hanging. SeededQuery reads the seeded operation (useQuery with cache-only) and hands you data, which is the same shape as the probe in the testing recipe (Mocking GraphQL for Tests).
data is typed as the operation's unmasked result, fragment fields included, so a mistyped or missing field fails tsc. The component still sees only its fragment: ask for a field it did not select and you get undefined, as in production.variables is required, and you pass the same values to withApollo and to SeededQuery.meta.decorators or on one story, never in .storybook/preview.ts. A global decorator makes every story depend on it, so Chromatic's TurboSnap (onlyChanged) would rebuild all of them on any change to it.withApollo works in any Storybook host. To import ./queries.graphql in a story, the host's viteFinal adds graphqlOperationsPlugin from @prepared911/tooling-config/vite/graphql (it resolves the import to the generated .graphql.ts, so run pnpm codegen first), and the host's package.json lists @prepared911/util-storybook-helpers as a dependency (packages/ui has none today).renderWithApollo from @prepared911/util-testing, which answers through MSW handlers rather than a seeded cache. Both run with data masking on, so a component sees only its own fragment's fields either way (see GraphQL Fragments).play method to the story.