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