@guardian/stand 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +116 -1759
  2. package/dist/components/avatar/Avatar.js +1 -1
  3. package/dist/components/button/Button.cjs +1 -4
  4. package/dist/components/button/Button.js +1 -4
  5. package/dist/components/button/styles.cjs +1 -3
  6. package/dist/components/byline/Byline.cjs +4 -18
  7. package/dist/components/byline/Byline.js +1 -1
  8. package/dist/components/byline/Preview.cjs +1 -8
  9. package/dist/components/byline/plugins.cjs +3 -13
  10. package/dist/components/favicon/Favicon.cjs +47 -0
  11. package/dist/components/favicon/Favicon.js +30 -0
  12. package/dist/components/favicon/styles.cjs +34 -0
  13. package/dist/components/favicon/styles.js +29 -0
  14. package/dist/components/icon/Icon.cjs +1 -4
  15. package/dist/components/icon/Icon.js +1 -4
  16. package/dist/components/icon-link-button/IconLinkButton.cjs +1 -6
  17. package/dist/components/tag-picker/TagAutocomplete.js +1 -1
  18. package/dist/components/tag-picker/TagTable.js +1 -1
  19. package/dist/components/user-menu/UserMenu.cjs +1 -8
  20. package/dist/components/user-menu/UserMenu.js +1 -1
  21. package/dist/favicon.cjs +9 -0
  22. package/dist/favicon.js +2 -0
  23. package/dist/fonts/OpenSans.css +64 -68
  24. package/dist/index.cjs +6 -4
  25. package/dist/index.js +1 -0
  26. package/dist/styleD/build/css/component/button.css +15 -16
  27. package/dist/styleD/build/css/component/favicon.css +17 -0
  28. package/dist/styleD/build/css/semantic/typography.css +5 -10
  29. package/dist/styleD/build/typescript/component/favicon.cjs +20 -0
  30. package/dist/styleD/build/typescript/component/favicon.js +18 -0
  31. package/dist/types/components/favicon/Favicon.d.ts +2 -0
  32. package/dist/types/components/favicon/styles.d.ts +8 -0
  33. package/dist/types/components/favicon/types.d.ts +38 -0
  34. package/dist/types/favicon.d.ts +19 -0
  35. package/dist/types/index.d.ts +2 -0
  36. package/dist/types/styleD/build/typescript/component/favicon.d.ts +20 -0
  37. package/package.json +27 -17
package/README.md CHANGED
@@ -1,1826 +1,183 @@
1
- # Stand - a tools component library
1
+ # `@guardian/stand`
2
2
 
3
3
  _Find what you need on the (news)stand!_
4
4
 
5
- Stand is component library for Guardian editorial tools. It is co-located within flexible-content as Composer is expected to be the main consumer of the UI components within Stand. But any editorial tool should be able to make use of the components as an npm package - `@guardian/stand` - and developers should feel comfortable contributing.
5
+ A tools component library and design system
6
6
 
7
- ## Installation
7
+ Any tool should be able to make use of the components as an npm package, [`@guardian/stand`](https://www.npmjs.com/package/@guardian/stand), and developers should feel comfortable [contributing](https://guardian.github.io/stand/?path=/docs/contributing--docs).
8
8
 
9
- ```bash
10
- $ pnpm add @guardian/stand # or yarn or npm
11
- ```
12
-
13
- You'll also need to have TypeScript, React, and Emotion installed in your project.
14
-
15
- The compatible versions are listed in the `peerDependencies` section of `package.json`.
16
-
17
- Some components have additional dependencies that you will need to install too. See the [Components](#components) section for more details for which components have which peer dependencies.
18
-
19
- ## Foundations
20
-
21
- The Editorial Design System foundations are available via Stand. These are split into two categories: Semantic and Base / Primitives.
22
-
23
- In most cases consumers should use the Semantic tokens, which are more meaningful abstractions of the Base / Primitives tokens, i.e applied to specific use cases.
24
-
25
- The base / primitives tokens are available for low-level use cases, or very specific cases, but these should be avoided where possible in favour of the semantic tokens.
26
-
27
- Stand provides the foundations via CSS variables, as well as JS/TS exports for use in code, which could also be used in CSS-in-JS solutions.
28
-
29
- Base / Primitive tokens should not be overridden if they are used directly, as this could have unintended consequences. Instead override the Semantic tokens which are designed to be overridden.
30
-
31
- ### Fonts
32
-
33
- Most applications should only need to load the `Open Sans` and `Guardian Headline` fonts, as these are the primary fonts used across the Guardian's Editorial Tool design system.
34
-
35
- You only need to load Guardian Text Egyptian if you're planning to use it in your project, in most cases you only need this when working on Guardian editorial content on an editorial tool, i.e. when using `article-body-*` semantic tokens.
36
-
37
- ##### Open Sans
38
-
39
- ##### Guardian Hosted (Recommended)
40
-
41
- We are self hosting this font under a Guardian specific domain, so you can load the Open Sans variable font CSS file from our CDN:
42
-
43
- Via CSS `@import`:
44
-
45
- ```css
46
- @import url('https://assets.guim.co.uk/fonts/open-sans/OpenSans.css');
47
- ```
48
-
49
- Via HTML `<link>`:
50
-
51
- ```html
52
- <link
53
- rel="stylesheet"
54
- href="https://assets.guim.co.uk/fonts/open-sans/OpenSans.css"
55
- />
56
- ```
57
-
58
- We also publish the same `OpenSans.css` file in the `@guardian/stand` package if you prefer to import it from there and you're using a bundler that supports CSS imports:
59
-
60
- ```ts
61
- import '@guardian/stand/fonts/OpenSans.css';
62
- ```
63
-
64
- All options use the font files hosted on the Guardian CDN at `https://assets.guim.co.uk/fonts/open-sans/woff2/`.
65
-
66
- You can also create your own CSS `@font-face` declarations, using the same format as in the `OpenSans.css` file, and/or self host the font files yourself if needed.
67
-
68
- ##### via Google Fonts
69
-
70
- The Open Sans variable font can also be loaded via Google Fonts, but we recommend using the Guardian hosted version to avoid loading from a third party domain:
71
-
72
- 1. Visit [Google Fonts - Open Sans](https://fonts.google.com/specimen/Open+Sans)
73
- 2. Click "Get Font" -> "Get embed code"
74
- 3. Click "Change styles" dropdown
75
- 4. Use "Full axis" for all options (Italic, Weight, Width)
76
- 5. Copy the relevant `<link>` tag or `@import` code snippet into your project
77
- - You don't need to include the CSS class, as the design system will handle applying the correct font-family via CSS variables or JS/TS tokens.
78
-
79
- #### Guardian Fonts
80
-
81
- Make sure to visit [guardian/fonts](https://github.com/guardian/fonts) repo for the latest information on how to self-host these fonts.
82
-
83
- In general, we always want to use the `full-not-hinted` versions of the fonts where possible.
84
-
85
- ##### Guardian Headline
86
-
87
- We only use the bold weight (700) of Guardian Headline in the design system.
88
-
89
- ```css
90
- @font-face {
91
- font-family: 'GH Guardian Headline';
92
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-headline/full-not-hinted/GHGuardianHeadline-Bold.woff2')
93
- format('woff2');
94
- font-weight: 700;
95
- font-style: normal;
96
- font-display: swap;
97
- }
98
- ```
99
-
100
- ##### Guardian Text Egyptian
101
-
102
- We want the regular/normal weight (400), the bold weight (700), and the italic version of each weight for Guardian Text Egyptian in the design system.
103
-
104
- ```css
105
- @font-face {
106
- font-family: 'GuardianTextEgyptian';
107
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-Regular.woff2')
108
- format('woff2');
109
- font-weight: 400;
110
- font-style: normal;
111
- font-display: swap;
112
- }
113
- @font-face {
114
- font-family: 'GuardianTextEgyptian';
115
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-RegularItalic.woff2')
116
- format('woff2');
117
- font-weight: 400;
118
- font-style: italic;
119
- font-display: swap;
120
- }
121
- @font-face {
122
- font-family: 'GuardianTextEgyptian';
123
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-Bold.woff2')
124
- format('woff2');
125
- font-weight: 700;
126
- font-style: normal;
127
- font-display: swap;
128
- }
129
- @font-face {
130
- font-family: 'GuardianTextEgyptian';
131
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-BoldItalic.woff2')
132
- format('woff2');
133
- font-weight: 700;
134
- font-style: italic;
135
- font-display: swap;
136
- }
137
- ```
138
-
139
- ### Semantic
140
-
141
- #### Colors
142
-
143
- _Status: WIP_
144
-
145
- ```ts
146
- import { css } from '@emotion/react';
147
- import { semanticColors } from '@guardian/stand'; // JS/TS usage
148
- import '@guardian/stand/semantic/colors.css'; // CSS usage
149
-
150
- const stringStyle = css`
151
- color: ${semanticColors.text.default}; /* JS/TS usage */
152
- background-color: var(
153
- --semantic-colors-bg-default-on-light
154
- ); /* CSS usage */
155
- `;
156
-
157
- const objectStyle = {
158
- color: semanticColors.text.default /* JS/TS usage */,
159
- backgroundColor:
160
- 'var(--semantic-colors-bg-default-on-light)' /* CSS usage */,
161
- };
162
- ```
163
-
164
- For a list of available semantic color styles see the [Storybook Semantic Colors](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-semantic-color-palette--docs) section.
165
-
166
- For a full list of CSS Semantic Color tokens see [`semantic/colors.css`](./src/styleD/build/css/semantic/colors.css).
167
-
168
- #### Typography
169
-
170
- _Status: WIP_
171
-
172
- ```ts
173
- import { css } from '@emotion/react';
174
- import { semanticColors, semanticTypography } from '@guardian/stand'; // JS/TS usage
175
- import {
176
- convertTypographyToEmotionObjectStyle, // helper function to convert from typography token object to emotion CSS object style
177
- convertTypographyToEmotionStringStyle, // helper function to convert from typography token object to emotion CSS string style
178
- } from '@guardian/stand/utils'; // Utils for working with design tokens
179
- import '@guardian/stand/semantic/typography.css'; // CSS usage
180
-
181
- /* JS/TS usage */
182
- const stringStyleJS = css`
183
- // other styles e.g.
184
- color: ${semanticColors.text.default};
185
-
186
- /* (recommended) emotion string style usage helper function*/
187
- ${convertTypographyToEmotionStringStyle(
188
- semanticTypography['body-compact-md'],
189
- )}
190
-
191
- /* or direct usage without helper function */
192
- font: ${semanticTypography['body-compact-md'].font};
193
- letter-spacing: ${semanticTypography['body-compact-md'].letterSpacing};
194
- font-variation-settings: 'wdth'
195
- ${semanticTypography['body-compact-md'].fontWidth};
196
- `;
197
- const objectStyleJS = {
198
- // other styles e.g.
199
- color: semanticColors.text.default,
200
-
201
- // (recommended) emotion object style usage helper function
202
- ...convertTypographyToEmotionObjectStyle(
203
- semanticTypography['body-compact-sm'],
204
- ),
205
-
206
- // or direct usage without helper function
207
- font: semanticTypography['body-compact-sm'].font,
208
- letterSpacing: semanticTypography['body-compact-sm'].letterSpacing,
209
- fontVariationSettings: `'wdth' ${semanticTypography['body-compact-sm'].fontWidth}`,
210
- };
211
-
212
- /* CSS usage */
213
- const stringStyleCSS = css`
214
- /* CSS usage */
215
- font: var(--semantic-typography-body-compact-sm-font);
216
- letter-spacing: var(--semantic-typography-body-compact-sm-letter-spacing);
217
- font-variation-settings: 'wdth'
218
- var(--semantic-typography-body-compact-sm-font-width);
219
- `;
220
- const objectStyleCSS = {
221
- font: 'var(--semantic-typography-body-compact-sm-font)',
222
- letterSpacing: 'var(--semantic-typography-body-compact-sm-letter-spacing)',
223
- fontVariationSettings: `'wdth' var(--semantic-typography-body-compact-sm-font-width)`,
224
- };
225
- ```
226
-
227
- For a list of available typography styles see the [Storybook Semantic Typography](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-semantic-typography--docs) section.
228
-
229
- For a full list of CSS Semantic Typography tokens see [`semantic/typography.css`](./src/styleD/build/css/semantic/typography.css).
230
-
231
- #### Sizing
232
-
233
- _Status: WIP_
234
-
235
- ```ts
236
- import { css } from '@emotion/react';
237
- import { semanticSizing } from '@guardian/stand'; // JS/TS usage
238
- import '@guardian/stand/semantic/sizing.css'; // CSS usage
239
-
240
- const stringStyle = css`
241
- height: ${semanticSizing.height.md}; /* JS/TS usage */
242
- border-width: var(--semantic-sizing-border-md); /* CSS usage */
243
- `;
244
-
245
- const objectStyle = {
246
- height: semanticSizing.height.md /* JS/TS usage */,
247
- borderWidth: 'var(--semantic-sizing-border-md)' /* CSS usage */,
248
- };
249
-
250
- // Icon sizing example
251
- const iconStyle = css`
252
- width: ${semanticSizing.icon.lg};
253
- height: ${semanticSizing.icon.lg};
254
- `;
255
- ```
256
-
257
- For a list of available semantic sizing styles see the [Storybook Semantic Sizing](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-semantic-sizing--docs) section.
258
-
259
- For a full list of CSS Semantic Sizing tokens see [`semantic/sizing.css`](./src/styleD/build/css/semantic/sizing.css).
260
-
261
- ### Base / Primitives
262
-
263
- #### Colors
264
-
265
- _Status: WIP_
266
-
267
- ```ts
268
- import { css } from '@emotion/react';
269
- import { baseColors } from '@guardian/stand'; // JS/TS usage
270
- import '@guardian/stand/base/colors.css'; // CSS usage
271
-
272
- const stringStyle = css`
273
- color: ${baseColors.neutral['900']}; /* JS/TS usage */
274
- background-color: var(--base-colors-blue-500); /* CSS usage */
275
- `;
276
-
277
- const objectStyle = {
278
- color: baseColors.neutral['900'] /* JS/TS usage */,
279
- backgroundColor: 'var(--base-colors-blue-500)' /* CSS usage */,
280
- };
281
- ```
282
-
283
- For a list of the available base/primitives color styles see the [Storybook Base Colors](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-color-palette--docs) section.
284
-
285
- For a full list of CSS Base/Primitives Color tokens see [`base/colors.css`](./src/styleD/build/css/base/colors.css).
286
-
287
- #### Typography
288
-
289
- _Status: WIP_
290
-
291
- ```ts
292
- import { css } from '@emotion/react';
293
- import { baseTypography } from '@guardian/stand'; // JS/TS usage
294
- import '@guardian/stand/base/typography.css'; // CSS usage
295
-
296
- /* JS/TS usage */
297
- const stringStyleJS = css`
298
- font-family: ${baseTypography.family['Open Sans']};
299
- font-size: ${baseTypography.size['14-rem']};
300
- font-weight: ${baseTypography.weight['Open Sans'].normal};
301
- font-variation-settings: 'wdth' ${baseTypography.width['Open Sans']};
302
- style: ${baseTypography.style.normal};
303
- line-height: ${baseTypography.lineHeight.normal};
304
- letter-spacing: ${baseTypography.letterSpacing['default-rem']};
305
- `;
306
- const objectStyleJS = {
307
- fontFamily: baseTypography.family['Open Sans'],
308
- fontSize: baseTypography.size['14-rem'],
309
- fontWeight: baseTypography.weight['Open Sans'].normal,
310
- fontVariationSettings: `'wdth' ${baseTypography.width['Open Sans']}`,
311
- fontStyle: baseTypography.style.normal,
312
- lineHeight: baseTypography.lineHeight.normal,
313
- letterSpacing: baseTypography.letterSpacing['default-rem'],
314
- };
315
-
316
- /* CSS usage */
317
- const stringStyleCSS = css`
318
- font-family: var(--base-typography-family-open-sans);
319
- font-size: var(--base-typography-size-14-rem);
320
- font-weight: var(--base-typography-weight-open-sans-normal);
321
- font-variation-settings: 'wdth' var(--base-typography-width-open-sans);
322
- font-style: var(--base-typography-style-normal);
323
- line-height: var(--base-typography-line-height-normal);
324
- letter-spacing: var(--base-typography-letter-spacing-default-rem);
325
- `;
326
- const objectStyleCSS = {
327
- fontFamily: 'var(--base-typography-family-open-sans)',
328
- fontSize: 'var(--base-typography-size-14-rem)',
329
- fontWeight: 'var(--base-typography-weight-open-sans-normal)',
330
- fontVariationSettings: `'wdth' var(--base-typography-width-open-sans)`,
331
- fontStyle: 'var(--base-typography-style-normal)',
332
- lineHeight: 'var(--base-typography-line-height-normal)',
333
- letterSpacing: 'var(--base-typography-letter-spacing-default-rem)',
334
- };
335
- ```
336
-
337
- For a list of the available base/primitives typography tokens see the [Storybook Base Typography](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-typography--docs) section.
338
-
339
- For a full list of CSS Base/Primitives Typography tokens see [`base/typography.css`](./src/styleD/build/css/base/typography.css).
340
-
341
- #### Spacing
342
-
343
- _Status: WIP_
344
-
345
- The `rem` tokens should be used in most cases to ensure scalability across different root font sizes.
346
- `px` tokens are available for cases where a fixed pixel value is required.
347
-
348
- ```ts
349
- import { css } from '@emotion/react';
350
- import { baseSpacing } from '@guardian/stand'; // JS/TS usage
351
- import '@guardian/stand/base/spacing.css'; // CSS usage
352
-
353
- /* JS/TS usage */
354
- const stringStyleJS = css`
355
- padding: ${baseSpacing['16-rem']};
356
- margin-bottom: ${baseSpacing['24-rem']};
357
- gap: ${baseSpacing['8-rem']};
358
- `;
359
- const objectStyleJS = {
360
- padding: baseSpacing['16-rem'],
361
- marginBottom: baseSpacing['24-rem'],
362
- gap: baseSpacing['8-rem'],
363
- };
364
-
365
- /* CSS usage */
366
- const stringStyleCSS = css`
367
- padding: var(--base-spacing-16-rem);
368
- margin-bottom: var(--base-spacing-24-rem);
369
- gap: var(--base-spacing-8-rem);
370
- `;
371
- const objectStyleCSS = {
372
- padding: 'var(--base-spacing-16-rem)',
373
- marginBottom: 'var(--base-spacing-24-rem)',
374
- gap: 'var(--base-spacing-8-rem)',
375
- };
376
- ```
377
-
378
- For a list of the available base/primitives spacing tokens see the [Storybook Base Spacing](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-spacing--docs) section.
379
-
380
- For a full list of CSS Base/Primitives Spacing tokens see [`base/spacing.css`](./src/styleD/build/css/base/spacing.css).
381
-
382
- #### Sizing
9
+ This library is split into two main sections:
383
10
 
384
- _Status: WIP_
11
+ - [Tools Design System](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs)
12
+ - The core design system and components used across the Guardian's internal tools.
13
+ - e.g. Colour, Typography, Buttons, Forms, etc.
14
+ - [Editorial Components](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs)
15
+ - A collection of highly specific components shared across a smaller number of tools.
16
+ - e.g. Byline editor, Tag picker, etc.
385
17
 
386
- The `rem` tokens should be used in most cases to ensure scalability across different root font sizes.
387
- `px` tokens are available for cases where a fixed pixel value is required.
18
+ Currently maintained by the P&E DevX E2E team, but open to contributions from anyone in the company. Do reach out for help or to get involved!
388
19
 
389
- ```ts
390
- import { css } from '@emotion/react';
391
- import { baseSizing } from '@guardian/stand'; // JS/TS usage
392
- import '@guardian/stand/base/sizing.css'; // CSS usage
20
+ Stand is built with [React](https://reactjs.org/), [Emotion](https://emotion.sh/docs/introduction), and [TypeScript](https://www.typescriptlang.org/), but the design system is designed to be as framework-agnostic as possible in that styles can be used without these dependencies if needed.
393
21
 
394
- /* JS/TS usage */
395
- const stringStyleJS = css`
396
- width: ${baseSizing['size-48-rem']};
397
- height: ${baseSizing['size-24-rem']};
398
- min-width: ${baseSizing['size-24-rem']};
399
- `;
400
- const objectStyleJS = {
401
- width: baseSizing['size-48-rem'],
402
- height: baseSizing['size-24-rem'],
403
- minWidth: baseSizing['size-24-rem'],
404
- };
22
+ ## Contents
405
23
 
406
- /* CSS usage */
407
- const stringStyleCSS = css`
408
- width: var(--base-sizing-size-48-rem);
409
- height: var(--base-sizing-size-24-rem);
410
- min-width: var(--base-sizing-size-24-rem);
411
- `;
412
- const objectStyleCSS = {
413
- width: 'var(--base-sizing-size-48-rem)',
414
- height: 'var(--base-sizing-size-24-rem)',
415
- minWidth: 'var(--base-sizing-size-24-rem)',
416
- };
417
- ```
418
-
419
- For a list of the available base/primitives sizing tokens see the [Storybook Base Sizing](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-sizing--docs) section.
420
-
421
- For a full list of CSS Base/Primitives Sizing tokens see [`base/sizing.css`](./src/styleD/build/css/base/sizing.css).
422
-
423
- #### Radius
424
-
425
- _Status: WIP_
426
-
427
- The `rem` tokens should be used in most cases to ensure scalability across different root font sizes.
428
- `px` tokens are available for cases where a fixed pixel value is required.
429
-
430
- ```ts
431
- import { css } from '@emotion/react';
432
- import { baseRadius } from '@guardian/stand'; // JS/TS usage
433
- import '@guardian/stand/base/radius.css'; // CSS usage
434
-
435
- /* JS/TS usage */
436
- const stringStyleJS = css`
437
- border-radius: ${baseRadius['corner-4-rem']};
438
- `;
439
- const objectStyleJS = {
440
- borderRadius: baseRadius['corner-4-rem'],
441
- };
442
-
443
- /* CSS usage */
444
- const stringStyleCSS = css`
445
- border-radius: var(--base-radius-corner-4-rem);
446
- `;
447
- const objectStyleCSS = {
448
- borderRadius: 'var(--base-radius-corner-4-rem)',
449
- };
450
- ```
451
-
452
- For a list of the available base/primitives radius tokens see the [Storybook Base Radius](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-radius--docs) section.
453
-
454
- For a full list of CSS Base/Primitives Radius tokens see [`base/radius.css`](./src/styleD/build/css/base/radius.css).
455
-
456
- ## Components - Base
457
-
458
- General purpose components for use across a variety of editorial tools based on the design system.
459
-
460
- ### `Avatar`
461
-
462
- The Avatar component displays a user's profile image or initials in a circular container. It supports multiple sizes, colors, and automatic fallback handling.
463
-
464
- **When to use**
465
-
466
- - Display user profiles in lists, comments, or headers
467
- - Show user identity in messaging interfaces
468
- - Represent users in collaborative features
469
-
470
- **Peer dependencies:**
471
-
472
- - `@emotion/react`
473
- - `react`
474
- - `react-dom`
475
- - `typescript`
476
-
477
- See the `peerDependencies` section of `package.json` for compatible versions.
478
-
479
- See [avatar custom component build](#avatar-custom-component-build) for usage without React/Emotion.
480
-
481
- #### Example usage
482
-
483
- See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/guardian-stand-avatar-component-mqvzh5)
484
-
485
- ```tsx
486
- import { Avatar } from '@guardian/stand/avatar';
487
-
488
- /* types, if required */
489
- import type { AvatarProps, AvatarTheme } from '@guardian/stand/avatar';
490
-
491
- /* Initials only */
492
- <Avatar initials="AB" size="md" />
493
-
494
- /* Image with alt text */
495
- <Avatar
496
- src="https://example.com/avatar.jpg"
497
- alt="User Name"
498
- size="md"
499
- />
500
-
501
- /* Image with fallback initials and a specific color */
502
- <Avatar
503
- src="https://example.com/avatar.jpg"
504
- alt="User Name"
505
- initials="AB"
506
- color="blue"
507
- size="md"
508
- />
509
- ```
510
-
511
- #### Props
512
-
513
- | Name | Type | Required | Default | Description |
514
- | -------------- | ------------------ | ----------- | ------------------------------- | ---------------------------------------------- |
515
- | `size` | `'sm'` \| `'md'` | No | `'md'` | Size of the avatar. |
516
- | `color` | Various | No | Deterministic based on initials | Color of the avatar. |
517
- | `initials` | `string` | Conditional | N/A | Initials to display when no image is provided. |
518
- | `src` | `string` | Conditional | N/A | URL of the avatar image. |
519
- | `alt` | `string` | Conditional | N/A | Alt text for the image for accessibility. |
520
- | `theme` | `AvatarTheme` | No | N/A | Custom theme overrides for the avatar. |
521
- | `cssOverrides` | `SerializedStyles` | No | N/A | Custom CSS styles for the avatar. |
522
- | `className` | `string` | No | N/A | Additional class name(s) for the avatar. |
523
-
524
- When using `src`, the `alt` prop is required for accessibility.
525
- When not using `src`, the `initials` prop is required.
526
-
527
- **`size`**
528
-
529
- The avatar supports two sizes:
530
-
531
- - `sm` (small): 32px
532
- - `md` (medium): 40px
533
-
534
- **`color`**
535
-
536
- When no `color` is set, the avatar picks a deterministic color based on the initials (with the exception of `outlined`). You can also specify a color explicitly.
537
-
538
- Available colors: `outlined`, `blue`, `green`, `red`, `cyan`, `teal`, `cool-purple`, `warm-purple`, `magenta`, `orange`, `yellow`
539
-
540
- #### Customisation
541
-
542
- We recommend using the Avatar component as provided, but it can be customised using the `theme` or `cssOverrides` props as required.
543
-
544
- **Custom theme**
545
-
546
- The `theme` prop allows you to override specific design tokens for the Avatar component:
547
-
548
- ```tsx
549
- import type { AvatarTheme } from '@guardian/stand/avatar';
550
- import { Avatar } from '@guardian/stand/avatar';
551
- import { baseColors, baseSizing } from '@guardian/stand';
552
-
553
- const customTheme: AvatarTheme = {
554
- shared: {
555
- color: {
556
- blue: {
557
- background: baseColors.blue[100],
558
- text: baseColors.neutral[900],
559
- },
560
- },
561
- },
562
- md: {
563
- size: baseSizing['size-48-rem'],
564
- },
565
- };
566
-
567
- const Component = () => (
568
- <Avatar color="blue" initials="CT" size="md" theme={customTheme} />
569
- );
570
- ```
571
-
572
- **CSS overrides**
573
-
574
- The `cssOverrides` prop allows you to pass custom CSS to the Avatar component, if the theme prop is not sufficient:
575
-
576
- ```tsx
577
- import { Avatar } from '@guardian/stand/avatar';
578
- import { baseColors, baseSizing, baseSpacing } from '@guardian/stand';
579
- import { css } from '@emotion/react';
24
+ - [Documentation](#documentation)
25
+ - [Install](#install)
26
+ - [Usage](#usage)
27
+ - [Contributing](#contributing)
28
+ - [Tasks](#tasks)
29
+ - [Design Tokens and Style Dictionary](#design-tokens-and-style-dictionary)
30
+ - [Compatibility](#compatibility)
31
+ - [Documentation Site Map](#documentation-site-map)
580
32
 
581
- const customStyles = css`
582
- border: ${baseSizing['size-2-rem']} solid ${baseColors.red[500]};
583
- margin: ${baseSpacing['8-rem']};
584
- `;
585
-
586
- const Component = () => (
587
- <Avatar initials="CO" size="md" color="red" cssOverrides={customStyles} />
588
- );
589
- ```
590
-
591
- #### Avatar Custom Component Build
592
-
593
- If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom Avatar component using the styles defined in the `AvatarTheme` type.
594
-
595
- You will however be responsible for any additional functionality on top of the styles, for example image loading, image fallback, accessibility etc.
596
-
597
- See "Custom Component" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/guardian-stand-avatar-component-mqvzh5)
598
-
599
- **`css`**
600
-
601
- You can import the Avatar styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
602
-
603
- ```css
604
- /* import the font and avatar styles */
605
- @import '@guardian/stand/fonts/OpenSans.css';
606
- @import '@guardian/stand/component/avatar.css';
607
-
608
- /*
609
- or for scenarios where you have to use relative paths/node_modules directly:
610
-
611
- @import 'node_modules/@guardian/stand/dist/fonts/OpenSans.css';
612
- @import 'node_modules/@guardian/stand/dist/styleD/build/css/component/avatar.css';
613
- */
614
-
615
- /* example setup of avatar style using md size and blue color */
616
- .stand-avatar {
617
- display: var(--component-avatar-shared-display);
618
- align-items: var(--component-avatar-shared-align-items);
619
- justify-content: var(--component-avatar-shared-justify-content);
620
- overflow: var(--component-avatar-shared-overflow);
621
- flex-shrink: var(--component-avatar-shared-flex-shrink);
622
- border-radius: var(--component-avatar-shared-border-radius);
623
- background-color: var(--component-avatar-shared-color-blue-background);
624
- width: var(--component-avatar-md-size);
625
- height: var(--component-avatar-md-size);
626
- border: var(--component-avatar-shared-color-blue-border);
627
- color: var(--component-avatar-shared-color-blue-text);
628
- font: var(--component-avatar-md-typography-font);
629
- letter-spacing: var(--component-avatar-md-typography-letter-spacing);
630
- font-variation-settings: 'wdth'
631
- var(--component-avatar-md-typography-font-width);
632
- }
633
-
634
- /* example setup for avatar image */
635
- .stand-avatar > img {
636
- width: 100%;
637
- height: 100%;
638
- object-fit: cover;
639
- }
640
- ```
641
-
642
- ```html
643
- <!-- example usage of avatar style in html with avatar -->
644
- <div class="stand-avatar">AB</div>
645
-
646
- <!-- example usage of avatar style in html with avatar image -->
647
- <div class="stand-avatar">
648
- <img src="https://example.com/avatar.jpg" alt="User Name" />
649
- </div>
650
- ```
651
-
652
- **TypeScript/JavaScript**
653
-
654
- Use the `componentAvatar` variable and the `ComponentAvatar` type to define your custom styles in TypeScript/JavaScript:
655
-
656
- ```ts
657
- import type { ComponentAvatar } from '@guardian/stand'; // if types required
658
- import { componentAvatar } from '@guardian/stand';
659
-
660
- const style = `
661
- display: ${componentAvatar.shared.display};
662
- align-items: ${componentAvatar.shared['align-items']};
663
- justify-content: ${componentAvatar.shared['justify-content']};
664
- overflow: ${componentAvatar.shared.overflow};
665
- flex-shrink: ${componentAvatar.shared['flex-shrink']};
666
- border-radius: ${componentAvatar.shared['border-radius']};
667
- background-color: ${componentAvatar.shared.color.blue.background};
668
- width: ${componentAvatar.md.size};
669
- height: ${componentAvatar.md.size};
670
- border: ${componentAvatar.shared.color.blue.border};
671
- color: ${componentAvatar.shared.color.blue.text};
672
- font: ${componentAvatar.md.typography.font};
673
- letter-spacing: ${componentAvatar.md.typography.letterSpacing};
674
- font-variation-settings: 'wdth' ${componentAvatar.md.typography.fontWidth};
675
- `;
676
-
677
- const imgStyle = `
678
- width: 100%;
679
- height: 100%;
680
- object-fit: cover;
681
- `;
682
-
683
- // e.g. adding to DOM using vanilla JS styles
684
- document.getElementById('app')!.innerHTML = `
685
- <h2>
686
- Using <code>typescript</code>/<code>javascript</code>
687
- </h2>
688
- <div style="${style}">AB</div>
689
- <div style="${style}">
690
- <img
691
- style="${imgStyle}"
692
- src="https://example.com/avatar.jpg"
693
- alt="User Name"
694
- />
695
- </div>
696
- `;
697
- ```
698
-
699
- ### `Button`
700
-
701
- A React Aria powered button that follows Stand design and accessibility defaults. It supports multiple visual variants, four sizes, disabled state handling, and render props for pending interactions.
702
-
703
- **When to use**
704
-
705
- - Primary and secondary actions that require clear affordance
706
- - Form submissions or confirm/cancel flows
707
- - Re-usable button styles that align with Stand tokens
708
-
709
- **Peer dependencies:**
710
-
711
- - `@emotion/react`
712
- - `react`
713
- - `react-dom`
714
- - `react-aria-components`
715
- - `typescript`
716
-
717
- See the `peerDependencies` section of `package.json` for compatible versions.
718
-
719
- See [button custom component build](#button-custom-component-build) for usage without React/Emotion.
720
-
721
- #### Example usage
722
-
723
- See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
724
-
725
- ```tsx
726
- import { Button } from '@guardian/stand/button';
727
- import type { ButtonProps, ButtonTheme } from '@guardian/stand/button';
728
-
729
- const Example = () => (
730
- <>
731
- <Button
732
- variant="emphasised-primary"
733
- size="md"
734
- onPress={() => {
735
- console.log('Primary action');
736
- }}
737
- >
738
- Publish
739
- </Button>
740
-
741
- <Button variant="neutral-secondary" size="sm" isDisabled>
742
- Disabled
743
- </Button>
744
- </>
745
- );
746
- ```
747
-
748
- #### Props
749
-
750
- | Name | Type | Required | Default | Description |
751
- | -------------- | -------------------------------------------------------------------------------------------------- | -------- | ---------------------- | ------------------------------------------------------------------------------------------------- |
752
- | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the button dimensions and typography. |
753
- | `variant` | `'emphasised-primary'` \| `'emphasised-secondary'` \| `'neutral-primary'` \| `'neutral-secondary'` | No | `'emphasised-primary'` | Chooses the colour scheme and interaction states. |
754
- | `children` | `ReactNode` \| `RenderProps` | Yes | N/A | Content inside the button. Render props receive `{ isPending }` from React Aria. |
755
- | `isDisabled` | `boolean` | No | `false` | Disables interaction and applies disabled styling. |
756
- | `theme` | `ButtonTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
757
- | `cssOverrides` | `SerializedStyles` \| `SerializedStyles[]` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
758
- | `className` | `string` | No | N/A | Optional class name forwarded to the root React Aria button. |
759
- | `...props` | `ReactAria Button` props | No | N/A | All other props from `react-aria-components` `Button` (e.g. `type`, `autoFocus`, event handlers). |
760
-
761
- **`size` and `variant`**
762
-
763
- - Sizes: `xs`, `sm`, `md`, `lg`
764
- - Variants: `emphasised-primary`, `emphasised-secondary`, `neutral-primary`, `neutral-secondary`
765
-
766
- #### Customisation
767
-
768
- We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
769
-
770
- **Custom theme**
771
-
772
- ```tsx
773
- import type { ButtonTheme } from '@guardian/stand/button';
774
- import { Button } from '@guardian/stand/button';
775
- import { baseColors } from '@guardian/stand';
776
-
777
- const customTheme: Partial<ButtonTheme> = {
778
- 'emphasised-primary': {
779
- shared: {
780
- backgroundColor: baseColors['cool-purple'][200],
781
- color: baseColors['cool-purple'][900],
782
- border: `2px solid ${baseColors['cool-purple'][700]}`,
783
- ':hover': {
784
- backgroundColor: baseColors['cool-purple'][300],
785
- border: `2px solid ${baseColors['cool-purple'][700]}`,
786
- },
787
- ':active': {
788
- backgroundColor: baseColors['cool-purple'][400],
789
- border: `2px solid ${baseColors['cool-purple'][700]}`,
790
- },
791
- },
792
- },
793
- };
794
-
795
- const Component = () => (
796
- <Button variant="emphasised-primary" size="md" theme={customTheme}>
797
- Custom Themed Button
798
- </Button>
799
- );
800
- ```
33
+ ## Documentation
801
34
 
802
- **CSS overrides**
35
+ All documentation is available on [Github Pages Storybook](https://guardian.github.io/stand/), which is updated with the latest changes to the library.
803
36
 
804
- ```tsx
805
- import { Button } from '@guardian/stand/button';
806
- import { baseSpacing } from '@guardian/stand';
807
- import { css } from '@emotion/react';
37
+ ## Install
808
38
 
809
- const customStyles = css`
810
- width: 100%;
811
- text-transform: full-width;
812
- font-variant: small-caps;
813
- padding-inline: ${baseSpacing['24-rem']};
814
- `;
39
+ The library is published to npm:
815
40
 
816
- const Component = () => (
817
- <Button variant="neutral-primary" size="sm" cssOverrides={customStyles}>
818
- CSSOverrides Button
819
- </Button>
820
- );
821
- ```
822
-
823
- #### Button Custom Component Build
824
-
825
- If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom `Button`/`LinkButton` component using the styles defined in the `ButtonTheme` type.
826
-
827
- You will however be responsible for any additional functionality on top of the styles, for example accessibility, focus management, interaction states etc.
828
-
829
- See "Custom Component" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
830
-
831
- **`css`**
832
-
833
- You can import the Button styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
834
-
835
- ```css
836
- /* import the font and button variables */
837
- @import '@guardian/stand/fonts/OpenSans.css';
838
- @import '@guardian/stand/component/button.css';
839
-
840
- /*
841
- or for scenarios where you have to use relative paths/node_modules directly:
842
-
843
- @import 'node_modules/@guardian/stand/dist/fonts/OpenSans.css';
844
- @import 'node_modules/@guardian/stand/dist/styleD/build/css/component/button.css';
845
- */
846
-
847
- /* shared button styles for all variants */
848
- .stand-button {
849
- display: var(--component-button-shared-display);
850
- -webkit-appearance: var(--component-button-shared-webkit-appearance);
851
- text-align: var(--component-button-shared-text-align);
852
- box-shadow: var(--component-button-shared-box-shadow);
853
- text-decoration: var(--component-button-shared-text-decoration);
854
- cursor: var(--component-button-shared-cursor);
855
- justify-content: var(--component-button-shared-justify-content);
856
- align-items: var(--component-button-shared-align-items);
857
- }
858
- .stand-button:focus-visible {
859
- outline: var(--component-button-shared-focus-visible-outline);
860
- outline-offset: var(--component-button-shared-focus-visible-outline-offset);
861
- }
862
- .stand-button:disabled {
863
- cursor: var(--component-button-shared-disabled-cursor);
864
- }
865
-
866
- /* example setup of button/link button style using md size and emphasised primary variant */
867
- .stand-button-emphasised-primary {
868
- color: var(--component-button-emphasised-primary-shared-color);
869
- background: var(
870
- --component-button-emphasised-primary-shared-background-color
871
- );
872
- height: var(--component-button-emphasised-primary-md-height);
873
- padding: var(--component-button-emphasised-primary-md-padding-top)
874
- var(--component-button-emphasised-primary-md-padding-right)
875
- var(--component-button-emphasised-primary-md-padding-bottom)
876
- var(--component-button-emphasised-primary-md-padding-left);
877
- font: var(--component-button-emphasised-primary-md-typography-font);
878
- letter-spacing: var(
879
- --component-button-emphasised-primary-md-typography-letter-spacing
880
- );
881
- font-variation-settings: 'wdth'
882
- var(--component-button-emphasised-primary-md-typography-font-width);
883
- border: var(--component-button-emphasised-primary-shared-border);
884
- border-radius: var(
885
- --component-button-emphasised-primary-shared-border-radius
886
- );
887
- }
888
- .stand-button-emphasised-primary:hover {
889
- background: var(
890
- --component-button-emphasised-primary-shared-hover-background-color
891
- );
892
- border: var(--component-button-emphasised-primary-shared-hover-border);
893
- }
894
- .stand-button-emphasised-primary:active {
895
- background: var(
896
- --component-button-emphasised-primary-shared-active-background-color
897
- );
898
- border: var(--component-button-emphasised-primary-shared-active-border);
899
- }
900
- .stand-button-emphasised-primary:disabled {
901
- color: var(--component-button-emphasised-primary-shared-disabled-color);
902
- background: var(
903
- --component-button-emphasised-primary-shared-disabled-background-color
904
- );
905
- border: var(--component-button-emphasised-primary-shared-disabled-border);
906
- }
907
-
908
- /* example setup of button/link button style using md size and neutral secondary variant */
909
- .stand-button-neutral-secondary {
910
- color: var(--component-button-neutral-secondary-shared-color);
911
- background: var(
912
- --component-button-neutral-secondary-shared-background-color
913
- );
914
- height: var(--component-button-neutral-secondary-md-height);
915
- padding: var(--component-button-neutral-secondary-md-padding-top)
916
- var(--component-button-neutral-secondary-md-padding-right)
917
- var(--component-button-neutral-secondary-md-padding-bottom)
918
- var(--component-button-neutral-secondary-md-padding-left);
919
- font: var(--component-button-neutral-secondary-md-typography-font);
920
- letter-spacing: var(
921
- --component-button-neutral-secondary-md-typography-letter-spacing
922
- );
923
- font-variation-settings: 'wdth'
924
- var(--component-button-neutral-secondary-md-typography-font-width);
925
- border: var(--component-button-neutral-secondary-shared-border);
926
- border-radius: var(
927
- --component-button-neutral-secondary-shared-border-radius
928
- );
929
- }
930
- .stand-button-neutral-secondary:hover {
931
- background: var(
932
- --component-button-neutral-secondary-shared-hover-background-color
933
- );
934
- border: var(--component-button-neutral-secondary-shared-hover-border);
935
- }
936
- .stand-button-neutral-secondary:active {
937
- background: var(
938
- --component-button-neutral-secondary-shared-active-background-color
939
- );
940
- border: var(--component-button-neutral-secondary-shared-active-border);
941
- }
942
- .stand-button-neutral-secondary:disabled {
943
- color: var(--component-button-neutral-secondary-shared-disabled-color);
944
- background: var(
945
- --component-button-neutral-secondary-shared-disabled-background-color
946
- );
947
- border: var(--component-button-neutral-secondary-shared-disabled-border);
948
- }
949
- ```
950
-
951
- ```html
952
- <button class="stand-button stand-button-emphasised-primary">
953
- Button Label
954
- </button>
955
- <button class="stand-button stand-button-emphasised-primary" disabled>
956
- Disabled Button Label
957
- </button>
958
- <a class="stand-button stand-button-neutral-secondary" href="#"
959
- >LinkButton Label</a
960
- >
961
- ```
962
-
963
- **TypeScript/JavaScript**
964
-
965
- Use the `componentButton` variable and the `ComponentButton` type to define your custom styles in TypeScript/JavaScript:
966
-
967
- ```ts
968
- import type { ComponentButton } from '@guardian/stand'; // if types required
969
- import { componentButton } from '@guardian/stand/button';
970
-
971
- /* NB: The HTML style attribute cannot target psuedo selectors, so they haven't been implemented e.g. hover/focus */
972
- const sharedButtonStyles = `
973
- display: ${componentButton.shared.display};
974
- -webkit-appearance: ${componentButton.shared['-webkit-appearance']};
975
- text-align: ${componentButton.shared['text-align']};
976
- box-shadow: ${componentButton.shared['box-shadow']};
977
- text-decoration: ${componentButton.shared['text-decoration']};
978
- cursor: ${componentButton.shared.cursor};
979
- justify-content: ${componentButton.shared['justify-content']};
980
- align-items: ${componentButton.shared['align-items']};
981
- `;
982
-
983
- const emphasisedPrimaryButtonStyles = `
984
- ${sharedButtonStyles}
985
- color: ${componentButton['emphasised-primary'].shared.color};
986
- background: ${componentButton['emphasised-primary'].shared.backgroundColor};
987
- height: ${componentButton['emphasised-primary'].md.height};
988
- padding: ${componentButton['emphasised-primary'].md.padding.top}
989
- ${componentButton['emphasised-primary'].md.padding.right}
990
- ${componentButton['emphasised-primary'].md.padding.bottom}
991
- ${componentButton['emphasised-primary'].md.padding.left};
992
- font: ${componentButton['emphasised-primary'].md.typography.font};
993
- letter-spacing: ${componentButton['emphasised-primary'].md.typography.letterSpacing};
994
- font-variation-settings: 'wdth'
995
- ${componentButton['emphasised-primary'].md.typography.fontWidth};
996
- border: ${componentButton['emphasised-primary'].shared.border};
997
- border-radius: ${componentButton['emphasised-primary'].shared.borderRadius};
998
- `;
999
-
1000
- const emphasisedPrimaryButtonDisabledStyles = `
1001
- ${emphasisedPrimaryButtonStyles}
1002
- cursor: ${componentButton.shared[':disabled'].cursor};
1003
- color: ${componentButton['emphasised-primary'].shared[':disabled'].color};
1004
- background: ${componentButton['emphasised-primary'].shared[':disabled'].backgroundColor};
1005
- border: ${componentButton['emphasised-primary'].shared[':disabled'].border};
1006
- `;
1007
-
1008
- const neutralSecondaryButtonStyles = `
1009
- ${sharedButtonStyles}
1010
- color: ${componentButton['neutral-secondary'].shared.color};
1011
- background: ${componentButton['neutral-secondary'].shared.backgroundColor};
1012
- height: ${componentButton['neutral-secondary'].md.height};
1013
- padding: ${componentButton['neutral-secondary'].md.padding.top}
1014
- ${componentButton['neutral-secondary'].md.padding.right}
1015
- ${componentButton['neutral-secondary'].md.padding.bottom}
1016
- ${componentButton['neutral-secondary'].md.padding.left};
1017
- font: ${componentButton['neutral-secondary'].md.typography.font};
1018
- letter-spacing: ${componentButton['neutral-secondary'].md.typography.letterSpacing};
1019
- font-variation-settings: 'wdth'
1020
- ${componentButton['neutral-secondary'].md.typography.fontWidth};
1021
- border: ${componentButton['neutral-secondary'].shared.border};
1022
- border-radius: ${componentButton['neutral-secondary'].shared.borderRadius};
1023
- `;
1024
-
1025
- // e.g. adding to DOM using vanilla JS styles
1026
- document.getElementById('app')!.innerHTML = `
1027
- <button style="${emphasisedPrimaryButtonStyles}">Button Label</button>
1028
- <button disabled style="${emphasisedPrimaryButtonDisabledStyles}">Disabled Button Label</button>
1029
- <a href="#" style="${neutralSecondaryButtonStyles}">LinkButton Label</a>
1030
- `;
1031
- ```
1032
-
1033
- ### `Icon`
1034
-
1035
- The Icon component provides a flexible way to display icons in your application. It supports both Material Symbols (font-based icons) and SVG icons (Material Icons or custom SVG components), with consistent sizing and color control.
1036
-
1037
- **When to use**
1038
-
1039
- - Display icons alongside text or buttons
1040
- - Represent actions, states, or categories visually
1041
- - Provide visual cues in UI elements
1042
-
1043
- **Peer dependencies:**
1044
-
1045
- - `@emotion/react`
1046
- - `react`
1047
- - `react-dom`
1048
- - `typescript`
1049
-
1050
- See the `peerDependencies` section of `package.json` for compatible versions.
1051
-
1052
- See [icon custom component build](#icon-custom-component-build) for usage without React/Emotion.
1053
-
1054
- #### Example usage
1055
-
1056
- See "Emotion/React" heading under the `Icon` component on [codesandbox.io](https://codesandbox.io/p/sandbox/mrzkrw).
1057
-
1058
- ```tsx
1059
- import { Icon } from '@guardian/stand/icon';
1060
- import { baseColors } from '@guardian/stand';
1061
-
1062
- /* types, if required */
1063
- import type { IconProps, IconTheme } from '@guardian/stand/icon';
1064
-
1065
- /* Material Symbols (font icon) */
1066
- <Icon size="md" symbol="home"></Icon>
1067
-
1068
- /* Material Symbols with custom color */
1069
- <Icon size="md" fill={baseColors.red[500]} symbol="home"></Icon>
1070
-
1071
- /* Standalone meaningful icon (use alt prop) */
1072
- <Icon alt="Warning: High priority" symbol="warning"></Icon>
1073
-
1074
- /* Material Icons (SVG) */
1075
- import HomeIcon from '@material-design-icons/svg/outlined/home.svg?react';
1076
- <Icon size="md" alt="Home">
1077
- <HomeIcon />
1078
- </Icon>
1079
-
1080
- /* Custom SVG component */
1081
- const CustomIcon = () => (
1082
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1083
- <path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z" />
1084
- </svg>
1085
- );
1086
-
1087
- <Icon size="lg" fill={baseColors.blue[500]} alt="Shield protection">
1088
- <CustomIcon />
1089
- </Icon>
1090
- ```
1091
-
1092
- #### Props
1093
-
1094
- | Name | Type | Required | Default | Description |
1095
- | -------------- | ------------------------------- | ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
1096
- | `symbol` | `MaterialSymbol` | Conditional | N/A | Name of the Material Symbol to display (e.g., "home", "add"). Uses the `MaterialSymbol` type for type safety. Alternative to providing the icon as a child. |
1097
- | `children` | `ReactNode` \| `MaterialSymbol` | Conditional | N/A | Icon content - either a Material Symbol name (typed as `MaterialSymbol`), an SVG component (ReactNode), or left empty if using `symbol`. |
1098
- | `size` | `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the icon dimensions. |
1099
- | `fill` | `string` | No | N/A | Fill/color of the icon. Default is to inherit from text color or icon defaults. |
1100
- | `alt` | `string` | Conditional | N/A | Alternative text for screen readers. Required if the icon conveys meaning on its own. Omit for decorative icons alongside text. |
1101
- | `theme` | `IconTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
1102
- | `cssOverrides` | `SerializedStyles` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
1103
- | `className` | `string` | No | N/A | Additional class name(s) for the icon. For Material Symbols, combines with `material-symbols`. |
1104
-
1105
- **`size`**
1106
-
1107
- The icon supports three sizes:
1108
-
1109
- - `sm` (small): 20px
1110
- - `md` (medium): 24px
1111
- - `lg` (large): 32px
1112
-
1113
- **`symbol`**
1114
-
1115
- The `symbol` prop provides a convenient way to specify a Material Symbol icon by name (typed as `MaterialSymbol`, e.g., `"home"`, `"add"`, `"close"`). This is the recommended approach for font-based icons. If both `symbol` and `children` are provided, `symbol` takes precedence for Material Symbols.
1116
-
1117
- **`children`**
1118
-
1119
- The Icon component accepts two types of children:
1120
-
1121
- - **MaterialSymbol**: Pass the icon name as a string typed as `MaterialSymbol` (e.g., `"home"`, `"add"`, `"close"`). Requires the Material Symbols font to be loaded. (Prefer using the `symbol` prop for clarity.)
1122
- - **ReactNode** (SVG): Pass an SVG component (e.g., from `@material-design-icons/svg` or a custom SVG component).
1123
-
1124
- **`alt`**
1125
-
1126
- Provide alternative text to describe the icon's meaning for screen readers:
1127
-
1128
- - **When to use**: Icons that convey meaning on their own (status indicators, standalone alerts, informational graphics)
1129
- - **When to omit**: Icons that are purely decorative or appear alongside descriptive text
1130
-
1131
- #### Customisation
1132
-
1133
- We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
1134
-
1135
- **Custom theme**
1136
-
1137
- ```tsx
1138
- import type { IconTheme } from '@guardian/stand/icon';
1139
- import { Icon } from '@guardian/stand/icon';
1140
- import { baseSizing } from '@guardian/stand';
1141
-
1142
- const customTheme: Partial<IconTheme> = {
1143
- shared: {
1144
- display: 'block',
1145
- 'user-select': 'all',
1146
- },
1147
- md: {
1148
- size: baseSizing['size-48-rem'],
1149
- },
1150
- };
1151
-
1152
- const Component = () => (
1153
- <Icon size="md" theme={customTheme}>
1154
- home
1155
- </Icon>
1156
- );
1157
- ```
1158
-
1159
- **CSS overrides**
1160
-
1161
- ```tsx
1162
- import { Icon } from '@guardian/stand/icon';
1163
- import { baseColors, baseSpacing, semanticSizing } from '@guardian/stand';
1164
- import { css } from '@emotion/react';
1165
-
1166
- const customStyles = css`
1167
- padding: ${baseSpacing['4-rem']};
1168
- background-color: ${baseColors.yellow[400]};
1169
- border-radius: ${semanticSizing.border['extra-wide']};
1170
- `;
1171
-
1172
- const Component = () => (
1173
- <Icon size="lg" fill={baseColors.neutral[900]} cssOverrides={customStyles}>
1174
- home
1175
- </Icon>
1176
- );
1177
- ```
1178
-
1179
- #### Icon Custom Component Build
1180
-
1181
- If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom Icon component using the styles defined in the `IconTheme` type.
1182
-
1183
- You will however be responsible for any additional functionality on top of the styles, for example icon loading, accessibility, etc.
1184
-
1185
- See "Custom Component" heading under the `Icon` component on [codesandbox.io](https://codesandbox.io/p/sandbox/mrzkrw) for an example of a custom component build without React/Emotion.
1186
-
1187
- **`css`**
1188
-
1189
- You can import the Icon styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
1190
-
1191
- ```css
1192
- /* import the icon font and icon styles */
1193
- @import '@guardian/stand/fonts/MaterialSymbolsOutlined.css';
1194
- @import '@guardian/stand/component/icon.css';
1195
-
1196
- /*
1197
- or for scenarios where you have to use relative paths/node_modules directly:
1198
-
1199
- @import 'node_modules/@guardian/stand/dist/fonts/MaterialSymbolsOutlined.css';
1200
- @import 'node_modules/@guardian/stand/dist/style/build/css/component/icon.css';
1201
- */
1202
-
1203
- @import '@guardian/stand/base/spacing.css';
1204
- @import '@guardian/stand/base/colors.css';
1205
-
1206
- .stand-icon {
1207
- display: var(--component-icon-shared-display);
1208
- user-select: var(--component-icon-shared-user-select);
1209
- font-size: var(--component-icon-lg-size);
1210
- }
1211
-
1212
- .stand-icon-font-color {
1213
- color: var(--base-colors-magenta-400);
1214
- }
1215
-
1216
- .stand-icon-svg > svg {
1217
- width: var(--component-icon-lg-size);
1218
- height: var(--component-icon-lg-size);
1219
- }
1220
-
1221
- .stand-icon-svg-color > svg {
1222
- fill: var(--base-colors-magenta-400);
1223
- }
1224
- ```
1225
-
1226
- ```html
1227
- <!-- Material Symbols (font icons) -->
1228
- <span class="material-symbols stand-icon">home</span>
1229
- <span class="material-symbols stand-icon stand-icon-font-color">upload</span>
1230
-
1231
- <!-- SVG icons -->
1232
- <span class="material-symbols stand-icon stand-icon-svg">
1233
- <svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
1234
- </span>
1235
- <span class="material-symbols stand-icon stand-icon-svg stand-icon-svg-color">
1236
- <svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
1237
- </span>
1238
- ```
1239
-
1240
- **TypeScript/JavaScript**
1241
-
1242
- Use the `componentIcon` variable and the `ComponentIcon` type to define your custom styles in TypeScript/JavaScript:
1243
-
1244
- ```ts
1245
- import type { ComponentIcon } from '@guardian/stand'; // if types required
1246
- import { componentIcon, baseColors } from '@guardian/stand';
1247
-
1248
- const iconStyles = `
1249
- display: ${componentIcon.shared.display};
1250
- user-select: ${componentIcon.shared['user-select']};
1251
- font-size: ${componentIcon.lg.size};
1252
- `;
1253
-
1254
- const iconFontColorStyles = `
1255
- ${iconStyles}
1256
- color: ${baseColors.magenta[400]};
1257
- `;
1258
-
1259
- const iconSvgStyles = `
1260
- width: ${componentIcon.lg.size};
1261
- height: ${componentIcon.lg.size};
1262
- `;
1263
-
1264
- const iconSvgColorStyles = `
1265
- ${iconSvgStyles}
1266
- fill: ${baseColors.magenta[400]};
1267
- `;
1268
-
1269
- // e.g. adding to DOM using vanilla JS styles
1270
- document.getElementById('app')!.innerHTML = `
1271
- <h3>
1272
- Using <code>typescript</code>/<code>javascript</code>
1273
- </h3>
1274
- <div>see <code>src/icon/custom.ts</code><div>
1275
- <div style="margin-top: 4px;">Material Symbols</div>
1276
- <div class="container">
1277
- <span class="material-symbols" style="${iconStyles}">home</span>
1278
- <span class="material-symbols" style="${iconFontColorStyles}">upload</span>
1279
- </div>
1280
- <div style="margin-top: 4px;">SVGs</div>
1281
- <div class="container">
1282
- <span class="material-symbols" style="${iconStyles}"><svg style="${iconSvgStyles}"...>...</svg></span>
1283
- <span class="material-symbols" style="${iconFontColorStyles}"><svg style="${iconSvgColorStyles}"...>...</svg></span>
1284
- </div>
1285
- `;
1286
- ```
1287
-
1288
- ### `LinkButton`
1289
-
1290
- An anchor element styled like the Stand `Button` component, based on the React Aria `Link`. It keeps link semantics (`href`, `target`, `rel`) while sharing the same variants and sizes as `Button`.
1291
-
1292
- **When to use**
1293
-
1294
- - Navigational links that should visually align with buttons
1295
- - Cross-page CTAs where a button look-and-feel is preferred
1296
- - Situations requiring disabled styling while preserving link semantics
1297
-
1298
- **Peer dependencies:**
1299
-
1300
- - `@emotion/react`
1301
- - `react`
1302
- - `react-dom`
1303
- - `react-aria-components`
1304
- - `typescript`
1305
-
1306
- See the `peerDependencies` section of `package.json` for compatible versions.
1307
-
1308
- See [link button custom component build](#button-custom-component-build) for usage without React/Emotion.
1309
-
1310
- #### Example usage
1311
-
1312
- See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
1313
-
1314
- ```tsx
1315
- import { LinkButton } from '@guardian/stand/button';
1316
- import type { LinkButtonTheme, LinkButtonProps } from '@guardian/stand/button';
1317
-
1318
- const Example = () => (
1319
- <>
1320
- <LinkButton
1321
- href="/article"
1322
- variant="emphasised-primary"
1323
- size="md"
1324
- target="_blank"
1325
- rel="noreferrer"
1326
- >
1327
- Read article
1328
- </LinkButton>
1329
-
1330
- <LinkButton
1331
- href="/settings"
1332
- variant="neutral-secondary"
1333
- size="sm"
1334
- isDisabled
1335
- >
1336
- Disabled link
1337
- </LinkButton>
1338
- </>
1339
- );
1340
- ```
1341
-
1342
- #### Props
1343
-
1344
- | Name | Type | Required | Default | Description |
1345
- | -------------- | -------------------------------------------------------------------------------------------------- | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
1346
- | `href` | `string` | Yes | N/A | Destination URL for the link. |
1347
- | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the link-button dimensions and typography. |
1348
- | `variant` | `'emphasised-primary'` \| `'emphasised-secondary'` \| `'neutral-primary'` \| `'neutral-secondary'` | No | `'emphasised-primary'` | Chooses the colour scheme and interaction states. |
1349
- | `children` | `ReactNode` | Yes | N/A | Content of the link. |
1350
- | `isDisabled` | `boolean` | No | `false` | Disables interaction and applies disabled styling. |
1351
- | `theme` | `LinkButtonTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
1352
- | `cssOverrides` | `SerializedStyles` \| `SerializedStyles[]` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
1353
- | `className` | `string` | No | N/A | Optional class name forwarded to the root React Aria link. |
1354
- | `...props` | `ReactAria Link` props | No | N/A | All other props from `react-aria-components` `Link` (e.g. `target`, `rel`, `aria-label`, event handlers). |
1355
-
1356
- **`size` and `variant`**
1357
-
1358
- - Sizes: `xs`, `sm`, `md`, `lg`
1359
- - Variants: `emphasised-primary`, `emphasised-secondary`, `neutral-primary`, `neutral-secondary`
1360
-
1361
- #### Customisation
1362
-
1363
- We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
1364
-
1365
- **Custom theme**
1366
-
1367
- ```tsx
1368
- import type { LinkButtonTheme } from '@guardian/stand/button';
1369
- import { LinkButton } from '@guardian/stand/button';
1370
- import { baseColors } from '@guardian/stand';
1371
-
1372
- const customTheme: Partial<LinkButtonTheme> = {
1373
- 'emphasised-primary': {
1374
- shared: {
1375
- backgroundColor: baseColors['cool-purple'][200],
1376
- color: baseColors['cool-purple'][900],
1377
- border: `2px solid ${baseColors['cool-purple'][700]}`,
1378
- ':hover': {
1379
- backgroundColor: baseColors['cool-purple'][300],
1380
- border: `2px solid ${baseColors['cool-purple'][700]}`,
1381
- },
1382
- ':active': {
1383
- backgroundColor: baseColors['cool-purple'][400],
1384
- border: `2px solid ${baseColors['cool-purple'][700]}`,
1385
- },
1386
- },
1387
- },
1388
- };
1389
-
1390
- const Component = () => (
1391
- <LinkButton
1392
- href="/"
1393
- variant="emphasised-primary"
1394
- size="md"
1395
- theme={customTheme}
1396
- >
1397
- Custom Themed LinkButton
1398
- </LinkButton>
1399
- );
1400
- ```
1401
-
1402
- **CSS overrides**
1403
-
1404
- ```tsx
1405
- import { LinkButton } from '@guardian/stand/button';
1406
- import { baseSpacing } from '@guardian/stand';
1407
- import { css } from '@emotion/react';
1408
-
1409
- const customStyles = css`
1410
- width: 100%;
1411
- text-transform: full-width;
1412
- font-variant: small-caps;
1413
- padding-inline: ${baseSpacing['24-rem']};
1414
- `;
1415
-
1416
- const Component = () => (
1417
- <LinkButton
1418
- href="/"
1419
- variant="neutral-primary"
1420
- size="sm"
1421
- cssOverrides={customStyles}
1422
- >
1423
- CSSOverrides LinkButton
1424
- </LinkButton>
1425
- );
1426
- ```
1427
-
1428
- #### LinkButton Custom Component Build
1429
-
1430
- LinkButton shares the same tokens and CSS output as `Button`. Use the [Button custom component build](#button-custom-component-build) guidance to consume the CSS or TypeScript tokens when you need a non-React implementation.
1431
-
1432
- ### Typography
1433
-
1434
- The Typography component provides a convenient way to wrap React elements in a font variant.
1435
-
1436
- **Peer dependencies**
1437
-
1438
- - `@emotion/react`
1439
- - `react`
1440
- - `react-dom`
1441
- - `typescript`
1442
-
1443
- See the `peerDependencies` section of `package.json` for compatible versions.
1444
-
1445
- #### Example usage
1446
-
1447
- ```tsx
1448
- import { Typography } from '@guardian/stand/typography';
1449
-
1450
- /* types, if required */
1451
- import type { Typography, TypographyTheme } from '@guardian/stand/typography';
1452
-
1453
- /* Paragraph element with body-md preset and text children */
1454
- <Typography element="p" variant="body-md">Body text here</Typography>
1455
-
1456
- /* Div with an italic text wrapped inside */
1457
- <Typography element="div" variant="body-md">Some text, with <Typography element="i" variant="body-italic-md">even more text</Typography></Typography>
1458
- ```
1459
-
1460
- #### Props
1461
-
1462
- | Name | Type | Required | Default | Description |
1463
- | -------------- | ------------------ | -------- | --------- | ---------------------------------------------------- |
1464
- | `element` | Various | No | 'span' | HTML element to render with font applied to. |
1465
- | `variant` | Various | No | 'body-md' | Font variant to apply as a CSS style to the element. |
1466
- | `children` | `ReactNode` | No | N/A | Content to render inside the supplied HTML element. |
1467
- | `theme` | `TypographyTheme` | No | N/A | Custom theme overrides for the typography. |
1468
- | `cssOverrides` | `SerializedStyles` | No | N/A | Custom CSS styles for the typography. |
1469
- | `className` | `string` | No | N/A | Additional class name(s) for the typography. |
1470
-
1471
- #### Customisation
1472
-
1473
- **Custom theme**
1474
-
1475
- The `theme` prop allows you to override the color of the text:
1476
-
1477
- ```tsx
1478
- import type { TypographyTheme } from '@guardian/stand/typography';
1479
- import { Typography } from '@guardian/stand/typography';
1480
-
1481
- const customTheme: TypographyTheme = {
1482
- color: 'red',
1483
- };
1484
- const Component = () => (
1485
- <Typography element="p" variant="body-md" theme={customTheme}>
1486
- Text
1487
- </Typography>
1488
- );
1489
- ```
1490
-
1491
- **CSS overrides**
1492
-
1493
- The `cssOverrides` prop allows you to pass custom CSS to the rendered element.
1494
-
1495
- ## Components - Editorial
1496
-
1497
- Specialised components for use in specific editorial use cases.
1498
-
1499
- ### `Byline`
1500
-
1501
- A flexible byline editor component built in ProseMirror and React with usability and accessibility in mind.
1502
-
1503
- **Peer dependencies:**
1504
-
1505
- You'll need to install the following peer dependencies in your project to use the `Byline` component:
1506
-
1507
- - `@emotion/react`
1508
- - `@guardian/prosemirror-invisibles`
1509
- - `prosemirror-dropcursor`
1510
- - `prosemirror-history`
1511
- - `prosemirror-keymap`
1512
- - `prosemirror-model`
1513
- - `prosemirror-state`
1514
- - `prosemirror-view`
1515
- - `react`
1516
- - `react-dom`
1517
- - `typescript`
1518
-
1519
- See the `peerDependencies` section of `package.json` for compatible versions.
1520
-
1521
- **Note:** If you only need the built CSS (`@guardian/stand/component/byline.css`), you don't need to install these dependencies.
1522
-
1523
- #### Usage
41
+ ```bash
42
+ # With pnpm
43
+ pnpm add @guardian/stand
1524
44
 
1525
- ```tsx
1526
- import type { BylineModel } from '@guardian/stand/byline';
1527
- import { Byline } from '@guardian/stand/byline';
45
+ # or with npm
46
+ npm install @guardian/stand
1528
47
 
1529
- const Component = () => {
1530
- const bylineModel: BylineModel = {
1531
- // ...set up your byline model here
1532
- };
1533
- ...
1534
- return (
1535
- <>
1536
- ...
1537
- <Byline initialValue={bylineModel} />
1538
- ...
1539
- </>
1540
- );
1541
- };
48
+ # or with yarn
49
+ yarn add @guardian/stand
1542
50
  ```
1543
51
 
1544
- By itself the `Byline` component is just the editor UI. You will need to set up the ProseMirror editor state, schema, and plugins to get a fully functioning byline editor. See the props and example below for a more complete implementation.
1545
-
1546
- The `BylineModel` type defines the structure of the byline data which is agnostic from any other data structure. You must convert to/from this model when integrating with your application's data structures.
52
+ Depending on your project setup, you may also need to install [peer dependencies](https://nodejs.org/en/blog/npm/peer-dependencies).
1547
53
 
1548
- #### Props
54
+ **My project uses React, Emotion, and TypeScript:**
1549
55
 
1550
- See [`BylineProps`](src/byline/Byline.tsx#L41) for the full list of props, usage example can be seen in Storybook.
56
+ See the `peerDependencies` in `package.json` for compatible versions and install them.
1551
57
 
1552
- #### Example
58
+ If you're using the Tools Design System, you may also want to install the compatible version of [`react-aria-components`](https://react-aria.adobe.com/).
1553
59
 
1554
- The `ContentByline` component in `flexible-frontend` has a detailed example of how to use the `Byline` component from Stand. See [ContentByline.tsx](https://github.com/guardian/flexible-content/blob/1d537615a18ae24a4a5410a3f945b2b9db1dbb47/flexible-frontend/src/app/components/furniture/content-byline/ContentByline.tsx#L72-L205).
60
+ For specific Editorial Components, check the documentation for each component for additional dependencies.
1555
61
 
1556
- ### TagPicker
62
+ **My project doesn't (or can't) use React, Emotion, or TypeScript:**
1557
63
 
1558
- #### TagAutocomplete
64
+ You can still use design tokens and styles from the Tools Design System. Import [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties) and/or JS design tokens directly from the package. Every component has a "Custom Component Build" section in its documentation showing how to do this.
1559
65
 
1560
- _Status: Testing_
66
+ **My project doesn't use JavaScript at all:**
1561
67
 
1562
- Part of the overall TagPicker component, the TagAutocomplete provides an accessible
1563
- autocomplete input for selecting tags from a list of options, based on the [React Aria ComboBox](https://react-spectrum.adobe.com/react-aria/ComboBox) component.
68
+ You can import CSS variables from the package, provided your build process supports importing CSS from `node_modules`. See the "Custom Component Build" section in each component's documentation.
1564
69
 
1565
- **Peer dependencies:**
70
+ ## Usage
1566
71
 
1567
- - `@emotion/react`
1568
- - `react`
1569
- - `react-aria-components`
1570
- - `react-dom`
1571
- - `typescript`
72
+ See the [Tools Design System](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs) and [Editorial Components](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs) documentation for details.
1572
73
 
1573
- See the `peerDependencies` section of `package.json` for compatible versions.
74
+ ## Contributing
1574
75
 
1575
- **Note:** If you only need the built CSS (`@guardian/stand/component/tagAutocomplete.css`), you don't need to install these dependencies.
76
+ See the [Contributing guidelines](https://guardian.github.io/stand/?path=/docs/contributing--docs) for guidelines on contributing to this project. Project setup and common tasks are listed below.
1576
77
 
1577
- ##### Props
78
+ ## Getting Started
1578
79
 
1579
- See [`TagAutocompleteProps`](src/components/tag-picker/TagAutocomplete.tsx#L23) for the full list of props, usage example can be seen in Storybook.
80
+ > If you are looking to **use Stand in your project**, see the [Tools Design System](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs) or [Editorial Components](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs) documentation for installation and usage instructions.
1580
81
 
1581
- #### TagTable
82
+ The following steps are for **developing the Stand library itself**.
1582
83
 
1583
- _Status: Testing_
84
+ 1. **Install dependencies:**
1584
85
 
1585
- Part of the overall TagPicker component, the TagTable provides an accessible
1586
- table for displaying tags, with options to add, remove, and reorder tags via drag and drop,
1587
- based on the [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table) component.
86
+ ```bash
87
+ # With pnpm
88
+ pnpm install
89
+ ```
1588
90
 
1589
- **Peer dependencies:**
1590
-
1591
- - `@emotion/react`
1592
- - `react`
1593
- - `react-aria-components`
1594
- - `react-dom`
1595
- - `typescript`
1596
-
1597
- See the `peerDependencies` section of `package.json` for compatible versions.
1598
-
1599
- **Note:** If you only need the built CSS (`@guardian/stand/component/tagTable.css`), you don't need to install these dependencies.
1600
-
1601
- ##### Props
1602
-
1603
- See [`TagTableProps`](src/components/tag-picker/TagTable.tsx#L31) for the full list of props, usage example can be seen in Storybook.
1604
-
1605
- #### Usage
1606
-
1607
- _Example with TagAutocomplete and TagTable combined:_
1608
-
1609
- ```tsx
1610
- import { TagAutocomplete, TagTable } from '@guardian/stand/tag-picker';
1611
-
1612
- const Component = () => {
1613
- const [selectedTags, setSelectedTags] = useState<
1614
- TagManagerObjectData[] // TagManagerObjectData is an internal type representing a Tag
1615
- >([]);
1616
- const [options, setOptions] = useState<TagManagerObjectData[]>([]);
1617
- const [value, setValue] = useState('');
1618
- const onChange = (inputText: string) => {
1619
- setValue(inputText);
1620
- if (inputText === '') {
1621
- setOptions([]);
1622
- return;
1623
- }
1624
-
1625
- if (inputText === '*') {
1626
- setOptions(exampleTags); // exampleTags is an array of Tags
1627
- return;
1628
- }
1629
-
1630
- // Simple filtering against exampleTags
1631
- const filteredItems = exampleTags.filter((t) =>
1632
- t.internalName.toLowerCase().includes(inputText.toLowerCase()),
1633
- );
1634
- return setOptions(filteredItems);
1635
- };
1636
- return (
1637
- <>
1638
- <div
1639
- css={css`
1640
- display: flex;
1641
- `}
1642
- >
1643
- <TagAutocomplete
1644
- onChange={onChange}
1645
- options={options}
1646
- label="Tags"
1647
- addTag={(tag) =>
1648
- setSelectedTags((tags) => {
1649
- return [...tags, tag];
1650
- })
1651
- }
1652
- loading={false}
1653
- placeholder={''}
1654
- disabled={false}
1655
- value={value}
1656
- />
1657
- <select>
1658
- option>All tags</option>
1659
- </select>
1660
- </div>
1661
- <TagTable rows={selectedTags} filterRows={() => true} />
1662
- </>
1663
- );
1664
- };
1665
- ```
91
+ 2. **Run Storybook**: Most development is done within Storybook, which provides a live environment for building and testing components in isolation:
1666
92
 
1667
- #### Example
93
+ ```bash
94
+ pnpm storybook
95
+ ```
1668
96
 
1669
- This is currently still in testing phase, so a production implementation is not yet available.
97
+ This will start Storybook at `http://localhost:6007`. See the [contributing guidelines](https://guardian.github.io/stand/?path=/docs/contributing--docs) for how to add new components and stories.
1670
98
 
1671
- ### `UserMenu`
99
+ 3. **Build** the package for publishing:
1672
100
 
1673
- The UserMenu component presents a collection of accessibility settings for users to customise their experience of using the application. The current supported settings are:
101
+ ```bash
102
+ pnpm build
103
+ ```
1674
104
 
1675
- - "Text Size"
1676
- - "Font Family"
1677
- - "Color scheme"
105
+ To regenerate Style Dictionary design token outputs after changing any token files (see [Design Tokens and Style Dictionary](#design-tokens-and-style-dictionary)):
1678
106
 
1679
- **Peer dependencies:**
107
+ ```bash
108
+ pnpm build-styled
109
+ ```
1680
110
 
1681
- - `@emotion/react`
1682
- - `react`
1683
- - `react-dom`
1684
- - `typescript`
1685
- - `react-aria-components`
111
+ 4. **Test:**
1686
112
 
1687
- See the `peerDependencies` section of `package.json` for compatible versions.
113
+ ```bash
114
+ pnpm test # unit tests (Jest)
115
+ pnpm test:e2e # end-to-end tests (Playwright)
116
+ pnpm test:react-matrix # compatibility matrix tests (see Compatibility)
117
+ pnpm tsc # TypeScript type checking
118
+ pnpm lint # lint (pnpm lint:fix to auto-fix)
119
+ pnpm format:check # formatting check (pnpm format:fix to auto-fix)
120
+ ```
1688
121
 
1689
- **When to use**
122
+ ## Design Tokens and Style Dictionary
1690
123
 
1691
- - as an application-level "accessibility options" panel
1692
- - as part of a more general options page
1693
-
1694
- #### Usage
1695
-
1696
- ```tsx
1697
- import { UserMenu, type UserMenuProps } from '@guardian/stand/user-menu';
1698
-
1699
- const customFontFamilyOptions = [
1700
- {
1701
- id: 'white',
1702
- buttonStyle: {
1703
- backgroundColor: 'white',
1704
- },
1705
- isDefault: true,
1706
- },
1707
- {
1708
- id: 'pink',
1709
- buttonStyle: {
1710
- backgroundColor: 'pink',
1711
- },
1712
- },
1713
- ];
1714
-
1715
- const Component = ({
1716
- currentPreferences,
1717
- updatePreferences,
1718
- }: {
1719
- currentPreferences: UserMenuProps['preferences'];
1720
- updatePreferences: UserMenuProps['updatePreferences'];
1721
- }) => {
1722
- ...
1723
- return (
1724
- <>
1725
- ...
1726
- <UserMenu
1727
- feedBacklink="https://example.com/feedback-form"
1728
- fontFamilyOptions={customFontFamilyOptions}
1729
- colorSchemeOptions={[]}
1730
- preferences={currentPreferences}
1731
- updatePreferences={updatePreferences}
1732
- />
1733
- ...
1734
- </>
1735
- );
1736
- };
1737
- ```
1738
-
1739
- `UserMenu` does not manage the persistence of the settings, nor how they are applied in the application when set - it merely presents a set of [controlled](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components) inputs for displaying and setting the values held by the application.
1740
-
1741
- There are options defaults for each of the setting, but these can be overridden with the props. IF the application does not support customising one of the options, setting an empty array for one of the sets of options will exclude that option from the UI (like in `colorSchemeOptions` in the example above).
1742
-
1743
- #### Props
1744
-
1745
- See [`UserMenuProps`](src/user-menu/UserMenu.tsx#L14) for the full list of props, usage example can be seen in Storybook.
1746
-
1747
- #### Example
1748
-
1749
- This is currently still in testing phase, so a production implementation is not yet available.
1750
-
1751
- ### Contributing
1752
-
1753
- See the [Contributing to Stand](./CONTRIBUTING.md) documentation for guidelines on contributing to this project. Project setup and common tasks are listed below.
1754
-
1755
- ### Setup
1756
-
1757
- - Run `./setup.sh` in the project root (flexible-content) directory to set up pnpm, install dependencies, and build the project.
1758
-
1759
- ### Tasks
1760
-
1761
- - Run `pnpm install` to install dependencies.
1762
- - Run `pnpm build` to build, this makes any changes available to flexible-frontend
1763
- - Run `pnpm storybook` to run Storybook
1764
- - Run `pnpm build:storybook` to build the Storybook static site
1765
- - Run `pnpm build-styled` to build the Style Dictionary styles
1766
- - Run `pnpm test` to run tests
1767
- - Run `pnpm test:e2e` to run end-to-end tests using Playwright
1768
- - Run `pnpm test:react-matrix` to run matrix tests (see Compatibility section below)
1769
- - Run `pnpm tsc` to run check TypeScript types
1770
- - Run `pnpm lint` to run the linter
1771
- - Run `pnpm lint:fix` to fix any auto-fixable issues
1772
- - Run `pnpm format:check` to check code formatting
1773
- - Run `pnpm format:fix` to fix code formatting issues
1774
-
1775
- ### Style Dictionary
124
+ The [Design Tokens](https://www.w3.org/community/design-tokens/) specification provides standards upon which products and design tools can rely for sharing stylistic pieces of a design system at scale.
1776
125
 
1777
126
  The project uses [Style Dictionary](https://styledictionary.com/) to manage design tokens.
1778
127
 
1779
- Tokens are defined in the `src/styleD/tokens` folder.
128
+ Tokens are defined in `src/styleD/tokens/`, the generated outputs live in `src/styleD/build/`, and both are committed to the repository. During the package build, Rollup copies the build outputs into `dist/styleD/build/` for publishing.
1780
129
 
1781
- The output styles are generated into the `src/styleD/build` folder.
130
+ Most foundation tokens are generated from Figma variables, see `scripts/figma/README.md` for details.
1782
131
 
1783
- We use rollup to copy the built styles into the `dist/styleD/build` folder during the build process, and these are published with the package.
132
+ After making changes to any token files, regenerate the outputs and commit them:
1784
133
 
1785
- Most tokens are generated from Figma variables using a script to make these available in the `src/styleD/tokens` folder. See the [Generate Design Tokens from Figma Variables](./scripts/figma/README.md) documentation for more details.
1786
-
1787
- Use `pnpm build-styled` to generate the styles after making changes to the tokens and make sure to test and commit the changes to the built styles.
1788
-
1789
- See the [Style Dictionary documentation](./docs/style-dictionary.md) for more details on how we structure and generate the styles.
134
+ ```bash
135
+ pnpm build-styled
136
+ ```
1790
137
 
1791
- ### Compatibility
138
+ See the [Style Dictionary documentation](https://guardian.github.io/stand/?path=/docs/style-dictionary--docs) for more details on how we structure and generate the styles.
1792
139
 
1793
- See the package.json `peerDependencies` section for compatible versions of React and other dependencies that Stand works with.
140
+ ## Compatibility
1794
141
 
1795
- Version sets for matrix testing live in `./scripts/deps-matrix-versions.json`:
142
+ See the `peerDependencies` in `package.json` for compatible versions of React and other dependencies.
1796
143
 
1797
- The test script `./scripts/test-deps-matrix.sh` reads this JSON file first, then applies any environment overrides you supply. Precedence is:
144
+ Version sets for matrix testing live in `./scripts/deps-matrix-versions.json`. The test script `./scripts/test-deps-matrix.sh` reads this file first, then applies any environment overrides. Precedence is:
1798
145
 
1799
146
  1. Explicit env var (e.g. `REACT_VERSIONS="18.0.0 19.0.0"`)
1800
147
  2. Value from `deps-matrix-versions.json`
1801
148
 
1802
149
  All three variables (`REACT_VERSIONS`, `EMOTION_VERSIONS`, `TS_VERSIONS`) must be defined after loading; otherwise the script exits with an error.
1803
150
 
1804
- Matrix generation in CI uses the same JSON file in the workflow: `../.github/workflows/stand-component-library-deps-matrix.yml` to ensure consistency.
151
+ Matrix generation in CI uses the same JSON file in the workflow `../.github/workflows/stand-component-library-deps-matrix.yml` to ensure consistency.
1805
152
 
1806
- #### Updating Supported Versions
153
+ ### Updating Supported Versions
1807
154
 
1808
- 1. Edit `./scripts/deps-matrix-versions.json` with new versions
155
+ 1. Edit `./scripts/deps-matrix-versions.json` with new versions.
1809
156
  2. Run the matrix test locally:
1810
- ```bash
1811
- ./scripts/test-deps-matrix.sh
1812
- ```
157
+ ```bash
158
+ ./scripts/test-deps-matrix.sh
159
+ ```
1813
160
  3. (Optional) Narrow the matrix with overrides:
1814
- ```bash
1815
- REACT_VERSIONS="18.0.0" EMOTION_VERSIONS="11.14.0" TS_VERSIONS="5.1" ./scripts/test-deps-matrix.sh
1816
- ```
1817
- 4. Review results (table output and any failures). Fix issues or adjust code
1818
- 5. Update `peerDependencies` in `package.json` to reflect the new minimum / tested range
1819
- 6. Open a PR, the CI pipeline will comment with the compatibility matrix
1820
-
1821
- #### Tips
1822
-
1823
- - Keep versions in ascending order for readability
1824
- - Remove deprecated versions only after confirming no downstream tool depends on them
1825
- - Add a new version first, then run the matrix, then adjust `peerDependencies` once green
1826
- - Changes to `peerDependencies` are always a breaking change to the library, as per our [recommendations](https://github.com/guardian/recommendations/blob/main/npm-packages.md#changes-to-peerdependencies-ranges-are-breaking)
161
+ ```bash
162
+ REACT_VERSIONS="18.0.0" EMOTION_VERSIONS="11.14.0" TS_VERSIONS="5.1" ./scripts/test-deps-matrix.sh
163
+ ```
164
+ 4. Review results (table output and any failures). Fix issues or adjust code.
165
+ 5. Update `peerDependencies` in `package.json` to reflect the new minimum/tested range.
166
+ 6. Open a PR, the CI pipeline will comment with the compatibility matrix.
167
+
168
+ ### Tips
169
+
170
+ - Keep versions in ascending order for readability.
171
+ - Remove deprecated versions only after confirming no downstream tool depends on them.
172
+ - Add a new version first, then run the matrix, then adjust `peerDependencies` once green.
173
+ - Changes to `peerDependencies` are always a breaking change, as per our [recommendations](https://github.com/guardian/recommendations/blob/main/npm-packages.md#changes-to-peerdependencies-ranges-are-breaking).
174
+
175
+ ## Documentation Site Map
176
+
177
+ - [Getting Started](https://guardian.github.io/stand/?path=/docs/getting-started--docs)
178
+ - [Contributing](https://guardian.github.io/stand/?path=/docs/contributing--docs)
179
+ - [Tools Design System Introduction](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs)
180
+ - [Editorial Components Introduction](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs)
181
+ - [Style Dictionary](https://guardian.github.io/stand/?path=/docs/style-dictionary--docs)
182
+ - [Architecture Decision Records](https://guardian.github.io/stand/?path=/docs/architecture-decision-records--docs)
183
+ - [Changelog](https://guardian.github.io/stand/?path=/docs/changelog--docs)