@workday/canvas-kit-mcp 14.1.1 → 14.1.4
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/dist/cli.js +39 -2
- package/dist/cli.js.map +2 -2
- package/dist/index.js +39 -2
- package/dist/index.js.map +2 -2
- package/dist/lib/llm-txt/llm-style-props-migration.txt +2346 -0
- package/dist/lib/llm-txt/llm-token-migration-14.txt +826 -0
- package/dist/lib/upgrade-guides/14.0-UPGRADE-GUIDE.md +1095 -0
- package/dist/types/lib/index.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,2346 @@
|
|
|
1
|
+
# Style Props Deprecation Overview
|
|
2
|
+
|
|
3
|
+
### Purpose
|
|
4
|
+
|
|
5
|
+
As part of the Canvas Kit’s modernization process, we’re moving away from Emotion’s runtime styling
|
|
6
|
+
and promoting a custom CSS-in-JS solution: `@workday/canvas-kit-styling`. This change improves
|
|
7
|
+
**performance**, **consistency**, and **maintainability** across our codebase. For more information,
|
|
8
|
+
view our [Future of](https://github.com/Workday/canvas-kit/discussions/2265) discussion.
|
|
9
|
+
|
|
10
|
+
### Goals
|
|
11
|
+
|
|
12
|
+
- **Reduce runtime overhead** by removing Emotion’s runtime from `@emotion/react`
|
|
13
|
+
- **Promote prescriptive, opinionated styling** across Workday
|
|
14
|
+
- **Enable static CSS compilation** for faster load times and smaller bundles
|
|
15
|
+
- **Support new design tokens and CSS Variables** for scalable theming
|
|
16
|
+
- **Ensure proper style merging** and stable selector behavior
|
|
17
|
+
- **Support advanced styling patterns** like compound styles, modifiers, and `data-parts`
|
|
18
|
+
|
|
19
|
+
> Emotion dynamically injects styles at runtime, causing costly re-renders and cache invalidations.
|
|
20
|
+
> The new system statically compiles styles at build time for optimal performance.
|
|
21
|
+
|
|
22
|
+
### Timeline
|
|
23
|
+
|
|
24
|
+
- **Deprecation introduced:** Canvas Kit **v14.1**
|
|
25
|
+
- **Removal:** _Not immediate_ — style props and `styled()` will continue to function in upcoming
|
|
26
|
+
releases
|
|
27
|
+
- **Migration timeline:** Gradual; no immediate codebase-wide update required
|
|
28
|
+
|
|
29
|
+
## LLM Assisted Migration <StorybookStatusIndicator type="ai" />
|
|
30
|
+
|
|
31
|
+
We've provided an **LLM migration mapping file** (`llm-style-props-migration.txt`) specifically
|
|
32
|
+
designed for use with LLM-based code assistants such as [Cursor](https://www.cursor.so/). It
|
|
33
|
+
contains a compiled LLM consumption version of this v14 Upgrade Guide. It is not intended for direct
|
|
34
|
+
human reference or team documentation, but rather as structured input for LLMs to automate and
|
|
35
|
+
assist with your migration process.
|
|
36
|
+
|
|
37
|
+
> **Important:** LLMs can make mistakes. Please verify changes using this Migration Guide.
|
|
38
|
+
|
|
39
|
+
**How to use:**
|
|
40
|
+
|
|
41
|
+
- **View raw file**: Open the file in a new tab to see the complete migration mapping
|
|
42
|
+
- **Download LLM File**: Save the file locally to upload or paste into your LLM/code assistant
|
|
43
|
+
- **Use with LLM**: Provide the raw content to your LLM/code assistant as context for automated
|
|
44
|
+
migration
|
|
45
|
+
|
|
46
|
+
<DownloadLLMFile
|
|
47
|
+
rawFileLink="https://raw.githubusercontent.com/Workday/canvas-kit/master/modules/docs/llm-txt/llm-style-props-migration.txt"
|
|
48
|
+
filename="llm-style-props-migration.txt"
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
## Changes Overview
|
|
52
|
+
|
|
53
|
+
### Replacements
|
|
54
|
+
|
|
55
|
+
Use the new **Canvas Kit Styling** utilities:
|
|
56
|
+
|
|
57
|
+
| Old API | New API | Purpose |
|
|
58
|
+
| -------------------------------------------------- | -------------------------------- | ------------------------------------------ |
|
|
59
|
+
| `styled()` | `createStyles` / `createStencil` | Define static or component-level styles |
|
|
60
|
+
| Inline style props, like `background` or `padding` | `cs` prop | Safely merge class names and styles |
|
|
61
|
+
| Dynamic values | `createVars` | Manage CSS variables for runtime overrides |
|
|
62
|
+
| Emotion modifiers | `modifiers`, `compound` | Define consistent appearance variants |
|
|
63
|
+
|
|
64
|
+
## Canvas Kit Styling
|
|
65
|
+
|
|
66
|
+
<InformationHighlight className="sb-unstyled" cs={{p: {marginBlock: 0}}}>
|
|
67
|
+
<InformationHighlight.Icon />
|
|
68
|
+
<InformationHighlight.Heading>Canvas Kit Styling Docs</InformationHighlight.Heading>
|
|
69
|
+
For a detailed overview of our styling approach, view our styling docs.
|
|
70
|
+
<InformationHighlight.Link href="https://workday.github.io/canvas-kit/?path=/docs/styling-getting-started-overview--docs">
|
|
71
|
+
Read more
|
|
72
|
+
</InformationHighlight.Link>
|
|
73
|
+
</InformationHighlight>
|
|
74
|
+
|
|
75
|
+
Canvas Kit’s styling utilities are built for **static CSS generation**, **token integration**, and
|
|
76
|
+
**predictable composition**.
|
|
77
|
+
|
|
78
|
+
### Core APIs
|
|
79
|
+
|
|
80
|
+
- **`createStyles`** — define reusable, static CSS objects.
|
|
81
|
+
- **`createStencil`** — define reusable, dynamic component styles with parts, vars, and modifiers
|
|
82
|
+
- **`cs` prop** — apply multiple styles and handle merges consistently to Canvas Kit components
|
|
83
|
+
|
|
84
|
+
### Best Practices
|
|
85
|
+
|
|
86
|
+
These best practices ensure your components remain **performant**, **consistent**, and
|
|
87
|
+
**maintainable** under the new Canvas Kit Styling system.
|
|
88
|
+
|
|
89
|
+
#### Define Styles Outside the Render Function
|
|
90
|
+
|
|
91
|
+
Always declare styles at the module level. Creating styles inside the render or component function
|
|
92
|
+
will trigger component re-render.
|
|
93
|
+
|
|
94
|
+
✅ **Do**
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// `createStyles` returns a string of className
|
|
98
|
+
const buttonStyles = createStyles({
|
|
99
|
+
backgroundColor: system.color.bg.primary.default,
|
|
100
|
+
color: system.color.text.inverse,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export const MyButton = () => <button className={buttonStyles}>Click me</button>;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
❌ **Don’t**
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
export const MyButton = () => {
|
|
110
|
+
const buttonStyles = createStyles({backgroundColor: 'red'}); // bad
|
|
111
|
+
return <button cs={buttonStyles}>Click me</button>;
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Use `createStyles` for Static Styling
|
|
116
|
+
|
|
117
|
+
Use `createStyles` for simple, reusable style objects that do **not** depend on dynamic data or
|
|
118
|
+
props.
|
|
119
|
+
|
|
120
|
+
✅ Ideal for:
|
|
121
|
+
|
|
122
|
+
- Defining base styles
|
|
123
|
+
- Applying static overrides
|
|
124
|
+
- Styling tokens-based components
|
|
125
|
+
|
|
126
|
+
`createStyles` returns a string of className that can be applied to a React element. If you're
|
|
127
|
+
applying the class to a Canvas Kit component, you can use the `cs` prop.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import {BaseButton} from '@workday/canvas-kit-react/button';
|
|
131
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
132
|
+
// `createStyles` returns a string of className
|
|
133
|
+
const buttonStyles = createStyles({
|
|
134
|
+
backgroundColor: system.color.bg.primary.default,
|
|
135
|
+
color: system.color.text.inverse,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const MyButton = () => <BaseButton cs={buttonStyles}>Click me</button>;
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Use `createStencil` for Dynamic or Complex Styling
|
|
142
|
+
|
|
143
|
+
Use `createStencil` when styles depend on **props**, **variants**, or **component parts**.
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
|
|
147
|
+
- Size or color variants (`primary`, `secondary`)
|
|
148
|
+
- Compound state combinations (`size=small`, `iconPosition=end`)
|
|
149
|
+
- Multi-part components (e.g. `Button`, `Card`, `MenuItem`)
|
|
150
|
+
|
|
151
|
+
✅ **Do**
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
const buttonStencil = createStencil({
|
|
155
|
+
vars: {color: '', background: ''},
|
|
156
|
+
base: ({color, backgroundColor}) => ({
|
|
157
|
+
color: cssVar(color, system.color.text.default),
|
|
158
|
+
backgroundColor: cssVar(backgroundColor, system.color.bg.default),
|
|
159
|
+
}),
|
|
160
|
+
modifiers: {
|
|
161
|
+
variant: {
|
|
162
|
+
primary: {background: system.color.bg.primary.default},
|
|
163
|
+
secondary: {background: system.color.bg.muted.default},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- **vars**: If you initialize the variable with an empty string, it will allow the variable to
|
|
170
|
+
cascade and be defined.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
const customButtonStencil = createStencil({
|
|
174
|
+
base: {
|
|
175
|
+
// Set the color variable to the primary color
|
|
176
|
+
[buttonStencil.vars.color]: system.color.fg.primary.default,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- **cssVar**: The `cssVar` function is used when you want to add a default value if the CSS Variable
|
|
182
|
+
is not defined.
|
|
183
|
+
- **modifiers**: The `modifiers` property is used to define the styles for the different variants of
|
|
184
|
+
the component.
|
|
185
|
+
|
|
186
|
+
#### Use `cs` Prop to Merge Styles
|
|
187
|
+
|
|
188
|
+
The `cs` prop merges `className` and `style` attributes safely and consistently. Use this over using
|
|
189
|
+
style props or className concatenation.
|
|
190
|
+
|
|
191
|
+
✅ **Do**
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<PrimaryButton cs={[baseStyles, variantStyles]} />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
❌ **Don’t**
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
<PrimaryButton className={`${baseStyles} ${variantStyles}`} />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Use Variables for Dynamic Values
|
|
204
|
+
|
|
205
|
+
Instead of inline styles or runtime calculations, use stencil variables.
|
|
206
|
+
|
|
207
|
+
✅ **Do**
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
const primaryButtonStencil = createStencil({
|
|
211
|
+
base: {
|
|
212
|
+
// Use the buttonStencil variable to set the background color
|
|
213
|
+
[buttonStencil.vars.background]: 'orange',
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
<PrimaryButton cs={primaryButtonStencil} />
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
❌ **Don’t**
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<PrimaryButton cs={{backgroundColor: 'orange'}} /> // breaks static optimization
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Extend Existing Stencils Instead of Overriding Styles
|
|
226
|
+
|
|
227
|
+
When modifying Canvas Kit components, extend the provided `Stencil` instead of creating your own
|
|
228
|
+
from scratch.
|
|
229
|
+
|
|
230
|
+
✅ **Do**
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
const customIconStencil = createStencil({
|
|
234
|
+
extends: systemIconStencil,
|
|
235
|
+
base: {
|
|
236
|
+
margin: system.space.x2,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This will inherit both the styles and variables from the `systemIconStencil`.
|
|
242
|
+
|
|
243
|
+
#### Use Modifiers for Variants and States
|
|
244
|
+
|
|
245
|
+
Define component variations (size, color, emphasis) using **modifiers** rather than conditional
|
|
246
|
+
logic.
|
|
247
|
+
|
|
248
|
+
✅ **Do**
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
const badgeStencil = createStencil({
|
|
252
|
+
modifiers: {
|
|
253
|
+
status: {
|
|
254
|
+
success: {background: system.color.bg.success.default},
|
|
255
|
+
error: {background: system.color.bg.negative.default},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Use Compound Modifiers for Complex Conditions
|
|
262
|
+
|
|
263
|
+
When two or more modifiers combine to produce a new style, define a **compound modifier**.
|
|
264
|
+
|
|
265
|
+
✅ **Do**
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
const myCustomStencil = createStencil({
|
|
269
|
+
base: {
|
|
270
|
+
//base styles
|
|
271
|
+
},
|
|
272
|
+
modifiers: {
|
|
273
|
+
variant: {
|
|
274
|
+
primary: {
|
|
275
|
+
// primary variant styles
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
size: {
|
|
279
|
+
large: {
|
|
280
|
+
// large size styles
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
compound: [
|
|
285
|
+
{
|
|
286
|
+
// apply styles when the variant is primary AND the size is large
|
|
287
|
+
modifiers: {variant: 'primary', size: 'large'},
|
|
288
|
+
styles: {paddingInline: system.space.x5},
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Avoid Nested Stencils Unless Necessary
|
|
295
|
+
|
|
296
|
+
Each Stencil should map to one semantic component. Nested stencils can increase CSS specificity and
|
|
297
|
+
complexity. Use **parts** instead of deep nesting.
|
|
298
|
+
|
|
299
|
+
✅ **Do**
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
const cardStencil = createStencil({
|
|
303
|
+
parts: {header: 'card-header', body: 'card-body'},
|
|
304
|
+
base: ({headerPart}) => ({
|
|
305
|
+
[headerPart]: {
|
|
306
|
+
fontWeight: 'bold',
|
|
307
|
+
},
|
|
308
|
+
}),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
<Card cs={cardStencil}>
|
|
312
|
+
<Card.Heading {...cardStencil.parts.header}>Card Title</Card.Heading>
|
|
313
|
+
<Card.Body {...cardStencil.parts.body}>Card Body</Card.Body>
|
|
314
|
+
</Card>;
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### Prefer Tokens and System Variables
|
|
318
|
+
|
|
319
|
+
Always use design tokens (`system`) for spacing, colors, typography, etc., instead of raw values.
|
|
320
|
+
View our System Tokens
|
|
321
|
+
[docs](https://workday.github.io/canvas-tokens/?path=/docs/docs-system-tokens-overview--docs) for
|
|
322
|
+
more information.
|
|
323
|
+
|
|
324
|
+
✅ **Do**
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
color: system.color.text.default;
|
|
328
|
+
margin: system.space.x2;
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
❌ **Don’t**
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
color: '#333';
|
|
335
|
+
margin: '8px';
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Debugging and Static Compilation
|
|
339
|
+
|
|
340
|
+
- Enable static compilation during development to catch type or value errors early.
|
|
341
|
+
- Use `as const` for static objects to ensure values are type-locked for the compiler.
|
|
342
|
+
|
|
343
|
+
✅ **Do**
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
const reusableStyles = {
|
|
347
|
+
position: 'absolute',
|
|
348
|
+
} as const;
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### Don’t Mix Emotion and Static Styling
|
|
352
|
+
|
|
353
|
+
Avoid combining Emotion’s `styled` or `css` with `createStyles` or `createStencil`. It reintroduces
|
|
354
|
+
runtime style recalculations and negates static benefits.
|
|
355
|
+
|
|
356
|
+
❌ **Don’t**
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
const StyledButton = styled(Button)(styles);
|
|
360
|
+
<StyledButton cs={createStyles({padding: 8})} />;
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Migration Example
|
|
364
|
+
|
|
365
|
+
### Style Props
|
|
366
|
+
|
|
367
|
+
#### Before
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import {Flex} from '@workday/canvas-kit-react/layout';
|
|
371
|
+
|
|
372
|
+
<Flex depth={1} marginX={10} background="frenchVanilla100" />;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
#### After
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import {Flex} from '@workday/canvas-kit-react/layout';
|
|
379
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
380
|
+
import {px2rem} from '@workday/canvas-kit-styling';
|
|
381
|
+
|
|
382
|
+
<Flex
|
|
383
|
+
cs={{
|
|
384
|
+
boxShadow: system.depth[1],
|
|
385
|
+
marginInline: px2rem(10),
|
|
386
|
+
background: system.color.bg.default,
|
|
387
|
+
}}
|
|
388
|
+
/>;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
- **px2rem**: The `px2rem` function is used to convert a pixel value to a rem value.
|
|
392
|
+
- Use [CSS logical
|
|
393
|
+
properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values
|
|
394
|
+
- Use `system` tokens over `base` tokens for better theming support.
|
|
395
|
+
|
|
396
|
+
### Emotion styled
|
|
397
|
+
|
|
398
|
+
#### Before (Emotion)
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
const StyledButton = styled('button')({
|
|
402
|
+
backgroundColor: 'blue',
|
|
403
|
+
color: 'white',
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### After (Canvas Kit Styling)
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
411
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
412
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
413
|
+
|
|
414
|
+
const primaryButtonStyles = createStyles({
|
|
415
|
+
backgroundColor: system.color.bg.primary.default,
|
|
416
|
+
color: system.color.text.inverse,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
<PrimaryButton cs={primaryButtonStyles}>Click me</PrimaryButton>;
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
# Canvas Kit Styling
|
|
423
|
+
|
|
424
|
+
## Introduction
|
|
425
|
+
|
|
426
|
+
Canvas Kit styling is a custom CSS-in-JS solution that provides both a runtime for development and a static parsing process for build time. This system offers several key benefits:
|
|
427
|
+
|
|
428
|
+
## Why Canvas Styling
|
|
429
|
+
|
|
430
|
+
This package contains everything needed to create CSS styling. This styling package contains a runtime for development and a static parsing process for build time.
|
|
431
|
+
|
|
432
|
+
Here are the goals for this project:
|
|
433
|
+
|
|
434
|
+
- TypeScript autocomplete of CSS object properties
|
|
435
|
+
- Low runtime for development
|
|
436
|
+
- Static CSS compilation for faster user experience
|
|
437
|
+
- Static CSS extraction for CSS only packages
|
|
438
|
+
- Dynamic styles using CSS Variables
|
|
439
|
+
|
|
440
|
+
If you're using Canvas Kit and not directly using this package, there is nothing extra to do on your end. The Canvas Kit packages are using the static compilation as part of the build process. If you want to use this package for your own styles, you don't need to do anything special to use in development. Included is a small runtime to get styling working. If you wish to statically compile your CSS from your TypeScript files for faster page loading, visit the [Getting Started](/docs/styling-getting-started--docs) page.
|
|
441
|
+
|
|
442
|
+
### Why?
|
|
443
|
+
|
|
444
|
+
Canvas Kit no longer needs to support IE11 which allows us to use [CSS Custom Properties a.k.a. CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). Dynamic style properties (properties that change during the lifecylce of the component) are the most costly in terms of performance in Emotion and should be avoided. Also, any conditionals in your Emotion style functions create unique hashes in the Emotion cache and makes that render frame pay an expensive [style recalculation](https://microsoftedge.github.io/DevTools/explainers/StyleTracing/explainer.html).
|
|
445
|
+
|
|
446
|
+
We can avoid most of the cost of Emotion's runtime by using [@emotion/css](https://www.npmjs.com/package/@emotion/css) instead and hoist all style definitions outside of a component's render function. All dynamic styling can be moved into "modifiers" (from [BEM](https://getbem.com/introduction/#modifer:~:text=%2C%20header%20title-,Modifier,-A%20flag%20on)).
|
|
447
|
+
|
|
448
|
+
There's still a runtime to select which styles should apply to an element and what the CSS Variable should be, but it is essentially only having to choose what CSS classes should apply to an element and changing the `style` property to set the CSS Variables. This does not require new [StyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/StyleSheet) inserts which cause expensive style recalculation. Think of the runtime as being the following:
|
|
449
|
+
|
|
450
|
+
```jsx
|
|
451
|
+
<div
|
|
452
|
+
className={getClassNames(/* input from props */)}
|
|
453
|
+
style={getCSSVarValues(/* input from props */)}
|
|
454
|
+
/>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
For further information, please read our GitHub discussion on [the future of styling](https://github.com/Workday/canvas-kit/discussions/2265)
|
|
458
|
+
|
|
459
|
+
### What is Canvas Kit Styling?
|
|
460
|
+
|
|
461
|
+
Canvas Kit Styling includes two things:
|
|
462
|
+
|
|
463
|
+
1. A utility function wrapper around `@emotion/css`
|
|
464
|
+
2. An optional static compiler to remove most of the runtime
|
|
465
|
+
|
|
466
|
+
#### Utility Functions
|
|
467
|
+
|
|
468
|
+
This packages provides three utility functions to make it easier to style element-based components. The following is a brief description of each function. If you'd like to read a more in-depth discussion of each, our [API docs](/docs/features-styling-api--create-styles).
|
|
469
|
+
|
|
470
|
+
The primary utility function is the `createStyles` function. It makes a call to the `css` function from `@emotion/css`. Emotion still does most of the heavy lifting by handling the serialization, hashing, caching, and style injection.
|
|
471
|
+
|
|
472
|
+
The other two utility functions, `createVars` and `createModifiers`, provide supplemental styling functionality. `createVars` allows you to create temporary CSS variables within components to create dynamic properties. And `createModifiers` creates a modifier function to create dynamic groups of properties. If you're familiar with modifiers in [BEM](https://getbem.com/introduction/) (Block, Element, Modifier) CSS, you're well on your way to understanding this function's intent.
|
|
473
|
+
|
|
474
|
+
#### Static Compiler
|
|
475
|
+
|
|
476
|
+
The static compiler run as a TypeScript transform during TypeScript's transpilation phase. It requires the TypeScript type checker to determine the static value of any variables used. The transformer calls `@emotion/serialize` to pre-compute the serialized styles and hash so that `@emotion/css` can skip these steps. For example, there's the before/after of the code.
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
// before
|
|
480
|
+
const myVars = createVars('textColor', 'backgroundColor');
|
|
481
|
+
|
|
482
|
+
const myStyles = createStyles({
|
|
483
|
+
fontSize: 12,
|
|
484
|
+
color: cssVar(myVars.textColor),
|
|
485
|
+
backgroundColor: cssVar(myVars.backgroundColor),
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// after
|
|
489
|
+
const myVars = createVars('textColor', 'backgroundColor');
|
|
490
|
+
|
|
491
|
+
const myStyles = createStyles({
|
|
492
|
+
name: 'a8g234',
|
|
493
|
+
styles:
|
|
494
|
+
'font-size: 12px; color: var(--css-my-vars-textColor); backgroundColor: var(--css-my-vars-backgroundColor);',
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Emotion has an internal shortcut that recognizes the `styles` property and [short-circuits interpolation](https://github.com/emotion-js/emotion/blob/f3b268f7c52103979402da919c9c0dd3f9e0e189/packages/serialize/src/index.js#L319).
|
|
499
|
+
|
|
500
|
+
### Performance
|
|
501
|
+
|
|
502
|
+
`createStyles` is more performant than `styled` components or the `css` prop because the styles must be statically evaluated. The actual characters of a CSS property value cannot change at runtime. This means we do not need to recalculate a hash every render or inject new `StyleSheet` entries into the `StyleSheetList` in a render cycle. Injecting new `StyleSheets` causes slow [Style Recalculation](https://web.dev/articles/reduce-the-scope-and-complexity-of-style-calculations). What is not well known is browser engines maintain an internal selector cache to make style recalculations as fast as possible. Adding a CSS class to a DOM element will invoke a style recalculation, but if the the CSS selector is already in the `StyleSheetList`, the browser can optimize how those styles are applied to the current element.
|
|
503
|
+
|
|
504
|
+
In the runtime Emotion's case, a novel style will result in a new style hash which results in a new `StyleSheet` being injected into the `StyleSheetList`. To be safe, the browser's runtime engine will throw away any style recalculation cache and start from scratch. This happens if you render a new component on the page that hasn't been rendered yet, or if you make one of your style properties dynamic between render cycles. Eventually the Emotion cache gets warm and style recalcuation costs start to normalize and no longer invalidate the browser's selector cache.
|
|
505
|
+
|
|
506
|
+
On a page with over 1,000 elements and over 1,000 [CSSRules](https://developer.mozilla.org/en-US/docs/Web/API/CSSRule), (typical of a large web application), the difference between a < 1ms for warmed selector cache and > 100ms for a fresh selector cache. `createStyles` encourages a pattern similar to [BEM](https://getbem.com/) which works well with the browser's selector cache by not injecting new `StyleSheet`s during a page's normal operations. All styles are injected before any rendering takes place.
|
|
507
|
+
|
|
508
|
+
> **Note:** Since style props force Emotion's dynamic rendering, style props will fall back to
|
|
509
|
+
> Emotion's runtime performance characteristics and lose any benefits gained. Also if you use
|
|
510
|
+
> `styled` components or the `css` prop in a tree that uses `createStyles`, the styles created by
|
|
511
|
+
> the runtime APIs will still result in a selector cache invalidation. Even if you want to use
|
|
512
|
+
> `styled` or the `css` prop, consider using CSS Variables for dynamic CSS property values to reduce
|
|
513
|
+
> the performance overhead of Emotion.
|
|
514
|
+
|
|
515
|
+
## Overview
|
|
516
|
+
|
|
517
|
+
The Canvas Kit styling system consists of two main packages:
|
|
518
|
+
|
|
519
|
+
- `@workday/canvas-kit-styling` - Core styling utilities for runtime use
|
|
520
|
+
- `@workday/canvas-kit-styling-transform` - Build-time optimization tools
|
|
521
|
+
|
|
522
|
+
These packages work together to provide a CSS-in-JS experience during development while enabling optimized static CSS in production.
|
|
523
|
+
|
|
524
|
+
## Installation
|
|
525
|
+
|
|
526
|
+
```sh
|
|
527
|
+
yarn add @workday/canvas-kit-styling
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Usage
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
import React from 'react';
|
|
534
|
+
import {createRoot} from 'react-dom/client';
|
|
535
|
+
|
|
536
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
537
|
+
|
|
538
|
+
const myStyles = createStyles({
|
|
539
|
+
backgroundColor: 'red',
|
|
540
|
+
}); // returns the CSS class name created for this style
|
|
541
|
+
|
|
542
|
+
myStyles; // something like "css-{hash}"
|
|
543
|
+
|
|
544
|
+
const domNode = document.getElementById('root');
|
|
545
|
+
const root = createRoot(domNode);
|
|
546
|
+
|
|
547
|
+
root.render(<div className={myStyles}>Hello!</div>);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Style Merging
|
|
551
|
+
|
|
552
|
+
The `@workday/canvas-kit-styling` package uses `@emotion/css` to inject styles during JavaScript evaluation time rather than `@emotion/react` or `@emotion/styled` injecting when a component is rendered. This means the Emotion cache needs to be known before any style is created. In order to properly merge styles with components using either dynamic styling package, the Emotion cache must be changed on any React application. Without this, styles will not be merged correctly when static and dynamic styles are used on the same element.
|
|
553
|
+
|
|
554
|
+
If you're using Canvas Kit React, you should use the `<CanvasProvider>` which includes Emotion's `<CacheProvider>` with the proper cache already set. If you're not using Canvas Kit React, you should use the `<CacheProvider>`:
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
// ONLY use if not using the <CanvasProvider>
|
|
558
|
+
import {getCache} from '@workday/canvas-kit-styling';
|
|
559
|
+
|
|
560
|
+
// in your application bootstrap
|
|
561
|
+
const root = React.createRoot(document.getElementById('root'));
|
|
562
|
+
|
|
563
|
+
root.render(
|
|
564
|
+
<CacheProvider value={getCache()}>
|
|
565
|
+
<App />
|
|
566
|
+
</CacheProvider>
|
|
567
|
+
);
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Known issues
|
|
571
|
+
|
|
572
|
+
### Hot Reloading
|
|
573
|
+
|
|
574
|
+
Style merging works by using CSS specificity rather than JavaScript runtime. This can cause problems during hot reloading. If you specify all styles in the same file, hot reloading shouldn't result in any style merging problems. But if you use `extends` in `createStencil` that references another file, you may run into style merge issues.
|
|
575
|
+
|
|
576
|
+
For example:
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
// base.tsx file
|
|
580
|
+
export const baseStencil = createStencil({
|
|
581
|
+
base: {
|
|
582
|
+
color: 'red',
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// extended.tsx file
|
|
587
|
+
import {baseStencil} from './base';
|
|
588
|
+
|
|
589
|
+
export const extendedStencil = createStencil({
|
|
590
|
+
extends: baseStencil,
|
|
591
|
+
base: {
|
|
592
|
+
color: 'blue',
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
This will render correctly until you change `color` in `base.tsx` and get a hot reload:
|
|
598
|
+
|
|
599
|
+
```tsx
|
|
600
|
+
// base.tsx file
|
|
601
|
+
export const baseStencil = createStencil({
|
|
602
|
+
base: {
|
|
603
|
+
color: 'purple',
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
The hot reload will evaluate this update and inject a new style.
|
|
609
|
+
|
|
610
|
+
## Development
|
|
611
|
+
|
|
612
|
+
Canvas Kit Styling comes with a runtime that doesn't need anything special for development. The runtime uses `@emotion/css` to include your styles on the page. If you plan to use static compilation, we recommend enabling in production as well so you can fix static compilation errors as you develop rather than get errors only in production builds.
|
|
613
|
+
|
|
614
|
+
## Static compilation
|
|
615
|
+
|
|
616
|
+
The `@workday/canvas-kit-styling-transform` package can to pre-build styles. This process takes style objects and turns them into CSS strings. This process moves serialization and hashing to build time rather than browser runtime when `@emotion/css` is processing styles. This will speed up production builds at runtime.
|
|
617
|
+
|
|
618
|
+
Static compilation has stricter requirements than when doing runtime styling. The static compiler uses the TypeScript type system to statically analyze style values and thus requires value types to be known by TypeScript. See [Restrictions](#restrictions).
|
|
619
|
+
|
|
620
|
+
Static compilation may be required for server side rendering (SSR), especially when using React Server Components.
|
|
621
|
+
|
|
622
|
+
### Hash generation
|
|
623
|
+
|
|
624
|
+
Emotion generates hashes based on the serialized style object. This means a style should always give the same hash. Static styling hashes differently. Every `createStyles` or `createStencil` call will generate a unique hash even if the style object is the same. This is required for proper style merging because static styling doesn't give single class names, but rather merges styles using CSS specificity.
|
|
625
|
+
|
|
626
|
+
For runtime development, the hash is always unique. For static compilation, the hash is based on the start and end character count in the source file of the style block. This is required for SSR so that the server and client agree on the same value during hydration. This means that while debugging, the hash depends on any code before it. If you add a `console.log` for example, the character index of a style block could shift which will generate a new hash.
|
|
627
|
+
|
|
628
|
+
### Restrictions
|
|
629
|
+
|
|
630
|
+
The static compiler uses the TypeScript type checker. The easiest way to think of these restrictions is if TypeScript knows the exact value, the static compiler will also know. A simple example:
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
// won't work - `value` is a type of `string` because `let` allows a value to be mutated
|
|
634
|
+
let value = 'absolute'; // `string`
|
|
635
|
+
|
|
636
|
+
const myStyles = createStyles({
|
|
637
|
+
position: value, // error - `string` isn't specific enough.
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// will work - `value` is a type of `'absolute'` because `const` restricts to a string literal
|
|
641
|
+
const value = 'absolute'; // `'absolute'`
|
|
642
|
+
|
|
643
|
+
const myStyles = createStyles({
|
|
644
|
+
position: value, // works. If you mouse over `value` in your editor, you'll see the type is `'absolute'`
|
|
645
|
+
});
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
More complex examples may be objects:
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
// won't work. TypeScript will not understand that the position will only be `'absolute'` and makes it a `string` instead
|
|
652
|
+
const reusableStyles = {
|
|
653
|
+
position: 'absolute',
|
|
654
|
+
}; // `{ position: string }`
|
|
655
|
+
|
|
656
|
+
const myStyles = createStyles({
|
|
657
|
+
...reusableStyles, // error - `position` is a `string` and not specific enough
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// will work. Adding `as const` tells TypeScript the object is readonly and therefore no values can change
|
|
661
|
+
const reusableStyles = {
|
|
662
|
+
position: 'absolute',
|
|
663
|
+
} as const; // `{ readonly position: 'absolute' }`
|
|
664
|
+
|
|
665
|
+
const myStyles = createStyles({
|
|
666
|
+
...reusableStyles, // works. If you mouse over, the position is a string literal `'absolute'`
|
|
667
|
+
});
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Functions are a little more tricky and may require generics.
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
// generic makes the type be statically knowable
|
|
674
|
+
function getPosition<V extends 'relative' | 'absolute'>(value: V): {position: V} {
|
|
675
|
+
return {position: value};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// mouse over `position` in your editor an the type will be `{ position: 'absolute' }`
|
|
679
|
+
const position = getPosition('absolute'); // { position: 'absolute' }
|
|
680
|
+
|
|
681
|
+
const myStyles = createStyles({
|
|
682
|
+
...getPosition('absolute'), // works - `{ position: 'absolute' }`
|
|
683
|
+
});
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Webpack
|
|
687
|
+
|
|
688
|
+
The `@webpack/canvas-kit-styling-transform` package comes with a webpack loader that can be added to development and/or production.
|
|
689
|
+
|
|
690
|
+
```js
|
|
691
|
+
// import the transform - CJS and ESM are supported
|
|
692
|
+
import {StylingWebpackPlugin} from '@workday/canvas-kit-styling-transform';
|
|
693
|
+
|
|
694
|
+
// somewhere only once. For static webpack config files, this can be near the top.
|
|
695
|
+
// If inside Storybook, Gatsby, Next.js, etc configs, put inside the function that is called that
|
|
696
|
+
// returns a webpack config
|
|
697
|
+
const tsPlugin = const tsPlugin = new StylingWebpackPlugin({
|
|
698
|
+
tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), // allows your TS config to be used
|
|
699
|
+
// A different tsconfig could be used if you want to use TS to transpile to something like ES2019 and
|
|
700
|
+
// also have Babel process the file.
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// However you need to define rules.
|
|
704
|
+
// This is different for using webpack directly or in Storybook/Gatsby/Next.js/etc
|
|
705
|
+
{
|
|
706
|
+
rules: [
|
|
707
|
+
//...
|
|
708
|
+
{
|
|
709
|
+
test: /.\.tsx?$/,
|
|
710
|
+
use: [
|
|
711
|
+
{
|
|
712
|
+
loader: require.resolve('@workday/canvas-kit-styling-transform/webpack-loader'),
|
|
713
|
+
options: tsPlugin.getLoaderOptions(),
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
enforce: 'pre'
|
|
717
|
+
},
|
|
718
|
+
];
|
|
719
|
+
// We need to pass the plugin to Webpack's plugin list. Failure to do this will result in a
|
|
720
|
+
// production build hanging
|
|
721
|
+
plugins: [tsPlugin]
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Core Styling Approaches for Static Styling
|
|
726
|
+
|
|
727
|
+
For proper static styling there's two methods that you can use to apply styles.
|
|
728
|
+
|
|
729
|
+
1. Using `createStyles` for simple object base styles.
|
|
730
|
+
2. Using `createStencil` for dynamic styles and reusable components.
|
|
731
|
+
|
|
732
|
+
Both approaches are intended to be used in tandem with the `cs` prop when applying styles to our components.
|
|
733
|
+
|
|
734
|
+
### `cs` Prop
|
|
735
|
+
|
|
736
|
+
The `cs` prop takes in a single, or an array of values that are created by the `cs` function, a string representing a CSS class name, or the return of the `createVars` function. It merges everything together and applies `className` and `style` attributes to a React element. Most of our components extend the `cs` prop so that you can statically apply styles to them.
|
|
737
|
+
|
|
738
|
+
> **Important**: While the `cs` prop accepts a style object, **this will not** be considered
|
|
739
|
+
> statically styling an element and you will lose the performance benefits. We plan on providing a
|
|
740
|
+
> babel plugin to extract these styles statically in a future version.
|
|
741
|
+
|
|
742
|
+
```tsx
|
|
743
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
744
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
745
|
+
|
|
746
|
+
const styles = createStyles({color: system.color.static.red.default});
|
|
747
|
+
|
|
748
|
+
function MyComponent() {
|
|
749
|
+
return <PrimaryButton cs={styles}>Text</PrimaryButton>;
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### `createStyles`
|
|
754
|
+
|
|
755
|
+
The primary utility function is the `createStyles` function. It makes a call to the `css` function from `@emotion/css`. Emotion still does most of the heavy lifting by handling the serialization, hashing, caching, and style injection.
|
|
756
|
+
|
|
757
|
+
In this example, the HTML will look like:
|
|
758
|
+
|
|
759
|
+
```html
|
|
760
|
+
<div class="css-m39zwu"></div>
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
The CSS will look like this:
|
|
764
|
+
|
|
765
|
+
```css
|
|
766
|
+
.css-m39zwu {
|
|
767
|
+
background: var(--cnvs-sys-color-bg-primary-default);
|
|
768
|
+
color: var(--cnvs-sys-color-text-inverse);
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
> Note:
|
|
773
|
+
> The `createStyles` function handles wrapping our Tokens in `var(--tokenName)`.
|
|
774
|
+
|
|
775
|
+
We're using `className` for simplicity here.
|
|
776
|
+
|
|
777
|
+
```ts
|
|
778
|
+
const styles = createStyles({
|
|
779
|
+
background: system.color.bg.primary.default,
|
|
780
|
+
color: system.color.text.inverse,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
export const CreateStyles = () => {
|
|
784
|
+
return <button className={styles}>Click Me</button>;
|
|
785
|
+
};
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
> Caution: Performance Hit
|
|
789
|
+
> Do not inline the call to `createStyles` in the render function of a component.
|
|
790
|
+
> This will cause performance issues as a new style is inserted into the browser on every render.
|
|
791
|
+
|
|
792
|
+
```tsx
|
|
793
|
+
// Bad example (inside render function)
|
|
794
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
795
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
796
|
+
|
|
797
|
+
function MyComponent() {
|
|
798
|
+
const styles = createStyles({color: system.color.static.red.default}); // Don't do this
|
|
799
|
+
return <PrimaryButton className={createStyles({color: system.color.static.red.default})}>Text</PrimaryButton>;
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
#### When to Use `createStyles`
|
|
804
|
+
|
|
805
|
+
`createStyles` is a great way to generate static styles when styling our components that don't rely on dynamic styles. Use `createStyles` if you want to create re useable styles or need to apply simple style overrides to our components.
|
|
806
|
+
|
|
807
|
+
#### When to Use Something Else
|
|
808
|
+
|
|
809
|
+
You should use [stencils](/docs/styling-getting-started-stencils--docs) when styling our components that have complex styles and dynamic properties.
|
|
810
|
+
|
|
811
|
+
#### Proper Usage
|
|
812
|
+
|
|
813
|
+
```tsx
|
|
814
|
+
// Bad example (inside render function)
|
|
815
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
816
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
817
|
+
|
|
818
|
+
function MyComponent() {
|
|
819
|
+
const styles = createStyles({color: system.color.static.red.default}); // Don't do this
|
|
820
|
+
return <PrimaryButton cs={styles}>Text</PrimaryButton>;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Good example (outside render function)
|
|
824
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
825
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
826
|
+
|
|
827
|
+
const styles = createStyles({color: system.color.static.red.default});
|
|
828
|
+
|
|
829
|
+
function MyComponent() {
|
|
830
|
+
return <PrimaryButton cs={styles}>Text</PrimaryButton>;
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
> Note:
|
|
835
|
+
> Most of our components support using the `cs` prop to apply the static styles.
|
|
836
|
+
> It merges everything together and applies `className` and `style` attributes to a React element
|
|
837
|
+
|
|
838
|
+
#### Performance Benefits
|
|
839
|
+
|
|
840
|
+
`createStyles` is performant because:
|
|
841
|
+
|
|
842
|
+
- Styles are statically evaluated when styles are defined outside the render function
|
|
843
|
+
- No new StyleSheets are injected during render
|
|
844
|
+
- It works well with the browser's selector cache
|
|
845
|
+
|
|
846
|
+
### `createStencil`
|
|
847
|
+
|
|
848
|
+
`createStencil` a reusable function that returns `style` and `className` props in an object. A Stencil should apply to a single element. If your component has nested elements, you can youse `parts` to target those elements in the Stencil. If your component is a compound component, a stencil should be created for each subcomponent. If your component is a config component, a stencil can have nested styles.
|
|
849
|
+
|
|
850
|
+
We created Stencils as the reusable primitive of components. Stencils provide:
|
|
851
|
+
|
|
852
|
+
- `vars`: CSS variables for dynamic properties
|
|
853
|
+
- `base`: base styles to any component
|
|
854
|
+
- `modifier`: modifiers like “size = small,medium,large” or “color=red,blue,etc”
|
|
855
|
+
- `parts`: matching sub-elements that are part of a component
|
|
856
|
+
- `compound`: compound modifiers - styles that match multiple modifiers
|
|
857
|
+
|
|
858
|
+
#### Basic Example
|
|
859
|
+
|
|
860
|
+
In the example below, Stencils allow you to dynamically style elements or components based on properties.
|
|
861
|
+
|
|
862
|
+
```tsx
|
|
863
|
+
const themedCardStencil = createStencil({
|
|
864
|
+
vars: {
|
|
865
|
+
// Create CSS variables for the color of the header
|
|
866
|
+
headerColor: '',
|
|
867
|
+
},
|
|
868
|
+
parts: {
|
|
869
|
+
// Allows for styling a sub element of the component that may not be exposed through the API
|
|
870
|
+
header: 'themed-card-header',
|
|
871
|
+
body: 'themed-card-body',
|
|
872
|
+
},
|
|
873
|
+
base: ({headerPart, headerColor}) => ({
|
|
874
|
+
padding: system.space.x4,
|
|
875
|
+
boxShadow: system.depth[2],
|
|
876
|
+
backgroundColor: system.color.bg.default,
|
|
877
|
+
color: system.color.text.default,
|
|
878
|
+
// Targets the header part via [data-part="themed-card-header"]"]
|
|
879
|
+
[headerPart]: {
|
|
880
|
+
color: headerColor,
|
|
881
|
+
},
|
|
882
|
+
}),
|
|
883
|
+
modifiers: {
|
|
884
|
+
isDarkTheme: {
|
|
885
|
+
// If the prop `isDarkTheme` is true, style the component and it's parts
|
|
886
|
+
true: ({headerPart, bodyPart}) => ({
|
|
887
|
+
backgroundColor: system.color.bg.contrast.default,
|
|
888
|
+
color: system.color.text.inverse,
|
|
889
|
+
[`${headerPart}, ${bodyPart}`]: {
|
|
890
|
+
color: system.color.text.inverse,
|
|
891
|
+
},
|
|
892
|
+
}),
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
export const CreateStencil = ({isDarkTheme, headerColor, elemProps}) => {
|
|
898
|
+
const [darkTheme, setIsDarkTheme] = React.useState(false);
|
|
899
|
+
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
900
|
+
setIsDarkTheme(event.target.checked);
|
|
901
|
+
};
|
|
902
|
+
return (
|
|
903
|
+
<div>
|
|
904
|
+
<FormField>
|
|
905
|
+
<FormField.Label>Toggle Dark Theme</FormField.Label>
|
|
906
|
+
<FormField.Input as={Switch} onChange={handleChange} checked={darkTheme} />
|
|
907
|
+
</FormField>
|
|
908
|
+
|
|
909
|
+
<Card cs={themedCardStencil({isDarkTheme: darkTheme, headerColor})} {...elemProps}>
|
|
910
|
+
<Card.Heading {...themedCardStencil.parts.header}>Canvas Supreme</Card.Heading>
|
|
911
|
+
<Card.Body {...themedCardStencil.parts.body}>
|
|
912
|
+
Our house special supreme pizza includes pepperoni, sausage, bell peppers, mushrooms,
|
|
913
|
+
onions, and oregano.
|
|
914
|
+
</Card.Body>
|
|
915
|
+
</Card>
|
|
916
|
+
</div>
|
|
917
|
+
);
|
|
918
|
+
};
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
#### When to Use `createStencil`
|
|
922
|
+
|
|
923
|
+
- When you're styling parts of a component that rely on dynamic properties.
|
|
924
|
+
- When you want to create a reusable component with dynamic styles.
|
|
925
|
+
|
|
926
|
+
Use a Stencil when building reusable components that have dynamic styles and properties.
|
|
927
|
+
|
|
928
|
+
#### Concepts
|
|
929
|
+
|
|
930
|
+
**Base styles**
|
|
931
|
+
|
|
932
|
+
Base styles are always applied to a Stencil. All your default styles should go here. Base styles support psuedo selectors like `:focus-visible` or `:hover` as well as child selectors. Any selector supported by `@emotion/css` is valid here. All styles must be static and statically analyzable by the tranformer. If you need dynamic styling, look at Variables and Modifiers.
|
|
933
|
+
|
|
934
|
+
**Variables**
|
|
935
|
+
|
|
936
|
+
Variables allow some properties to be dynamic. They work by creating [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) with unique names and are applied using the [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) property of an element to locally scope an override. Since we don't have access to those names, we need a function wrapper around our style objects. This includes `base`, `modifiers`, and `compound` modifiers.
|
|
937
|
+
|
|
938
|
+
Here's a simplified example:
|
|
939
|
+
|
|
940
|
+
```tsx
|
|
941
|
+
const myStencil = createStencil({
|
|
942
|
+
vars: {
|
|
943
|
+
defaultColor: 'red' // default value
|
|
944
|
+
nonDefaultedColor: '', // will allow for uninitialization
|
|
945
|
+
},
|
|
946
|
+
base: ({defaultColor}) => {
|
|
947
|
+
color: defaultColor // `defaultColor` is '--defaultColor-abc123', not 'red'
|
|
948
|
+
}
|
|
949
|
+
})
|
|
950
|
+
|
|
951
|
+
const elemProps = myStencil({color: 'blue'}) // {style: {'--defaultColor-abc123': 'blue'}}
|
|
952
|
+
|
|
953
|
+
<div {...elemProps} />
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
This will produce the following HTML:
|
|
957
|
+
|
|
958
|
+
```html
|
|
959
|
+
<style>
|
|
960
|
+
.css-abc123 {
|
|
961
|
+
--defaultColor-abc123: red;
|
|
962
|
+
color: var(--defaultColor-abc123);
|
|
963
|
+
}
|
|
964
|
+
</style>
|
|
965
|
+
<div class="css-123abc" style="--defaultColor-abc123: blue;"></div>
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
The element will have a `color` property of `'blue'` because the element style is the highest specificity and wins over a local class name. In the "Styles" tab of developer tools, it will look like the following:
|
|
969
|
+
|
|
970
|
+
```
|
|
971
|
+
element.style {
|
|
972
|
+
--defaultColor-abc123: blue;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.css-abc123 {
|
|
976
|
+
--defaultColor-abc123: red;
|
|
977
|
+
color: var(--defaultColor-abc123); // blue
|
|
978
|
+
}
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
Variables are automatically added to the config of a Stencil. They share the same namespace as modifiers, so **do not have a modifier with the same name as a variable**.
|
|
982
|
+
|
|
983
|
+
> Note:
|
|
984
|
+
> Variables should be used sparingly. Style properties can be easily overridden without variables.
|
|
985
|
+
> Variables are useful if you want to expose changing properties regardless of selectors.
|
|
986
|
+
> For example, Buttons use variables for colors of all states (hover, active, focus, disabled, and nested icons).
|
|
987
|
+
> Without variables, overriding the focus color would require deeply nested selector overrides.
|
|
988
|
+
|
|
989
|
+
**Cascading Variables**
|
|
990
|
+
|
|
991
|
+
Notice the `nonDefaultedColor` is not included in the base styles like `defaultColor` was. If a variable has an empty string, it will can be uninitialized. Stencil variables with a default value will create a "cascade barrier". A cascade barrier prevents the variable from "leaking" into the component. For example, if a `Card` component was rendered within another `Card` component, the variables from the parent `Card` would not leak into the child `Card` component. But there are times where a component expects a parent component to set a CSS variable and that it should cascade to the component. An example of this is the relationship between `SystemIcon` and `Button`. The `Button` components set the `SystemIcon` variables and they should cascade into the `SystemIcon` component.
|
|
992
|
+
|
|
993
|
+
> Note:
|
|
994
|
+
> Non-cascade variables _could_ be initialized. If you use uninitialized variables, be sure to use a fallback in your styles.
|
|
995
|
+
|
|
996
|
+
```tsx
|
|
997
|
+
const myStencil = createStencil({
|
|
998
|
+
vars: {
|
|
999
|
+
color: '', // uninitialized
|
|
1000
|
+
},
|
|
1001
|
+
base({color}) {
|
|
1002
|
+
return {
|
|
1003
|
+
// provide a fallback. A uninitialized CSS variable will fall back to `initial`.
|
|
1004
|
+
// for the `color` CSS property, that's most likely black (default text color)
|
|
1005
|
+
color: cssVar(color, 'red'),
|
|
1006
|
+
};
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
**Nested Variables**
|
|
1012
|
+
|
|
1013
|
+
Variables can be nested one level. This can be useful for colors with different psuedo selectors like `:hover` or `:focus`. Here's an example:
|
|
1014
|
+
|
|
1015
|
+
```tsx
|
|
1016
|
+
const myStencil = createStencil({
|
|
1017
|
+
vars: {
|
|
1018
|
+
default: {
|
|
1019
|
+
color: 'red'
|
|
1020
|
+
},
|
|
1021
|
+
hover: {
|
|
1022
|
+
color: 'blue'
|
|
1023
|
+
},
|
|
1024
|
+
focus: {
|
|
1025
|
+
color: 'orange'
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
base: ({default, hover, focus}) => {
|
|
1029
|
+
color: default.color,
|
|
1030
|
+
'&:hover': {
|
|
1031
|
+
color: hover.color
|
|
1032
|
+
},
|
|
1033
|
+
'&:focus': {
|
|
1034
|
+
color: focus.color
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
})
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
**Modifiers**
|
|
1041
|
+
|
|
1042
|
+
Modifiers are modifications to base styles. It should be used to change the appearance of a base style. For example, a button may have a modifier for "primary" or "secondary" which may change the visual emphasis of the button. Each modifier has its own CSS class name and the stencil will return the correct CSS classes to apply to an element based on what modifiers are active.
|
|
1043
|
+
|
|
1044
|
+
```tsx
|
|
1045
|
+
const buttonStencil = createStencil({
|
|
1046
|
+
base: {
|
|
1047
|
+
padding: 5
|
|
1048
|
+
// base styles
|
|
1049
|
+
},
|
|
1050
|
+
modifiers: {
|
|
1051
|
+
variant: { // modifier name
|
|
1052
|
+
primary: {
|
|
1053
|
+
background: 'blue'
|
|
1054
|
+
},
|
|
1055
|
+
secondary: {
|
|
1056
|
+
background: 'gray'
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
defaultModifiers: {
|
|
1061
|
+
variant: 'secondary'
|
|
1062
|
+
}
|
|
1063
|
+
})
|
|
1064
|
+
|
|
1065
|
+
const elemProps = myStencil({variant: 'primary'}) // {className: "css-a0 css-a1"}
|
|
1066
|
+
|
|
1067
|
+
<div {...elemProps} />
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
The HTML may look something like this:
|
|
1071
|
+
|
|
1072
|
+
```html
|
|
1073
|
+
<style>
|
|
1074
|
+
.css-a0 {
|
|
1075
|
+
padding: 5px;
|
|
1076
|
+
}
|
|
1077
|
+
.css-a1 {
|
|
1078
|
+
background: 'blue';
|
|
1079
|
+
}
|
|
1080
|
+
.css-a2 {
|
|
1081
|
+
background: 'gray';
|
|
1082
|
+
}
|
|
1083
|
+
</style>
|
|
1084
|
+
<div class="css-a0 css-a1"></div>
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
The optional `defaultModifiers` config property will default modifiers to a value. If a modifier is not passed to the stencil, the default will be used.
|
|
1088
|
+
|
|
1089
|
+
```tsx
|
|
1090
|
+
myStencil(); // className will be `'css-a0 css-a2'`
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
**Compound Modifiers**
|
|
1094
|
+
|
|
1095
|
+
A compound modifier creates a new CSS class for the intersection of two or more modifiers. Each modifier can have its own separate CSS class while the intersection is a different CSS class.
|
|
1096
|
+
|
|
1097
|
+
For example:
|
|
1098
|
+
|
|
1099
|
+
```tsx
|
|
1100
|
+
const buttonStencil = createStencil({
|
|
1101
|
+
base: {
|
|
1102
|
+
padding: 10,
|
|
1103
|
+
// base styles
|
|
1104
|
+
},
|
|
1105
|
+
modifiers: {
|
|
1106
|
+
size: {
|
|
1107
|
+
// modifier name
|
|
1108
|
+
large: {
|
|
1109
|
+
padding: 20,
|
|
1110
|
+
},
|
|
1111
|
+
small: {
|
|
1112
|
+
padding: 5,
|
|
1113
|
+
},
|
|
1114
|
+
},
|
|
1115
|
+
iconPosition: {
|
|
1116
|
+
start: {
|
|
1117
|
+
paddingInlineStart: 5,
|
|
1118
|
+
},
|
|
1119
|
+
end: {
|
|
1120
|
+
paddingInlineEnd: 5,
|
|
1121
|
+
},
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
compound: [
|
|
1125
|
+
{
|
|
1126
|
+
modifiers: {size: 'large', position: 'start'},
|
|
1127
|
+
styles: {
|
|
1128
|
+
paddingInlineStart: 15,
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
modifiers: {size: 'small', position: 'end'},
|
|
1133
|
+
styles: {
|
|
1134
|
+
paddingInlineEnd: 0,
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
1137
|
+
],
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
<div {...buttonStencil()} />
|
|
1141
|
+
<div {...buttonStencil({size: 'small'})} />
|
|
1142
|
+
<div {...buttonStencil({size: 'small', iconPosition: 'end'})} />
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
The HTML will look something like this:
|
|
1146
|
+
|
|
1147
|
+
```html
|
|
1148
|
+
<style>
|
|
1149
|
+
.a0 {
|
|
1150
|
+
padding: 10px;
|
|
1151
|
+
}
|
|
1152
|
+
.a1 {
|
|
1153
|
+
padding: 20px;
|
|
1154
|
+
}
|
|
1155
|
+
.a2 {
|
|
1156
|
+
padding: 5px;
|
|
1157
|
+
}
|
|
1158
|
+
.a3 {
|
|
1159
|
+
padding-inline-start: 5px;
|
|
1160
|
+
}
|
|
1161
|
+
.a4 {
|
|
1162
|
+
padding-inline-end: 5px;
|
|
1163
|
+
}
|
|
1164
|
+
.a5 {
|
|
1165
|
+
padding-inline-start: 15px;
|
|
1166
|
+
}
|
|
1167
|
+
.a6 {
|
|
1168
|
+
padding-inline-start: 0px;
|
|
1169
|
+
}
|
|
1170
|
+
</style>
|
|
1171
|
+
<div class="a0"></div>
|
|
1172
|
+
<div class="a0 a2"></div>
|
|
1173
|
+
<div class="a0 a2 a4 a6"></div>
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
Notice the stencil adds all the class names that match the base, modifiers, and compound modifiers.
|
|
1177
|
+
|
|
1178
|
+
**Variables and Modifiers with same keys**
|
|
1179
|
+
|
|
1180
|
+
It is possible to have a variable and modifier sharing the same key. The Stencil will accept either the modifier option or a string. The value will be sent as a variable regardless while the modifer will only match if it is a valid modifer key.
|
|
1181
|
+
|
|
1182
|
+
```tsx
|
|
1183
|
+
const buttonStencil = createStencil({
|
|
1184
|
+
vars: {
|
|
1185
|
+
width: '10px',
|
|
1186
|
+
},
|
|
1187
|
+
base({width}) {
|
|
1188
|
+
return {
|
|
1189
|
+
width: width,
|
|
1190
|
+
};
|
|
1191
|
+
},
|
|
1192
|
+
modifiers: {
|
|
1193
|
+
width: {
|
|
1194
|
+
zero: {
|
|
1195
|
+
width: '0', // overrides base styles
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
},
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
// `'zero'` is part of autocomplete
|
|
1202
|
+
myStencil({width: 'zero'});
|
|
1203
|
+
// returns {className: 'css-button css-button--width-zero', styles: { '--button-width': 'zero'}}
|
|
1204
|
+
|
|
1205
|
+
// width also accepts a string
|
|
1206
|
+
myStencil({width: '10px'});
|
|
1207
|
+
// returns {className: 'css-button', styles: { '--button-width': '10px'}}
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
#### Styling Elements via Component Parts
|
|
1211
|
+
|
|
1212
|
+
The goal of compound components is to expose one component per semantic element. Most of the time this means a 1:1 relationship of a component and DOM element. Sometimes a semantic element contains non-semantic elements for styling. An example might be a `<button>` with a icon for visual reinforcement, and a label for a semantic label. The semantic element is the `<button>` while the icon has no semantic value and the label automatically provides the semantic button with an accessible name. In order to style the icon and label elements, you have to know the DOM structure to target those specific elements in order to style it.
|
|
1213
|
+
|
|
1214
|
+
```jsx
|
|
1215
|
+
import {createStencil} from '@workday/canvas-kit-styling';
|
|
1216
|
+
|
|
1217
|
+
const myButtonStencil = createStencil({
|
|
1218
|
+
base: {
|
|
1219
|
+
background: 'transparent',
|
|
1220
|
+
i: {
|
|
1221
|
+
// ...icon styles
|
|
1222
|
+
},
|
|
1223
|
+
span: {
|
|
1224
|
+
// ...label styles
|
|
1225
|
+
},
|
|
1226
|
+
':hover': {
|
|
1227
|
+
// ...hover button styles
|
|
1228
|
+
i: {
|
|
1229
|
+
// ...hover icon styles
|
|
1230
|
+
},
|
|
1231
|
+
span: {
|
|
1232
|
+
// ...hover label styles
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
},
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
const MyButton = ({children, ...elemProps}) => {
|
|
1239
|
+
return (
|
|
1240
|
+
<button {...handleCsProp(elemProps, myButtonStencil())}>
|
|
1241
|
+
<i />
|
|
1242
|
+
<span>{children}</span>
|
|
1243
|
+
</button>
|
|
1244
|
+
);
|
|
1245
|
+
};
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
**Using Component Parts to Style Elements**
|
|
1249
|
+
|
|
1250
|
+
To style elements in the render function, we'll need to choose what elements to add the parts to. In the example below, we're able to spread the parts directly to elements. The Stencil will generate the type and value most appropriate for the context the part is used. In the Stencil, the part is represented by a string that looks like `[data-part="{partValue}"]` and in the render function, it is an object that looks like `{'data-part': partValue}`.
|
|
1251
|
+
|
|
1252
|
+
```jsx
|
|
1253
|
+
import {createStencil, handleCsProp} from '@workday/canvas-kit-styling';
|
|
1254
|
+
|
|
1255
|
+
const myButtonStencil = createStencil({
|
|
1256
|
+
parts: {
|
|
1257
|
+
icon: 'my-button-icon',
|
|
1258
|
+
label: 'my-button-label',
|
|
1259
|
+
},
|
|
1260
|
+
base: ({iconPart, labelPart}) => ({
|
|
1261
|
+
background: 'transparent',
|
|
1262
|
+
[iconPart]: {
|
|
1263
|
+
// `[data-part="my-button-icon"]`
|
|
1264
|
+
// ...icon styles
|
|
1265
|
+
},
|
|
1266
|
+
[labelPart]: {
|
|
1267
|
+
// `[data-part="my-button-label"]`
|
|
1268
|
+
// ...label styles
|
|
1269
|
+
},
|
|
1270
|
+
'&:hover': {
|
|
1271
|
+
// ...hover styles for button element
|
|
1272
|
+
[iconPart]: {
|
|
1273
|
+
// ...hover styles for icon part
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
}),
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
const MyButton = ({children, ...elemProps}) => {
|
|
1280
|
+
return (
|
|
1281
|
+
<button {...handleCsProp(elemProps, myButtonStencil())}>
|
|
1282
|
+
<i {...myButtonStencil.parts.icon} /> {/* data-part={my-button-icon} */}
|
|
1283
|
+
<span {...myButtonStencil.parts.label}>{children}</span> {/* data-part={my-button-label} */}
|
|
1284
|
+
</button>
|
|
1285
|
+
);
|
|
1286
|
+
};
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
As a reusable component, you can use component parts to style elements that are not exposed in the API. Consumers can also use the type safe Stencil to target that element to style it as well. As a general rule, a Stencil maps to a component. Multiple Stencils per component usually means nested elements that are not targets for style overrides.
|
|
1290
|
+
|
|
1291
|
+
> Note
|
|
1292
|
+
> While component parts are a way to give access to elements in order to style, they should be used sparingly.
|
|
1293
|
+
> Using component parts increases CSS specificity.
|
|
1294
|
+
> A component part should not be used on a nested component that has its own Stencil. The result will be any style properties defined with a component part will have a higher specificity than other styles.
|
|
1295
|
+
|
|
1296
|
+
## Converting from @emotion/styled
|
|
1297
|
+
|
|
1298
|
+
The most difficult part of understanding styling without Emotion's runtime is the mindset shift. You are using CSS to merge properties instead of JavaScript. This is essential to remove the runtime of Emotion. We'll use a contrived button example using `@emotion/styled` and our styling solution to step through the differences.
|
|
1299
|
+
|
|
1300
|
+
### Button using `@emotion/styled`
|
|
1301
|
+
|
|
1302
|
+
```tsx
|
|
1303
|
+
import React from 'react';
|
|
1304
|
+
import styled from '@emotion/styled';
|
|
1305
|
+
|
|
1306
|
+
interface ButtonProps {
|
|
1307
|
+
variant: 'primary' | 'secondary' | 'danger';
|
|
1308
|
+
size: 'large' | 'medium' | 'small';
|
|
1309
|
+
backgroundColor?: string;
|
|
1310
|
+
children?: React.ReactNode;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const StyledButton = styled('button')<ButtonProps>(
|
|
1314
|
+
{
|
|
1315
|
+
// base styles
|
|
1316
|
+
fontSize: '1rem',
|
|
1317
|
+
display: 'flex',
|
|
1318
|
+
borderRadius: '1rem',
|
|
1319
|
+
},
|
|
1320
|
+
// variant styles
|
|
1321
|
+
({variant, backgroundColor}) => {
|
|
1322
|
+
switch (variant) {
|
|
1323
|
+
case 'primary':
|
|
1324
|
+
return {
|
|
1325
|
+
background: backgroundColor || 'blue',
|
|
1326
|
+
color: 'white',
|
|
1327
|
+
};
|
|
1328
|
+
case 'secondary':
|
|
1329
|
+
return {
|
|
1330
|
+
background: backgroundColor || 'gray',
|
|
1331
|
+
};
|
|
1332
|
+
case 'danger':
|
|
1333
|
+
return {
|
|
1334
|
+
background: backgroundColor || 'red',
|
|
1335
|
+
};
|
|
1336
|
+
default:
|
|
1337
|
+
return {};
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
// size styles
|
|
1341
|
+
({size}) => {
|
|
1342
|
+
switch (size) {
|
|
1343
|
+
case 'large':
|
|
1344
|
+
return {
|
|
1345
|
+
fontSize: '1.4rem',
|
|
1346
|
+
height: '2rem',
|
|
1347
|
+
};
|
|
1348
|
+
case 'medium':
|
|
1349
|
+
return {
|
|
1350
|
+
fontSize: '1rem',
|
|
1351
|
+
height: '1.5rem',
|
|
1352
|
+
};
|
|
1353
|
+
case 'small':
|
|
1354
|
+
return {
|
|
1355
|
+
fontSize: '0.8rem',
|
|
1356
|
+
height: '1.2rem',
|
|
1357
|
+
};
|
|
1358
|
+
default:
|
|
1359
|
+
return {};
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
export const EmotionButton = () => {
|
|
1365
|
+
return (
|
|
1366
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
|
|
1367
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1368
|
+
<StyledButton variant="primary" size="large">
|
|
1369
|
+
Primary Large
|
|
1370
|
+
</StyledButton>
|
|
1371
|
+
<StyledButton variant="primary" size="medium">
|
|
1372
|
+
Primary Medium
|
|
1373
|
+
</StyledButton>
|
|
1374
|
+
<StyledButton variant="primary" size="small">
|
|
1375
|
+
Primary Small
|
|
1376
|
+
</StyledButton>
|
|
1377
|
+
</div>
|
|
1378
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1379
|
+
<StyledButton variant="secondary" size="large">
|
|
1380
|
+
Secondary Large
|
|
1381
|
+
</StyledButton>
|
|
1382
|
+
<StyledButton variant="secondary" size="medium">
|
|
1383
|
+
Secondary Medium
|
|
1384
|
+
</StyledButton>
|
|
1385
|
+
<StyledButton variant="secondary" size="small">
|
|
1386
|
+
Secondary Small
|
|
1387
|
+
</StyledButton>
|
|
1388
|
+
</div>
|
|
1389
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1390
|
+
<StyledButton variant="danger" size="large">
|
|
1391
|
+
Danger Large
|
|
1392
|
+
</StyledButton>
|
|
1393
|
+
<StyledButton variant="danger" size="medium">
|
|
1394
|
+
Danger Medium
|
|
1395
|
+
</StyledButton>
|
|
1396
|
+
<StyledButton variant="danger" size="small">
|
|
1397
|
+
Danger Small
|
|
1398
|
+
</StyledButton>
|
|
1399
|
+
</div>
|
|
1400
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1401
|
+
<StyledButton variant="danger" size="large" backgroundColor="orange">
|
|
1402
|
+
Custom Large
|
|
1403
|
+
</StyledButton>
|
|
1404
|
+
<StyledButton variant="danger" size="medium" backgroundColor="orange">
|
|
1405
|
+
Custom Medium
|
|
1406
|
+
</StyledButton>
|
|
1407
|
+
<StyledButton variant="danger" size="small" backgroundColor="orange">
|
|
1408
|
+
Custom Small
|
|
1409
|
+
</StyledButton>
|
|
1410
|
+
</div>
|
|
1411
|
+
</div>
|
|
1412
|
+
);
|
|
1413
|
+
};
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1416
|
+
If we inspect each button, we'll notice each has a different class name. They all look like `css-{hash}`:
|
|
1417
|
+
|
|
1418
|
+
For example, the Primary buttons:
|
|
1419
|
+
|
|
1420
|
+
- Primary Large: `css-oqv33j`
|
|
1421
|
+
- Primary Medium: `css-1nhzlx`
|
|
1422
|
+
- Primary Small: `css-1ygk6q`
|
|
1423
|
+
|
|
1424
|
+
This means each button is a unique style sheet insert by Emotion. If we render each permutation at once, there will only be one expensive [style recalculation](https://microsoftedge.github.io/DevTools/explainers/StyleTracing/explainer.html)
|
|
1425
|
+
|
|
1426
|
+
Converting to use the Canvas Kit Styling solution means organizing a little different. In our example, it is already organized well, but conditionals might be anywhere in the style functions and will need to be organized in groups.
|
|
1427
|
+
|
|
1428
|
+
### Button using only `createStyles`
|
|
1429
|
+
|
|
1430
|
+
What are we really trying to accomplish? [BEM](https://getbem.com/introduction) fits well with compound components. BEM stands for Block, Element, Modifer. In compound components, "Block" refers to a container component while "Element" refers to subcomponets. The "Modifer" refers to changing the appearance of a block.
|
|
1431
|
+
|
|
1432
|
+
In our example, all styles that are common to all appearances of our button. It might be `borderRadius`, `fontFamily`. We can use `createStyles` to define these styles:
|
|
1433
|
+
|
|
1434
|
+
```ts
|
|
1435
|
+
const baseStyles = createStyles({
|
|
1436
|
+
fontSize: '1rem',
|
|
1437
|
+
display: 'flex',
|
|
1438
|
+
borderRadius: '1rem',
|
|
1439
|
+
});
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
The `variant` modifiers use a variable prop called `backgroundColor` which cannot be variable at runtime. We need to use a CSS Variable for this.
|
|
1443
|
+
|
|
1444
|
+
We can create modifers using `createStyles` and organize them in an object:
|
|
1445
|
+
|
|
1446
|
+
```ts
|
|
1447
|
+
const modifierStyles = {
|
|
1448
|
+
variant: {
|
|
1449
|
+
primary: createStyles({
|
|
1450
|
+
background: `var(--background-color-button, blue)`,
|
|
1451
|
+
color: 'white',
|
|
1452
|
+
}),
|
|
1453
|
+
secondary: createStyles({
|
|
1454
|
+
background: `var(--background-color-button, gray)`,
|
|
1455
|
+
}),
|
|
1456
|
+
danger: createStyles({
|
|
1457
|
+
background: `var(--background-color-button, red)`,
|
|
1458
|
+
}),
|
|
1459
|
+
},
|
|
1460
|
+
size: {
|
|
1461
|
+
large: createStyles({
|
|
1462
|
+
fontSize: '1.4rem',
|
|
1463
|
+
height: '2rem',
|
|
1464
|
+
}),
|
|
1465
|
+
medium: createStyles({
|
|
1466
|
+
fontSize: '1rem',
|
|
1467
|
+
height: '1.5rem',
|
|
1468
|
+
}),
|
|
1469
|
+
small: createStyles({
|
|
1470
|
+
fontSize: '0.8rem',
|
|
1471
|
+
height: '1.2rem',
|
|
1472
|
+
}),
|
|
1473
|
+
},
|
|
1474
|
+
};
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
Each modifier value uses `createStyles` which returns a different class name. This means we can create a "Primary Large" button by applying these modifiers to the `className` prop of a React element:
|
|
1478
|
+
|
|
1479
|
+
```jsx
|
|
1480
|
+
<button className={`${baseStyles} ${modifierStyles.variant.primary} ${modifierStyles.size.large}`}>
|
|
1481
|
+
Primary Large
|
|
1482
|
+
</button>
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
This will create a button with 3 separate class names applied. `@emotion/styled` only applies a single css class name.
|
|
1486
|
+
|
|
1487
|
+
```html
|
|
1488
|
+
<!-- @emotion/styled -->
|
|
1489
|
+
<button class="css-108wq52">Primary Large</button>
|
|
1490
|
+
|
|
1491
|
+
<!-- createStyles -->
|
|
1492
|
+
<button class="css-puxv12 css-puxv13 css-puxv16">Primary Large</button>
|
|
1493
|
+
```
|
|
1494
|
+
|
|
1495
|
+
If you want to change the background color, you'll have to pass it using `style`:
|
|
1496
|
+
|
|
1497
|
+
```jsx
|
|
1498
|
+
<button
|
|
1499
|
+
className={`${baseStyles} ${modifierStyles.size.large}`}
|
|
1500
|
+
style={{'--color-background-button': 'orange'}}
|
|
1501
|
+
>
|
|
1502
|
+
Orange Large
|
|
1503
|
+
</button>
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
The output HTML will look like:
|
|
1507
|
+
|
|
1508
|
+
```html
|
|
1509
|
+
<button class="css-puxv12 css-puxv16" style="--color-background-button: orange;">
|
|
1510
|
+
Orange Large
|
|
1511
|
+
</button>
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
This works because CSS Custom Properties cascade values. The `style` attribute defines styles on the element directly. This is a runtime in React that allows us to change a style without a new style block - the styles can be static, but we can still have variable property values.
|
|
1515
|
+
|
|
1516
|
+
```tsx
|
|
1517
|
+
import React from 'react';
|
|
1518
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
1519
|
+
|
|
1520
|
+
interface ButtonProps {
|
|
1521
|
+
variant: 'primary' | 'secondary' | 'danger';
|
|
1522
|
+
size: 'large' | 'medium' | 'small';
|
|
1523
|
+
backgroundColor?: string;
|
|
1524
|
+
children?: React.ReactNode;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const baseStyles = createStyles({
|
|
1528
|
+
fontSize: '1rem',
|
|
1529
|
+
display: 'flex',
|
|
1530
|
+
borderRadius: '1rem',
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
const modifierStyles = {
|
|
1534
|
+
variant: {
|
|
1535
|
+
primary: createStyles({
|
|
1536
|
+
background: `var(--button-background-color, blue)`,
|
|
1537
|
+
color: 'white',
|
|
1538
|
+
}),
|
|
1539
|
+
secondary: createStyles({
|
|
1540
|
+
background: `var(--button-background-color, gray)`,
|
|
1541
|
+
}),
|
|
1542
|
+
danger: createStyles({
|
|
1543
|
+
background: `var(--button-background-color, red)`,
|
|
1544
|
+
}),
|
|
1545
|
+
},
|
|
1546
|
+
size: {
|
|
1547
|
+
large: createStyles({
|
|
1548
|
+
fontSize: '1.4rem',
|
|
1549
|
+
height: '2rem',
|
|
1550
|
+
}),
|
|
1551
|
+
medium: createStyles({
|
|
1552
|
+
fontSize: '1rem',
|
|
1553
|
+
height: '1.5rem',
|
|
1554
|
+
}),
|
|
1555
|
+
small: createStyles({
|
|
1556
|
+
fontSize: '0.8rem',
|
|
1557
|
+
height: '1.2rem',
|
|
1558
|
+
}),
|
|
1559
|
+
},
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1562
|
+
const Button = ({variant, size, backgroundColor, children}: ButtonProps) => {
|
|
1563
|
+
const className = [baseStyles, modifierStyles.variant[variant], modifierStyles.size[size]].join(
|
|
1564
|
+
' '
|
|
1565
|
+
);
|
|
1566
|
+
const style = {'--button-background-color': backgroundColor} as React.CSSProperties;
|
|
1567
|
+
return (
|
|
1568
|
+
<button className={className} style={style}>
|
|
1569
|
+
{children}
|
|
1570
|
+
</button>
|
|
1571
|
+
);
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
export const ManualStylesButton = () => {
|
|
1575
|
+
return (
|
|
1576
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
|
|
1577
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1578
|
+
<Button variant="primary" size="large">
|
|
1579
|
+
Primary Large
|
|
1580
|
+
</Button>
|
|
1581
|
+
<Button variant="primary" size="medium">
|
|
1582
|
+
Primary Medium
|
|
1583
|
+
</Button>
|
|
1584
|
+
<Button variant="primary" size="small">
|
|
1585
|
+
Primary Small
|
|
1586
|
+
</Button>
|
|
1587
|
+
</div>
|
|
1588
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1589
|
+
<Button variant="secondary" size="large">
|
|
1590
|
+
Secondary Large
|
|
1591
|
+
</Button>
|
|
1592
|
+
<Button variant="secondary" size="medium">
|
|
1593
|
+
Secondary Medium
|
|
1594
|
+
</Button>
|
|
1595
|
+
<Button variant="secondary" size="small">
|
|
1596
|
+
Secondary Small
|
|
1597
|
+
</Button>
|
|
1598
|
+
</div>
|
|
1599
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1600
|
+
<Button variant="danger" size="large">
|
|
1601
|
+
Danger Large
|
|
1602
|
+
</Button>
|
|
1603
|
+
<Button variant="danger" size="medium">
|
|
1604
|
+
Danger Medium
|
|
1605
|
+
</Button>
|
|
1606
|
+
<Button variant="danger" size="small">
|
|
1607
|
+
Danger Small
|
|
1608
|
+
</Button>
|
|
1609
|
+
</div>
|
|
1610
|
+
<div style={{display: 'flex', gap: '1rem'}}>
|
|
1611
|
+
<Button variant="danger" size="large" backgroundColor="orange">
|
|
1612
|
+
Custom Large
|
|
1613
|
+
</Button>
|
|
1614
|
+
<Button variant="danger" size="medium" backgroundColor="orange">
|
|
1615
|
+
Custom Medium
|
|
1616
|
+
</Button>
|
|
1617
|
+
<Button variant="danger" size="small" backgroundColor="orange">
|
|
1618
|
+
Custom Small
|
|
1619
|
+
</Button>
|
|
1620
|
+
</div>
|
|
1621
|
+
</div>
|
|
1622
|
+
);
|
|
1623
|
+
};
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
### Button using all utilities
|
|
1627
|
+
|
|
1628
|
+
If we want variables that are hashed and make it easier to define and use, we have `createVars`. There are also edge cases for modifiers like allowing `undefined`, so we made a `createModifiers` function as well. Both `createModifiers` and `createVars` return a function that makes it easier to call with inputs and will return the correct output.
|
|
1629
|
+
|
|
1630
|
+
For example, `createModifiers`:
|
|
1631
|
+
|
|
1632
|
+
```tsx
|
|
1633
|
+
const myModifiers = createModifiers({
|
|
1634
|
+
size: {
|
|
1635
|
+
large: 'button-large',
|
|
1636
|
+
small: 'button-small'
|
|
1637
|
+
}
|
|
1638
|
+
})
|
|
1639
|
+
|
|
1640
|
+
myModifiers.size.large // 'button-large'
|
|
1641
|
+
|
|
1642
|
+
// the function knows what config can be passed
|
|
1643
|
+
// and what restrictions each value has
|
|
1644
|
+
myModifiers({size: 'large'}) // 'button-large'
|
|
1645
|
+
myModifiers({size: 'small'}) // 'button-small'
|
|
1646
|
+
myModifiers() // ''
|
|
1647
|
+
|
|
1648
|
+
// in a component
|
|
1649
|
+
<div className={myModifiers({size: 'large'})} /> // <div class="button-large" />
|
|
1650
|
+
```
|
|
1651
|
+
|
|
1652
|
+
`createVars`:
|
|
1653
|
+
|
|
1654
|
+
```tsx
|
|
1655
|
+
const myVars = createVars('background', 'color')
|
|
1656
|
+
|
|
1657
|
+
myVars.color // something like `--color-{hash}`
|
|
1658
|
+
|
|
1659
|
+
// the function knows what keys are allowed
|
|
1660
|
+
myVars({color: 'red'}) // {'--color-{hash}': 'red'}
|
|
1661
|
+
|
|
1662
|
+
// in a component
|
|
1663
|
+
<div style={myVars({color: 'red'})} /> // <div style="--color-{hash}: red;">
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
## How To Customize Styles
|
|
1667
|
+
|
|
1668
|
+
There are multiple ways to customize styles for components within Canvas Kit. The approach you choose will depend on use case.
|
|
1669
|
+
|
|
1670
|
+
### Create Styles
|
|
1671
|
+
|
|
1672
|
+
#### Using `createStyles` with `cs` prop
|
|
1673
|
+
|
|
1674
|
+
Use `createStyles` in tandem with `cs` prop when you're overriding static styles and making small modifications to an existing Canvas Kit component like padding, color and flex properties. Take our `Text` component as an example.
|
|
1675
|
+
|
|
1676
|
+
```tsx
|
|
1677
|
+
import {createStyles} from '@Workday/canvas-kit-styling';
|
|
1678
|
+
import {system} from '@Workday/canvas-tokens-web';
|
|
1679
|
+
import {Text} from '@Workday/canvas-kit-react/text';
|
|
1680
|
+
|
|
1681
|
+
const uppercaseTextStyles = createStyles({
|
|
1682
|
+
textTransform: 'uppercase',
|
|
1683
|
+
margin: system.space.x4
|
|
1684
|
+
})
|
|
1685
|
+
//...
|
|
1686
|
+
<Text cs={uppercaseTextStyles}>My uppercased text</Text>;
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
> **Note:** `createStyles` handles wrapping our token variables in `var(--${token})`
|
|
1690
|
+
|
|
1691
|
+
You can also apply styles created via `createStyles` via `className`.
|
|
1692
|
+
|
|
1693
|
+
```tsx
|
|
1694
|
+
import {createStyles} from '@Workday/canvas-kit-styling';
|
|
1695
|
+
import {system} from '@Workday/canvas-tokens-web';
|
|
1696
|
+
import {Text} from '@Workday/canvas-kit-react/text';
|
|
1697
|
+
|
|
1698
|
+
const uppercaseTextStyles = createStyles({
|
|
1699
|
+
textTransform: 'uppercase',
|
|
1700
|
+
margin: system.space.x4
|
|
1701
|
+
})
|
|
1702
|
+
//...
|
|
1703
|
+
<Text className={uppercaseTextStyles}>My uppercased text</Text>;
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
If you need to dynamically apply styles based on some state or prop, use [Stencils](#stencils) instead.
|
|
1707
|
+
|
|
1708
|
+
## Stencils
|
|
1709
|
+
|
|
1710
|
+
Stencils can be useful when applying dynamic styles or building your own reusable component.
|
|
1711
|
+
|
|
1712
|
+
### Extending Stencils
|
|
1713
|
+
|
|
1714
|
+
[Stencils](https://workday.github.io/canvas-kit/?path=/docs/styling-getting-started-create-stencil--docs) help you organize the styling of reusable components into base styles, modifiers, and variables. The organization makes it more natural to produce static and clean CSS with optional extraction into CSS files.
|
|
1715
|
+
|
|
1716
|
+
Stencils that define variables, modifiers and base styles can be extended to create your own reusable component using Canvas Kit styles.
|
|
1717
|
+
|
|
1718
|
+
If we take `SystemIcon` component as an example, it defines `systemIconStencil` which defines styles for an icon. This stencil can be extended to build a custom icon component for your use case.
|
|
1719
|
+
|
|
1720
|
+
**Before v11** you'd have to use `systemIconStyles` function to overwrite styles for an icon:
|
|
1721
|
+
|
|
1722
|
+
```tsx
|
|
1723
|
+
// Before v11
|
|
1724
|
+
import {systemIconStyles} from '@workday/canvas-kit-react';
|
|
1725
|
+
import {space} from '@workday/canvas-kit-react/tokens'; // old tokens
|
|
1726
|
+
|
|
1727
|
+
// old way of styling with Emotion styled
|
|
1728
|
+
const StyledNavIcon = styled('span')(({size, iconStyles}){
|
|
1729
|
+
display: 'inline-flex',
|
|
1730
|
+
pointerEvents: 'unset',
|
|
1731
|
+
margin: `${space.xxxs} ${space.xxxs} 0 0`,
|
|
1732
|
+
padding: '0',
|
|
1733
|
+
'svg': {
|
|
1734
|
+
...iconStyles,
|
|
1735
|
+
width: size,
|
|
1736
|
+
height: size,
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
const NavIcon = ({iconColor, iconHover, iconBackground, iconBackgroundHover, icon, size}) => {
|
|
1741
|
+
// old way of styling with systemIconStyles function
|
|
1742
|
+
// systemIconStyles is deprecated in v11
|
|
1743
|
+
const iconStyles = systemIconStyles({
|
|
1744
|
+
fill: iconColor,
|
|
1745
|
+
fillHover: iconHover,
|
|
1746
|
+
background: iconBackground,
|
|
1747
|
+
backgroundHover: iconBackgroundHover,
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
// insert icon function used by platform or any other functionality here
|
|
1751
|
+
|
|
1752
|
+
return (
|
|
1753
|
+
<StyledNavIcon
|
|
1754
|
+
icon={icon}
|
|
1755
|
+
size={size}
|
|
1756
|
+
iconStyles={iconStyles}
|
|
1757
|
+
/>
|
|
1758
|
+
);
|
|
1759
|
+
};
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
**From v11** you'd extend `systemIconStencil` to reuse its styles:
|
|
1763
|
+
|
|
1764
|
+
```tsx
|
|
1765
|
+
// v11
|
|
1766
|
+
import {createStencil} from '@workday/canvas-kit-styling';
|
|
1767
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
1768
|
+
import {systemIconStencil} from '@workday/canvas-kit-react/icon';
|
|
1769
|
+
|
|
1770
|
+
const navIconStencil = createStencil({
|
|
1771
|
+
// We extend `systemIconStencil` to inherit it's base styles, modifiers and variables so that we can customize it
|
|
1772
|
+
extends: systemIconStencil,
|
|
1773
|
+
vars: {
|
|
1774
|
+
// These variables support our styling iconHover and iconBackgroundHover
|
|
1775
|
+
// they can be removed later and overwritten by `cs`.
|
|
1776
|
+
// Also note the variables have no value. This allows for cascading styles.
|
|
1777
|
+
fillHover: '',
|
|
1778
|
+
backgroundHover: '',
|
|
1779
|
+
},
|
|
1780
|
+
base: ({fillHover, backgroundHover}) => ({
|
|
1781
|
+
display: 'inline-flex',
|
|
1782
|
+
pointerEvents: 'unset',
|
|
1783
|
+
// instead of using our old tokens it's better to use our new system tokens
|
|
1784
|
+
margin: `${system.space.x1} ${system.space.x1} 0 0`,
|
|
1785
|
+
padding: '0',
|
|
1786
|
+
'&:hover, &.hover': {
|
|
1787
|
+
// systemIconStencil doesn't have hover specific variables
|
|
1788
|
+
// so we reassigned color and backgroundColor variables using pseudo-selector
|
|
1789
|
+
[systemIconStencil.vars.color]: fillHover,
|
|
1790
|
+
[systemIconStencil.vars.backgroundColor]: backgroundHover,
|
|
1791
|
+
},
|
|
1792
|
+
}),
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
// Your reusable NavIcon component using Stencils
|
|
1796
|
+
const NavIcon = ({
|
|
1797
|
+
iconColor,
|
|
1798
|
+
iconHover,
|
|
1799
|
+
iconBackground,
|
|
1800
|
+
iconBackgroundHover,
|
|
1801
|
+
icon,
|
|
1802
|
+
size,
|
|
1803
|
+
...elemProps
|
|
1804
|
+
}) => {
|
|
1805
|
+
// insert icon function used by platform or any other functionality here
|
|
1806
|
+
|
|
1807
|
+
return (
|
|
1808
|
+
<span
|
|
1809
|
+
icon={icon}
|
|
1810
|
+
{...handleCsProp(
|
|
1811
|
+
elemProps,
|
|
1812
|
+
navIconStencil({
|
|
1813
|
+
// Because we're extending systemIconStencil, it already has a size prop and applies size to the svg's width and height
|
|
1814
|
+
// so we don't need to set these variables in our navIconStencil
|
|
1815
|
+
size,
|
|
1816
|
+
// systemIconStencil already has color (for icon fill) and backgroundColor variables
|
|
1817
|
+
// so we assigned them to our prop values
|
|
1818
|
+
color: iconColor,
|
|
1819
|
+
backgroundColor: iconBackground,
|
|
1820
|
+
fillHover: iconHover,
|
|
1821
|
+
backgroundHover: iconBackgroundHover,
|
|
1822
|
+
})
|
|
1823
|
+
)}
|
|
1824
|
+
/>
|
|
1825
|
+
);
|
|
1826
|
+
};
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
Another example of Stencil extension and customization is our [CustomButton](https://workday.github.io/canvas-kit/?path=/story/components-buttons--docs#custom-styles) example. This example highlights the power of inheritance that you get from extending stencils.
|
|
1830
|
+
|
|
1831
|
+
## Merging Styles
|
|
1832
|
+
|
|
1833
|
+
### handleCsProp
|
|
1834
|
+
|
|
1835
|
+
But what about when using components that use `@emotion/react` or `@emotion/styled`? Those libraries use a different approach. Instead of multiple class names, they use a single, merged class name.
|
|
1836
|
+
|
|
1837
|
+
`handleCsProp` was created to handle integration with existing components that use the `css` prop from `@emotion/react` or the `styled` components from `@emotion/styled`. If a class name from one of those libraries is detected, style merging will follow the same rules as those libraries. Instead of multiple class names, a single class name with all matching properties is created. The `handleCsProp` also takes care of merging `style` props, `className` props, and can handle the `cs` prop:
|
|
1838
|
+
|
|
1839
|
+
```tsx
|
|
1840
|
+
const myStencil = createStencil({
|
|
1841
|
+
// ...
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
const MyComponent = elemProps => {
|
|
1845
|
+
return <div {...handleProps(elemProps, myStencil({}))} />;
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
// All props will be merged for you
|
|
1849
|
+
<MyComponent style={{color: 'red'}} className="my-classname" cs={{position: 'relative'}} />;
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
`handleCsProp` will make sure the `style` prop is passed to the `div` and that the `my-classname` CSS class name appears on the `div`'s class list. Also the `cs` prop will add the appropriate styles to the element via a CSS class name. If your component needs to handle being passed a `className`, `style`, or `cs` prop, use `handleCsProp`.
|
|
1853
|
+
|
|
1854
|
+
### mergeStyles (deprecated)
|
|
1855
|
+
|
|
1856
|
+
In v9, we used `@emotion/styled` or `@emotion/react` for all styling which is a runtime styling solution. Starting in v10, we're migrating our styling to a more static solution using `createStyles` and the `cs` prop.
|
|
1857
|
+
|
|
1858
|
+
For a transition period, we're opting for backwards compatibility. If style props are present, [styled components](https://emotion.sh/docs/styled) are used, or the [css prop](https://emotion.sh/docs/css-prop) is used in a component, Emotion's style merging will be invoked to make sure the following style precedence:
|
|
1859
|
+
|
|
1860
|
+
```
|
|
1861
|
+
createStyles > CSS Prop > Styled Component > Style props
|
|
1862
|
+
```
|
|
1863
|
+
|
|
1864
|
+
This will mean that any `css` prop or use of `styled` within the component tree _per element_ will cause style class merging. For example:
|
|
1865
|
+
|
|
1866
|
+
```tsx
|
|
1867
|
+
import styled from '@emotion/styled';
|
|
1868
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
1869
|
+
import {mergeStyles} from '@workday/canvas-kit-react/layout';
|
|
1870
|
+
|
|
1871
|
+
const styles1 = createStyles({
|
|
1872
|
+
padding: 4,
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
const styles2 = createStyles({
|
|
1876
|
+
padding: 12,
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
const Component1 = props => {
|
|
1880
|
+
return <div {...mergeStyles(props, [styles1])} />;
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
const Component2 = props => {
|
|
1884
|
+
return <Component1 cs={styles2} />;
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
const Component3 = styled(Component1)({
|
|
1888
|
+
padding: 8,
|
|
1889
|
+
});
|
|
1890
|
+
|
|
1891
|
+
const Component4 = props => {
|
|
1892
|
+
return <Component3 cs={styles2} />;
|
|
1893
|
+
};
|
|
1894
|
+
|
|
1895
|
+
export default () => (
|
|
1896
|
+
<>
|
|
1897
|
+
<Component1 />
|
|
1898
|
+
<Component2 />
|
|
1899
|
+
<Component3 />
|
|
1900
|
+
<Component4 />
|
|
1901
|
+
</>
|
|
1902
|
+
);
|
|
1903
|
+
```
|
|
1904
|
+
|
|
1905
|
+
The `styled` component API is forcing `mergeStyles` to go into Emotion merge mode, which removes the `style1` class name and creates a new class based on all the merged style properties. So `.component3` is a new class created by Emotion at render time that merges `.style1` and `{padding: 8px}`. `Component4` renders `Component3` with a `cs` prop, but `Component3` is already in merge mode and so `Component4` will also merge all styles into a new class name of `.component4` that has the styles from `.style1`, `.component3`, and `{padding: 12px}`:
|
|
1906
|
+
|
|
1907
|
+
```html
|
|
1908
|
+
<head>
|
|
1909
|
+
<style>
|
|
1910
|
+
.styles1 {
|
|
1911
|
+
padding: 4px;
|
|
1912
|
+
}
|
|
1913
|
+
.styles2 {
|
|
1914
|
+
padding: 8px;
|
|
1915
|
+
}
|
|
1916
|
+
.component3 {
|
|
1917
|
+
padding: 4px;
|
|
1918
|
+
padding: 8px;
|
|
1919
|
+
}
|
|
1920
|
+
.component4 {
|
|
1921
|
+
padding: 4px;
|
|
1922
|
+
padding: 8px;
|
|
1923
|
+
padding: 12px;
|
|
1924
|
+
}
|
|
1925
|
+
</style>
|
|
1926
|
+
</head>
|
|
1927
|
+
|
|
1928
|
+
<div class="styles1"></div>
|
|
1929
|
+
<div class="styles1 styles2"></div>
|
|
1930
|
+
<div class="component3"></div>
|
|
1931
|
+
<div class="component4"></div>
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
The `css` prop and `styled` component APIs will rewrite the `className` React prop by iterating over all class names and seeing if any exist within the cache. If a class name does exist in the cache, the CSS properties are copied to a new style property map until all the class names are evaluated and removed from the `className` prop. Emotion will then combine all the CSS properties and inject a new `StyleSheet` with a new class name and add that class name to the element.
|
|
1935
|
+
|
|
1936
|
+
The following example shows this style merging.
|
|
1937
|
+
|
|
1938
|
+
```tsx
|
|
1939
|
+
import * as React from 'react';
|
|
1940
|
+
import styled from '@emotion/styled';
|
|
1941
|
+
import {jsx} from '@emotion/react';
|
|
1942
|
+
|
|
1943
|
+
import {Flex} from '@workday/canvas-kit-react/layout';
|
|
1944
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
1945
|
+
import {base} from '@workday/canvas-tokens-web';
|
|
1946
|
+
import {createStyles, cssVar} from '@workday/canvas-kit-styling';
|
|
1947
|
+
|
|
1948
|
+
const backgroundColors = {
|
|
1949
|
+
cssProp: cssVar(base.orange500),
|
|
1950
|
+
styledComponent: cssVar(base.green500),
|
|
1951
|
+
styleProps: cssVar(base.magenta500),
|
|
1952
|
+
createStyles: cssVar(base.purple500),
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
const StyledPrimaryButton = styled(PrimaryButton)({
|
|
1956
|
+
backgroundColor: backgroundColors.styledComponent,
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
const styles = createStyles({
|
|
1960
|
+
backgroundColor: backgroundColors.createStyles,
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1963
|
+
const CSSProp = () => (
|
|
1964
|
+
<div
|
|
1965
|
+
style={{
|
|
1966
|
+
color: 'white',
|
|
1967
|
+
padding: '0 4px',
|
|
1968
|
+
height: 40,
|
|
1969
|
+
width: 100,
|
|
1970
|
+
backgroundColor: backgroundColors.cssProp,
|
|
1971
|
+
}}
|
|
1972
|
+
>
|
|
1973
|
+
CSS Prop
|
|
1974
|
+
</div>
|
|
1975
|
+
);
|
|
1976
|
+
const StyledComponent = () => (
|
|
1977
|
+
<div
|
|
1978
|
+
style={{
|
|
1979
|
+
color: 'white',
|
|
1980
|
+
padding: '0 4px',
|
|
1981
|
+
height: 40,
|
|
1982
|
+
width: 100,
|
|
1983
|
+
backgroundColor: backgroundColors.styledComponent,
|
|
1984
|
+
}}
|
|
1985
|
+
>
|
|
1986
|
+
Styled Component
|
|
1987
|
+
</div>
|
|
1988
|
+
);
|
|
1989
|
+
const CreateStyles = () => (
|
|
1990
|
+
<div
|
|
1991
|
+
style={{
|
|
1992
|
+
color: 'white',
|
|
1993
|
+
padding: '0 4px',
|
|
1994
|
+
height: 40,
|
|
1995
|
+
width: 100,
|
|
1996
|
+
backgroundColor: backgroundColors.createStyles,
|
|
1997
|
+
}}
|
|
1998
|
+
>
|
|
1999
|
+
createStyles
|
|
2000
|
+
</div>
|
|
2001
|
+
);
|
|
2002
|
+
const StyleProps = () => (
|
|
2003
|
+
<div
|
|
2004
|
+
style={{
|
|
2005
|
+
color: 'white',
|
|
2006
|
+
padding: '0 4px',
|
|
2007
|
+
height: 40,
|
|
2008
|
+
width: 100,
|
|
2009
|
+
backgroundColor: backgroundColors.styleProps,
|
|
2010
|
+
}}
|
|
2011
|
+
>
|
|
2012
|
+
Style Props
|
|
2013
|
+
</div>
|
|
2014
|
+
);
|
|
2015
|
+
|
|
2016
|
+
// We use this object and cast to `{}` to keep TypeScript happy. Emotion extends the JSX interface
|
|
2017
|
+
// to include the `css` prop, but the `jsx` function type doesn't accept the `css` prop. Casting to
|
|
2018
|
+
// an empty object keeps TypeScript happy and the `css` prop is valid at runtime.
|
|
2019
|
+
const cssProp = {css: {backgroundColor: backgroundColors.cssProp}} as {};
|
|
2020
|
+
|
|
2021
|
+
export const StylingOverrides = () => {
|
|
2022
|
+
return (
|
|
2023
|
+
<Flex flexDirection="column" minHeight="100vh" gap="s">
|
|
2024
|
+
<Flex flexDirection="column" gap="s">
|
|
2025
|
+
<h2>Buttons</h2>
|
|
2026
|
+
<Flex flexDirection="row" gap="s">
|
|
2027
|
+
<PrimaryButton cs={styles}>createStyles</PrimaryButton>
|
|
2028
|
+
{jsx(PrimaryButton, {...cssProp}, 'CSS Prop')}
|
|
2029
|
+
<StyledPrimaryButton>Styled Component</StyledPrimaryButton>
|
|
2030
|
+
<PrimaryButton backgroundColor={backgroundColors.styleProps}>Style Props</PrimaryButton>
|
|
2031
|
+
</Flex>
|
|
2032
|
+
<div>
|
|
2033
|
+
{jsx(
|
|
2034
|
+
PrimaryButton,
|
|
2035
|
+
{
|
|
2036
|
+
...cssProp,
|
|
2037
|
+
cs: styles,
|
|
2038
|
+
},
|
|
2039
|
+
'createStyles + CSS Prop'
|
|
2040
|
+
)}
|
|
2041
|
+
</div>
|
|
2042
|
+
<div>
|
|
2043
|
+
<StyledPrimaryButton cs={styles}>createStyles + Styled Component</StyledPrimaryButton>
|
|
2044
|
+
</div>
|
|
2045
|
+
<div>
|
|
2046
|
+
<PrimaryButton cs={styles} backgroundColor={backgroundColors.styleProps}>
|
|
2047
|
+
createStyles + Style Props
|
|
2048
|
+
</PrimaryButton>
|
|
2049
|
+
</div>
|
|
2050
|
+
<div>
|
|
2051
|
+
<StyledPrimaryButton backgroundColor={backgroundColors.styleProps} cs={styles}>
|
|
2052
|
+
createStyles + Styled Component + Style Props
|
|
2053
|
+
</StyledPrimaryButton>
|
|
2054
|
+
</div>
|
|
2055
|
+
<div>
|
|
2056
|
+
{jsx(
|
|
2057
|
+
StyledPrimaryButton,
|
|
2058
|
+
{
|
|
2059
|
+
...cssProp,
|
|
2060
|
+
backgroundColor: backgroundColors.styleProps,
|
|
2061
|
+
cs: styles,
|
|
2062
|
+
},
|
|
2063
|
+
'createStyles + CSS Prop + Styled Component + Style Props'
|
|
2064
|
+
)}
|
|
2065
|
+
</div>
|
|
2066
|
+
<div>{jsx(StyledPrimaryButton, {...cssProp}, 'CSS Prop + Styled Component')}</div>
|
|
2067
|
+
<div>
|
|
2068
|
+
{jsx(
|
|
2069
|
+
PrimaryButton,
|
|
2070
|
+
{
|
|
2071
|
+
...cssProp,
|
|
2072
|
+
backgroundColor: backgroundColors.styleProps,
|
|
2073
|
+
},
|
|
2074
|
+
'CSS Prop + Style Props'
|
|
2075
|
+
)}
|
|
2076
|
+
</div>
|
|
2077
|
+
<div>
|
|
2078
|
+
<StyledPrimaryButton backgroundColor={backgroundColors.styleProps}>
|
|
2079
|
+
Styled Component + Style Props
|
|
2080
|
+
</StyledPrimaryButton>
|
|
2081
|
+
</div>
|
|
2082
|
+
</Flex>
|
|
2083
|
+
<div>
|
|
2084
|
+
<p>Legend:</p>
|
|
2085
|
+
<CreateStyles />
|
|
2086
|
+
<CSSProp />
|
|
2087
|
+
<StyledComponent />
|
|
2088
|
+
<StyleProps />
|
|
2089
|
+
</div>
|
|
2090
|
+
<p>
|
|
2091
|
+
Style Precedence: <strong>createStyles</strong> > <strong>CSS Props</strong> >{' '}
|
|
2092
|
+
<strong>Styled Component</strong> > <strong>Style Props</strong>
|
|
2093
|
+
</p>
|
|
2094
|
+
</Flex>
|
|
2095
|
+
);
|
|
2096
|
+
};
|
|
2097
|
+
```
|
|
2098
|
+
|
|
2099
|
+
CSS style property merging works by [CSS specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity). If two matching selectors have the same specificity, the last defined property wins. Stencils take advantage of this by making all styles have the same specificity of `0-1-0` and inserting `base` styles, then `modifiers` (in order), then `compound` modifiers (in order). This means if a property was defined in `base`, a `modifier`, and a `compound` modifier, the `compound` modifier would win because it is the last defined. This should be the expected order.
|
|
2100
|
+
|
|
2101
|
+
> Caution
|
|
2102
|
+
> While we support `mergeStyles` we'd advise against using this in your components so that users can get the performance benefit of static styling using utilities like `createStyles` and `createStencil` in tandem with the `cs` prop.
|
|
2103
|
+
|
|
2104
|
+
## Canvas Kit Styling Utilities
|
|
2105
|
+
|
|
2106
|
+
A collection of helpful functions for styling with `@workday/canvas-kit-styling`. While they're fairly simple, they make styling much nicer.
|
|
2107
|
+
|
|
2108
|
+
### Pixels to Rem
|
|
2109
|
+
|
|
2110
|
+
This function converts a `px` value (number) to `rem` (string). This keeps you from having to do any tricky mental division or write irrational numbers.
|
|
2111
|
+
|
|
2112
|
+
```ts
|
|
2113
|
+
import {px2rem} from '@workday/canvas-kit-styling';
|
|
2114
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2115
|
+
|
|
2116
|
+
const styles = {
|
|
2117
|
+
// returns '0.0625rem'
|
|
2118
|
+
margin: px2rem(1),
|
|
2119
|
+
};
|
|
2120
|
+
```
|
|
2121
|
+
|
|
2122
|
+
### Calc Functions
|
|
2123
|
+
|
|
2124
|
+
Calc functions are useful for doing basic math operations with CSS `calc()` and variables. They will also wrap variables automatically in `var()`.
|
|
2125
|
+
|
|
2126
|
+
#### Add
|
|
2127
|
+
|
|
2128
|
+
This function returns a CSS `calc()` addition string.
|
|
2129
|
+
|
|
2130
|
+
```ts
|
|
2131
|
+
import {calc} from '@workday/canvas-kit-styling';
|
|
2132
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2133
|
+
|
|
2134
|
+
const styles = {
|
|
2135
|
+
// returns 'calc(var(--cnvs-sys-space-x1) + 0.125rem)'
|
|
2136
|
+
padding: calc.add(system.space.x1, '0.125rem'),
|
|
2137
|
+
};
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
#### Subtract
|
|
2141
|
+
|
|
2142
|
+
This function returns a CSS `calc()` subtraction string.
|
|
2143
|
+
|
|
2144
|
+
```ts
|
|
2145
|
+
import {calc} from '@workday/canvas-kit-styling';
|
|
2146
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2147
|
+
|
|
2148
|
+
const styles = {
|
|
2149
|
+
// returns 'calc(var(--cnvs-sys-space-x1) - 0.125rem)'
|
|
2150
|
+
padding: calc.subtract(system.space.x1, '0.125rem'),
|
|
2151
|
+
};
|
|
2152
|
+
```
|
|
2153
|
+
|
|
2154
|
+
#### Multiply
|
|
2155
|
+
|
|
2156
|
+
This function returns a CSS `calc()` multiplication string.
|
|
2157
|
+
|
|
2158
|
+
```ts
|
|
2159
|
+
import {calc} from '@workday/canvas-kit-styling';
|
|
2160
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2161
|
+
|
|
2162
|
+
const styles = {
|
|
2163
|
+
// returns 'calc(var(--cnvs-sys-space-x1) * 3)'
|
|
2164
|
+
padding: calc.multiply(system.space.x1, 3),
|
|
2165
|
+
};
|
|
2166
|
+
```
|
|
2167
|
+
|
|
2168
|
+
#### Divide
|
|
2169
|
+
|
|
2170
|
+
This function returns a CSS `calc()` division string
|
|
2171
|
+
|
|
2172
|
+
```ts
|
|
2173
|
+
import {calc} from '@workday/canvas-kit-styling';
|
|
2174
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2175
|
+
|
|
2176
|
+
const styles = {
|
|
2177
|
+
// returns 'calc(var(--cnvs-sys-space-x1) / 2)'
|
|
2178
|
+
padding: calc.divide(system.space.x1, 2),
|
|
2179
|
+
};
|
|
2180
|
+
```
|
|
2181
|
+
|
|
2182
|
+
#### Negate
|
|
2183
|
+
|
|
2184
|
+
This function negates a CSS variable to give you the opposite value. This keeps you from having to wrap the variable in `calc()` and multiplying by `-1`.
|
|
2185
|
+
|
|
2186
|
+
```ts
|
|
2187
|
+
import {calc} from '@workday/canvas-kit-styling';
|
|
2188
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2189
|
+
|
|
2190
|
+
const styles = {
|
|
2191
|
+
// returns 'calc(var(--cnvs-sys-space-x4) * -1)'
|
|
2192
|
+
margin: calc.negate(system.space.x4),
|
|
2193
|
+
};
|
|
2194
|
+
```
|
|
2195
|
+
|
|
2196
|
+
### keyframes
|
|
2197
|
+
|
|
2198
|
+
The `keyframes` function re-exports the [Emotion CSS keyframes](https://emotion.sh/docs/keyframes) function, but is compatible with a custom Emotion instance and is understood by the Static style transformer.
|
|
2199
|
+
|
|
2200
|
+
#### Example
|
|
2201
|
+
|
|
2202
|
+
```tsx
|
|
2203
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2204
|
+
import {createComponent} from '@workday/canvas-kit-react/common';
|
|
2205
|
+
import {
|
|
2206
|
+
handleCsProp,
|
|
2207
|
+
keyframes,
|
|
2208
|
+
createStencil,
|
|
2209
|
+
calc,
|
|
2210
|
+
px2rem,
|
|
2211
|
+
CSProps,
|
|
2212
|
+
} from '@workday/canvas-kit-styling';
|
|
2213
|
+
|
|
2214
|
+
/**
|
|
2215
|
+
* Keyframe for the dots loading animation.
|
|
2216
|
+
*/
|
|
2217
|
+
const keyframesLoading = keyframes({
|
|
2218
|
+
'0%, 80%, 100%': {
|
|
2219
|
+
transform: 'scale(0)',
|
|
2220
|
+
},
|
|
2221
|
+
'40%': {
|
|
2222
|
+
transform: 'scale(1)',
|
|
2223
|
+
},
|
|
2224
|
+
});
|
|
2225
|
+
|
|
2226
|
+
export const loadingStencil = createStencil({
|
|
2227
|
+
base: {
|
|
2228
|
+
display: 'inline-flex',
|
|
2229
|
+
gap: system.space.x2,
|
|
2230
|
+
width: system.space.x4,
|
|
2231
|
+
height: system.space.x4,
|
|
2232
|
+
fontSize: system.space.zero,
|
|
2233
|
+
borderRadius: system.shape.round,
|
|
2234
|
+
backgroundColor: system.color.bg.muted.softer,
|
|
2235
|
+
outline: `${px2rem(2)} solid transparent`,
|
|
2236
|
+
transform: 'scale(0)',
|
|
2237
|
+
animationName: keyframesLoading,
|
|
2238
|
+
animationDuration: calc.multiply('150ms', 35),
|
|
2239
|
+
animationIterationCount: 'infinite',
|
|
2240
|
+
animationTimingFunction: 'ease-in-out',
|
|
2241
|
+
animationFillMode: 'both',
|
|
2242
|
+
},
|
|
2243
|
+
});
|
|
2244
|
+
|
|
2245
|
+
/**
|
|
2246
|
+
* A simple component that displays three horizontal dots, to be used when some data is loading.
|
|
2247
|
+
*/
|
|
2248
|
+
export const LoadingDot = createComponent('div')({
|
|
2249
|
+
displayName: 'LoadingDots',
|
|
2250
|
+
Component: ({...elemProps}: CSProps, ref, Element) => {
|
|
2251
|
+
return <Element ref={ref} {...handleCsProp(elemProps, loadingStencil())}></Element>;
|
|
2252
|
+
},
|
|
2253
|
+
});
|
|
2254
|
+
```
|
|
2255
|
+
|
|
2256
|
+
### injectGlobal
|
|
2257
|
+
|
|
2258
|
+
The `injectGlobal` function re-exports the [Emotion CSS injectGlobal](https://emotion.sh/docs/@emotion/css#global-styles) function, but is compatible with a custom Emotion instance and is understood by the Static style transformer. It will also wrap our CSS tokens to ensure you can inject global styles using our CSS variables.
|
|
2259
|
+
|
|
2260
|
+
```tsx
|
|
2261
|
+
injectGlobal({
|
|
2262
|
+
...fonts,
|
|
2263
|
+
'html, body': {
|
|
2264
|
+
fontFamily: system.fontFamily.default,
|
|
2265
|
+
margin: 0,
|
|
2266
|
+
minHeight: '100vh',
|
|
2267
|
+
...system.type.heading.large,
|
|
2268
|
+
},
|
|
2269
|
+
'#root, #root < div': {
|
|
2270
|
+
minHeight: '100vh',
|
|
2271
|
+
},
|
|
2272
|
+
});
|
|
2273
|
+
```
|
|
2274
|
+
|
|
2275
|
+
#### Example
|
|
2276
|
+
|
|
2277
|
+
```tsx
|
|
2278
|
+
import {createRoot} from 'react-dom/client';
|
|
2279
|
+
import {fonts} from '@workday/canvas-kit-react-fonts';
|
|
2280
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
2281
|
+
import {cssVar, injectGlobal} from '@workday/canvas-kit-styling';
|
|
2282
|
+
import {App} from './App';
|
|
2283
|
+
|
|
2284
|
+
import '@workday/canvas-tokens-web/css/base/_variables.css';
|
|
2285
|
+
import '@workday/canvas-tokens-web/css/brand/_variables.css';
|
|
2286
|
+
import '@workday/canvas-tokens-web/css/system/_variables.css';
|
|
2287
|
+
|
|
2288
|
+
//@ts-ignore
|
|
2289
|
+
injectGlobal({
|
|
2290
|
+
...fonts,
|
|
2291
|
+
'html, body': {
|
|
2292
|
+
fontFamily: cssVar(system.fontFamily.default),
|
|
2293
|
+
margin: 0,
|
|
2294
|
+
minHeight: '100vh',
|
|
2295
|
+
},
|
|
2296
|
+
'#root, #root < div': {
|
|
2297
|
+
minHeight: '100vh',
|
|
2298
|
+
...system.type.body.small,
|
|
2299
|
+
},
|
|
2300
|
+
});
|
|
2301
|
+
|
|
2302
|
+
const container = document.getElementById('root')!;
|
|
2303
|
+
const root = createRoot(container);
|
|
2304
|
+
root.render(<App />);
|
|
2305
|
+
```
|
|
2306
|
+
|
|
2307
|
+
### Custom Emotion Instance
|
|
2308
|
+
|
|
2309
|
+
Static style injection happens during the parsing stages of the files. This means when you `import` a component that uses static styling, the styles are injected immediately. This happens way before rendering, so using the Emotion [CacheProvider](https://emotion.sh/docs/cache-provider) does not work. A custom instance must be created _before_ any style utilities are called - during the bootstrapping phase of an application. We don't have a working example because it requires an isolated application, but here's an example adding a `nonce` to an application:
|
|
2310
|
+
|
|
2311
|
+
```tsx
|
|
2312
|
+
// bootstrap-styles.ts
|
|
2313
|
+
import {createInstance} from '@workday/canvas-kit-styling';
|
|
2314
|
+
|
|
2315
|
+
// assuming this file is being called via a `script` tag and that
|
|
2316
|
+
// script tag has a `nonce` attribute set from the server
|
|
2317
|
+
createInstance({nonce: document.currentScript.nonce});
|
|
2318
|
+
|
|
2319
|
+
// index.ts
|
|
2320
|
+
import React from 'react';
|
|
2321
|
+
import ReactDOM from 'react-dom';
|
|
2322
|
+
|
|
2323
|
+
// call the bootstrap in the import list. This has the side-effect
|
|
2324
|
+
// of creating an instance
|
|
2325
|
+
import './bootstrap-styles';
|
|
2326
|
+
|
|
2327
|
+
import App from './App';
|
|
2328
|
+
|
|
2329
|
+
const root = ReactDOM.createRoot(document.querySelector('#root'));
|
|
2330
|
+
|
|
2331
|
+
root.render(<App />);
|
|
2332
|
+
|
|
2333
|
+
// App.tsx
|
|
2334
|
+
import React from 'react';
|
|
2335
|
+
|
|
2336
|
+
// The following will create and inject styles. We cannot adjust
|
|
2337
|
+
// the Emotion instance after this import
|
|
2338
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
2339
|
+
|
|
2340
|
+
// if we call `createInstance` here, we'll get a warning in
|
|
2341
|
+
// development mode
|
|
2342
|
+
|
|
2343
|
+
export default () => {
|
|
2344
|
+
return <PrimaryButton>Button</PrimaryButton>;
|
|
2345
|
+
};
|
|
2346
|
+
```
|