|
| 1 | +import { Meta, Source } from '@storybook/addon-docs/blocks'; |
| 2 | + |
| 3 | +<Meta title="Writing Stories" /> |
| 4 | + |
| 5 | +# Writing Stories |
| 6 | + |
| 7 | +If you're not familiar with the concept of stories, see the [Storybook docs](https://storybook.js.org/docs/9) and [Chromatic docs](https://www.chromatic.com/docs/) for an introduction. |
| 8 | + |
| 9 | +We recommend using Storybook's [Component Story Format](https://storybook.js.org/docs/9/api/csf/index) version 3 (CSFv3) to write stories. The [documentation](https://storybook.js.org/docs/9/writing-stories) covers this in detail, but in brief it means that your stories file should look something like this: |
| 10 | + |
| 11 | +<Source language="tsx" code={` |
| 12 | +import type { Meta, StoryObj } from '@storybook/react-webpack5'; |
| 13 | +import { Headline } from './Headline'; |
| 14 | +
|
| 15 | +const meta = { |
| 16 | + title: 'Components/Headline', |
| 17 | + component: Headline, |
| 18 | +} satisfies Meta<typeof Headline>; |
| 19 | +
|
| 20 | +export default meta; |
| 21 | +
|
| 22 | +type Story = StoryObj<typeof meta>; |
| 23 | +
|
| 24 | +export const MyStory = { |
| 25 | + args: { |
| 26 | + text: 'A headline', |
| 27 | + }, |
| 28 | +} satisfies Story; |
| 29 | +`} /> |
| 30 | + |
| 31 | +The `title` field is optional, as it can be inferred from the name of the component and its file path. You may also, sometimes, want to choose a different prefix than `Components/`. See [the docs](https://storybook.js.org/docs/9/writing-stories/naming-components-and-hierarchy) for more information. In addition, if you only have a single story you may want to ["hoist"](https://storybook.js.org/docs/9/writing-stories/naming-components-and-hierarchy#single-story-hoisting) it in the sidebar. |
| 32 | + |
| 33 | +## Toolbar and Modes |
| 34 | + |
| 35 | +Viewing a story in a particular state unrelated to its props or context, such as at a specific breakpoint or in dark mode, is achieved through the [toolbar and globals](https://storybook.js.org/docs/9/essentials/toolbars-and-globals). |
| 36 | + |
| 37 | +### Breakpoints |
| 38 | + |
| 39 | +We have configured Storybook to display stories in one of the eight Guardian breakpoints: |
| 40 | + |
| 41 | +- mobile: 320px |
| 42 | +- mobileMedium: 375px |
| 43 | +- mobileLandscape: 480px |
| 44 | +- phablet: 660px |
| 45 | +- tablet: 740px |
| 46 | +- desktop: 980px |
| 47 | +- leftCol: 1140px |
| 48 | +- wide: 1300px |
| 49 | + |
| 50 | +These can be applied using the dropdown in the Storybook toolbar, which is powered by the built-in [viewport feature](https://storybook.js.org/docs/9/essentials/viewport). |
| 51 | + |
| 52 | +### Colour Scheme |
| 53 | + |
| 54 | +Most of our components also have both light and dark mode versions, and we want to be able to view either of these in Storybook. To this end we have a custom toolbar item that allows you to display a story in one of four colour schemes: |
| 55 | + |
| 56 | +- Light |
| 57 | +- Dark |
| 58 | +- Horizontal Split |
| 59 | +- Vertical Split |
| 60 | + |
| 61 | +The "split" schemes will display two versions of the story at the same time; either vertically, light above dark, or horizontally, light on the left and dark on the right. The [docs](https://storybook.js.org/docs/9/essentials/toolbars-and-globals) contain details about how this works internally. |
| 62 | + |
| 63 | +These colour schemes will include default background and text colours. If you want to override these defaults this can be done with the `colourSchemeBackground` and `colourSchemeTextColour` parameters: |
| 64 | + |
| 65 | +<Source language="tsx" code={` |
| 66 | +export const MyStory = { |
| 67 | + parameters: { |
| 68 | + colourSchemeBackground: { |
| 69 | + light: 'blue', |
| 70 | + dark: '#222', |
| 71 | + }, |
| 72 | + colourSchemeTextColour: { |
| 73 | + light: palette('--custom-text-colour'), |
| 74 | + dark: palette('--custom-text-colour'), |
| 75 | + }, |
| 76 | + }, |
| 77 | +} satisfies Story; |
| 78 | +`} /> |
| 79 | + |
| 80 | +As seen in this example, any CSS colour value should work, including variables from the DCAR palette (see `palette.ts`). |
| 81 | + |
| 82 | +### Chromatic and Modes |
| 83 | + |
| 84 | +To apply a particular combination of breakpoint and colour scheme to a story in Chromatic, we use [modes](https://www.chromatic.com/docs/modes/). Some are already defined in `.storybook/modes.ts`, and if the one you want is missing you can add it there. We apply these modes at either the component level via a parameter in the `meta` object, or at the story level via a parameter in the story object. For example: |
| 85 | + |
| 86 | +<Source language="tsx" code={` |
| 87 | +import { allModes } from '../../../.storybook/modes'; |
| 88 | +
|
| 89 | +const meta = { |
| 90 | + title: 'Components/Headline', |
| 91 | + component: Headline, |
| 92 | + parameters: { |
| 93 | + chromatic: { |
| 94 | + modes: { |
| 95 | + 'light mobileMedium': allModes['light mobileMedium'], |
| 96 | + 'vertical leftCol': allModes['vertical leftCol'], |
| 97 | + }, |
| 98 | + } |
| 99 | + } |
| 100 | +} satisfies Meta<typeof Headline>; |
| 101 | +
|
| 102 | +export default meta; |
| 103 | +`} /> |
| 104 | + |
| 105 | +Note that the previous way of achieving some of this functionality was through a [viewports parameter](https://www.chromatic.com/docs/legacy-viewports/#viewports-legacy-storybook-api). That is now deprecated. |
| 106 | + |
| 107 | +## Format and Colour Palette |
| 108 | + |
| 109 | +Many components will use the DCAR palette (see `palette.ts`) to derive their colours, and the colours in this palette are determined by a "Format" value. If a component takes a `format` prop, and the story therefore provides a `format` arg, then the palette will use this: |
| 110 | + |
| 111 | +<Source language="tsx" code={` |
| 112 | +export const MyStory = { |
| 113 | + args: { |
| 114 | + text: 'A headline', |
| 115 | + format: { |
| 116 | + design: ArticleDesign.Standard, |
| 117 | + display: ArticleDisplay.Standard, |
| 118 | + theme: Pillar.News, |
| 119 | + }, |
| 120 | + }, |
| 121 | +} satisfies Story; |
| 122 | +`} /> |
| 123 | + |
| 124 | +If the component does not take this prop, you can instead pass a `formats` parameter: |
| 125 | + |
| 126 | +<Source language="tsx" code={` |
| 127 | +export const MyStory = { |
| 128 | + args: { |
| 129 | + text: 'A headline', |
| 130 | + }, |
| 131 | + parameters: { |
| 132 | + formats: [ |
| 133 | + { |
| 134 | + design: ArticleDesign.Standard, |
| 135 | + display: ArticleDisplay.Standard, |
| 136 | + theme: Pillar.News, |
| 137 | + }, |
| 138 | + ], |
| 139 | + }, |
| 140 | +} satisfies Story; |
| 141 | +`} /> |
| 142 | + |
| 143 | +This parameter takes an array because you can pass multiple Formats to render the story multiple times, with a different format, and therefore colour palette, each time. Note that if the component *also* takes a `format` prop then this prop will be overridden by the `formats` parameter. This has consequences beyond the colour palette, as components can derive many aspects of the way they appear from Format. |
| 144 | + |
| 145 | +If a Format is not passed in either the `args` or `parameters` of a story then we use a default to determine the colour palette instead (defined in `globalColourScheme.ts`). |
| 146 | + |
| 147 | +## Story Layout |
| 148 | + |
| 149 | +Components often represent small pieces of UI that are only intended to appear in a certain area of the page, such as in one of the Guardian layout columns (left, centre, right). Due to the way our pages are designed, the styles that determine their layout often live at a higher level than the component, and so rendering the component in a story without that environment may not be very useful. Storybook provides the [decorators](https://storybook.js.org/docs/9/writing-stories/decorators) feature to solve this problem, and we have some custom decorators to place stories within the [Guardian grid](?path=/docs/grid--docs): |
| 150 | + |
| 151 | +<dl> |
| 152 | + <dt>centreColumnDecorator</dt> |
| 153 | + <dd> |
| 154 | + Place the story in the centre column of a Guardian grid across all breakpoints. |
| 155 | + </dd> |
| 156 | + <dt>leftColumnDecorator</dt> |
| 157 | + <dd> |
| 158 | + Place the story in the left column of a Guardian grid, when available. On narrower breakpoints, where the left column isn't available, it will instead be placed in the centre column (this is a common layout pattern). |
| 159 | + </dd> |
| 160 | + <dt>rightColumnDecorator</dt> |
| 161 | + <dd> |
| 162 | + Place the story in the right column of a Guardian grid, when available. On narrower breakpoints, where the right column isn't available, it will instead be placed in the centre column (this is a common layout pattern). |
| 163 | + </dd> |
| 164 | + <dt>gridContainerDecorator</dt> |
| 165 | + <dd> |
| 166 | + Wrap the story in a Guardian grid container. This allows styles either: |
| 167 | + <ol style={{ listStyle: 'decimal' }}> |
| 168 | + <li>Within the component</li> |
| 169 | + <li>In the story</li> |
| 170 | + <li>In other decorators</li> |
| 171 | + </ol> |
| 172 | + to position elements on the grid. |
| 173 | + </dd> |
| 174 | +</dl> |
| 175 | + |
| 176 | +These decorators can be applied at either the component level via the `meta` object, or at the story level via the story object. For example: |
| 177 | + |
| 178 | +<Source language="tsx" code={` |
| 179 | +import { centreColumnDecorator } from '../../.storybook/decorators/gridDecorators'; |
| 180 | +
|
| 181 | +export const MyStory = { |
| 182 | + args: { |
| 183 | + text: 'A headline', |
| 184 | + }, |
| 185 | + decorators: [centreColumnDecorator], |
| 186 | +} satisfies Story; |
| 187 | +`} /> |
| 188 | + |
| 189 | +## Other Decorators |
| 190 | + |
| 191 | +Aside from the grid decorators mentioned above, we also have the `splitThemeDecorator`, the `themeDecorator`, and the `configContextDecorator`. These are mostly intended for use in Storybook setup, in `preview.ts`, and are _not_ meant to be used directly in stories files. |
0 commit comments