@workday/canvas-kit-docs 14.0.0-alpha.1149-next.0 → 14.0.0-alpha.1151-next.0
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/es6/lib/stackblitzFiles/packageJSONFile.js +5 -5
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.ts +5 -5
- package/dist/mdx/styling/mdx/CreateStyles.mdx +111 -0
- package/dist/mdx/styling/mdx/CustomizingStyles.mdx +179 -0
- package/dist/mdx/styling/mdx/FromEmotion.mdx +178 -0
- package/dist/mdx/styling/mdx/MergingStyles.mdx +164 -0
- package/dist/mdx/styling/mdx/Overview.mdx +254 -0
- package/dist/mdx/styling/mdx/Stencils.mdx +459 -0
- package/dist/mdx/styling/mdx/Utilities.mdx +246 -0
- package/dist/mdx/styling/mdx/WhyCanvasStyling.mdx +136 -0
- package/dist/mdx/styling/mdx/examples/CSProp.tsx +36 -0
- package/dist/mdx/styling/mdx/examples/CreateModifiers.tsx +27 -0
- package/dist/mdx/styling/mdx/examples/CreateStencil.tsx +63 -0
- package/dist/mdx/styling/mdx/examples/CreateStyles.tsx +13 -0
- package/dist/mdx/styling/mdx/examples/CreateVars.tsx +20 -0
- package/dist/mdx/styling/mdx/examples/CustomButton.tsx +69 -0
- package/dist/mdx/styling/mdx/examples/CustomIcon.tsx +23 -0
- package/dist/mdx/styling/mdx/examples/EmotionButton.tsx +111 -0
- package/dist/mdx/styling/mdx/examples/ManualStylesButton.tsx +107 -0
- package/dist/mdx/styling/mdx/examples/StyledButton.tsx +31 -0
- package/dist/mdx/styling/mdx/examples/StylingButton.tsx +107 -0
- package/dist/mdx/styling/mdx/examples/StylingOverrides.tsx +158 -0
- package/package.json +6 -6
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {InformationHighlight} from '@workday/canvas-kit-preview-react/information-highlight';
|
|
2
|
+
import {Hyperlink} from '@workday/canvas-kit-react/button';
|
|
3
|
+
import {system} from '@workday/canvas-tokens-web'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Canvas Kit Styling
|
|
7
|
+
|
|
8
|
+
## Introduction
|
|
9
|
+
|
|
10
|
+
Canvas Kit styling is a custom CSS-in-JS solution that provides both a runtime for development and a
|
|
11
|
+
static parsing process for build time. This system offers several key benefits:
|
|
12
|
+
|
|
13
|
+
- TypeScript autocomplete for enhanced developer experience
|
|
14
|
+
- Low runtime overhead for better performance
|
|
15
|
+
- Static CSS compilation for optimized builds
|
|
16
|
+
- Dynamic styling with CSS Variables for flexible design
|
|
17
|
+
|
|
18
|
+
The motivation behind this custom styling solution stems from the need to move beyond IE11 support
|
|
19
|
+
and implement performance improvements using static styling methods. For more details, refer to the
|
|
20
|
+
[Why Canvas Kit Styling](https://workday.github.io/canvas-kit/?path=/docs/styling-why-canvas-styling--docs)
|
|
21
|
+
section.
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
The Canvas Kit styling system consists of two main packages:
|
|
26
|
+
|
|
27
|
+
- `@workday/canvas-kit-styling` - Core styling utilities for runtime use
|
|
28
|
+
- `@workday/canvas-kit-styling-transform` - Build-time optimization tools
|
|
29
|
+
|
|
30
|
+
These packages work together to provide a CSS-in-JS experience during development while enabling optimized static CSS in production.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn add @workday/canvas-kit-styling
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import React from 'react';
|
|
42
|
+
import {createRoot} from 'react-dom/client';
|
|
43
|
+
|
|
44
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
45
|
+
|
|
46
|
+
const myStyles = createStyles({
|
|
47
|
+
backgroundColor: 'red',
|
|
48
|
+
}); // returns the CSS class name created for this style
|
|
49
|
+
|
|
50
|
+
myStyles; // something like "css-{hash}"
|
|
51
|
+
|
|
52
|
+
const domNode = document.getElementById('root');
|
|
53
|
+
const root = createRoot(domNode);
|
|
54
|
+
|
|
55
|
+
root.render(<div className={myStyles}>Hello!</div>);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
Canvas Kit Styling comes with a runtime that doesn't need anything special for developement. The
|
|
61
|
+
runtime uses `@emotion/css` to include your styles on the page.
|
|
62
|
+
|
|
63
|
+
## Production
|
|
64
|
+
|
|
65
|
+
If you wish to use the static compilation, you must use the `@workday/canvas-kit-styling-transform`
|
|
66
|
+
package. Add the following to your project's `tsconfig.json` file:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"compilerOptions": {
|
|
71
|
+
// other options
|
|
72
|
+
"plugins": [
|
|
73
|
+
{
|
|
74
|
+
"transform": "@workday/canvas-kit-styling-transform",
|
|
75
|
+
"prefix": "css",
|
|
76
|
+
"fallbackFiles": [""]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This adds a list of plugins to use when transforming TypeScript files into JavaScript files. The
|
|
84
|
+
[ts-patch](https://www.npmjs.com/package/ts-patch) projects uses the `plugins` when running
|
|
85
|
+
transforms.
|
|
86
|
+
|
|
87
|
+
### Webpack
|
|
88
|
+
|
|
89
|
+
You will need to transform TypeScript files using the `ts-patch` which is the same as the TypeScript
|
|
90
|
+
tanspiler except it uses TypeScript's
|
|
91
|
+
[transform API](https://levelup.gitconnected.com/writing-a-custom-typescript-ast-transformer-731e2b0b66e6)
|
|
92
|
+
to transform code during compilation.
|
|
93
|
+
|
|
94
|
+
In your webpack config, you add the following:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
{
|
|
98
|
+
rules: [
|
|
99
|
+
//...
|
|
100
|
+
{
|
|
101
|
+
test: /.\.tsx?$/,
|
|
102
|
+
loaders: [
|
|
103
|
+
// ts-loader
|
|
104
|
+
{
|
|
105
|
+
loader: require.resolve('ts-loader'),
|
|
106
|
+
options: {
|
|
107
|
+
compiler: 'ts-patch/compiler',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
// OR awesome-typescript-loader
|
|
111
|
+
{
|
|
112
|
+
loader: require.resolve('awesome-typescript-loader'),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Core Styling Approaches for Static Styling
|
|
121
|
+
For proper static styling there's two methods that you can use to apply styles.
|
|
122
|
+
1. Using `createStyles` for simple object base styles.
|
|
123
|
+
2. Using `createStencil` for dynamic styles and reusable components.
|
|
124
|
+
|
|
125
|
+
Both approaches are intended to be used in tandem with the `cs` prop when applying styles to our components.
|
|
126
|
+
|
|
127
|
+
### `cs` Prop
|
|
128
|
+
|
|
129
|
+
The `cs` prop takes in a single, or an array of values that are created by the `cs` function, a
|
|
130
|
+
string representing a CSS class name, or the return of the `createVars` function. It merges
|
|
131
|
+
everything together and applies `className` and `style` attributes to a React element. Most of our
|
|
132
|
+
components extend the `cs` prop so that you can statically apply styles to them.
|
|
133
|
+
|
|
134
|
+
> **Important**: While the `cs` prop accepts a style object, **this will not** be considered
|
|
135
|
+
> statically styling an element and you will lose the performance benefits. We plan on providing a babel plugin to extract these styles statically in a future version.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
139
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
140
|
+
|
|
141
|
+
const styles = createStyles({color: system.color.static.red.default});
|
|
142
|
+
|
|
143
|
+
function MyComponent() {
|
|
144
|
+
return <PrimaryButton cs={styles}>Text</PrimaryButton>;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `createStyles`
|
|
149
|
+
|
|
150
|
+
The primary utility function is the `createStyles` function. It makes a call to the `css` function
|
|
151
|
+
from `@emotion/css`. Emotion still does most of the heavy lifting by handling the serialization,
|
|
152
|
+
hashing, caching, and style injection.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
// Bad example (inside render function)
|
|
156
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
157
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
158
|
+
|
|
159
|
+
function MyComponent() {
|
|
160
|
+
const styles = createStyles({color: system.color.static.red.default}); // Don't do this
|
|
161
|
+
return <PrimaryButton cs={styles}>Text</PrimaryButton>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Good example (outside render function)
|
|
165
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
166
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
167
|
+
|
|
168
|
+
const styles = createStyles({color: system.color.static.red.default});
|
|
169
|
+
|
|
170
|
+
function MyComponent() {
|
|
171
|
+
return <PrimaryButton cs={styles}>Text</PrimaryButton>;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Most of our components support using the `cs` prop to apply the static styles. It merges
|
|
176
|
+
everything together and applies `className` and `style` attributes to a React element.
|
|
177
|
+
|
|
178
|
+
<InformationHighlight className="sb-unstyled" cs={{marginBlock: system.space.x4,}}>
|
|
179
|
+
<InformationHighlight.Icon />
|
|
180
|
+
<InformationHighlight.Heading>Information</InformationHighlight.Heading>
|
|
181
|
+
<InformationHighlight.Body>
|
|
182
|
+
For a more in depth overview, please view our <Hyperlink src="https://workday.github.io/canvas-kit/?path=/docs/styling-getting-started-create-styles--docs">Create Styles</Hyperlink> docs.
|
|
183
|
+
</InformationHighlight.Body>
|
|
184
|
+
</InformationHighlight>
|
|
185
|
+
|
|
186
|
+
### `createStencil`
|
|
187
|
+
|
|
188
|
+
`createStencil` is a function for creating reusable, complex component styling systems. It manages
|
|
189
|
+
`base` styles, `parts`, `modifiers`, `variables`, and `compound` modifiers. Most of our components
|
|
190
|
+
also export their own Stencil that might expose CSS variables in order to modify the component.
|
|
191
|
+
|
|
192
|
+
In the example below, we leverage `parts`, `vars`, `base` and `modifiers` to create a reusable
|
|
193
|
+
`Card` component. The Stencil allows us to dynamic style the component based on the props.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import {createStencil}from '@workday/canvas-kit-styling';
|
|
197
|
+
import {Card} from '@workday/canvas-kit-react/card';
|
|
198
|
+
import {system} from '@workday/canvas-tokens-webs';
|
|
199
|
+
|
|
200
|
+
const themedCardStencil = createStencil({
|
|
201
|
+
vars: {
|
|
202
|
+
// Create CSS variables for the color of the header
|
|
203
|
+
headerColor: ''
|
|
204
|
+
},
|
|
205
|
+
parts: {
|
|
206
|
+
// Allows for styling a sub element of the component that may not be exposed through the API
|
|
207
|
+
header: 'themed-card-header'
|
|
208
|
+
},
|
|
209
|
+
base: ({headerPart, headerColor}) => ({
|
|
210
|
+
padding: system.space.x4,
|
|
211
|
+
boxShadow: system.depth[2],
|
|
212
|
+
backgroundColor: system.color.bg.default,
|
|
213
|
+
color: system.color.text.default,
|
|
214
|
+
// Targets the header part via [data-part="themed-card-header"]"]
|
|
215
|
+
[headerPart]: {
|
|
216
|
+
color: headerColor
|
|
217
|
+
}
|
|
218
|
+
}),
|
|
219
|
+
modifiers: {
|
|
220
|
+
isDarkTheme: {
|
|
221
|
+
// If the prop `isDarkTheme` is true, style the component and it's parts
|
|
222
|
+
true: ({headerPart}) => ({
|
|
223
|
+
backgroundColor: system.color.bg.contrast.default,
|
|
224
|
+
color: system.color.text.inverse
|
|
225
|
+
[headerPart]: {
|
|
226
|
+
color: system.color.text.inverse
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const ThemedCard = ({isDarkTheme, headerColor, elemProps}) => {
|
|
234
|
+
return (
|
|
235
|
+
/* Use the `cs` prop to apply the stencil and pass it the dynamic properties it needs to style accordingly */
|
|
236
|
+
<Card cs={themedCardStencil({isDarkTheme, headerColor})} {...elemProps}>
|
|
237
|
+
/* Apply the data part selector to the header */
|
|
238
|
+
<Card.Heading {...themedCardStencil.parts.header}>Canvas Supreme</Card.Heading>
|
|
239
|
+
<Card.Body>
|
|
240
|
+
Our house special supreme pizza includes pepperoni, sausage, bell peppers, mushrooms,
|
|
241
|
+
onions, and oregano.
|
|
242
|
+
</Card.Body>
|
|
243
|
+
</Card>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
<InformationHighlight className="sb-unstyled" cs={{marginBlock: system.space.x4,}}>
|
|
249
|
+
<InformationHighlight.Icon />
|
|
250
|
+
<InformationHighlight.Heading>Information</InformationHighlight.Heading>
|
|
251
|
+
<InformationHighlight.Body>
|
|
252
|
+
For a more in depth overview, please view our <Hyperlink src="https://workday.github.io/canvas-kit/?path=/docs/styling-getting-started-stencils--docs">Create Stencil</Hyperlink> docs.
|
|
253
|
+
</InformationHighlight.Body>
|
|
254
|
+
</InformationHighlight>
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
+
import {InformationHighlight} from '@workday/canvas-kit-preview-react/information-highlight';
|
|
3
|
+
import {system} from '@workday/canvas-tokens-web'
|
|
4
|
+
|
|
5
|
+
import CreateStencil from './examples/CreateStencil';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Stencils
|
|
9
|
+
|
|
10
|
+
Stencils are a reusable function that returns `style` and `className` props in an object. A Stencil
|
|
11
|
+
should apply to a single element. If your component has nested elements, you can youse `parts` to
|
|
12
|
+
targer those elements in the Stencil. If your component is a compound component, a stencil should be
|
|
13
|
+
created for each subcomponent. If your component is a config component, a stencil can have nested
|
|
14
|
+
styles.
|
|
15
|
+
|
|
16
|
+
We created Stencils as the reusable primitive of components. Stencils provide:
|
|
17
|
+
|
|
18
|
+
- `vars`: CSS variables for dynamic properties
|
|
19
|
+
- `base`: base styles to any component
|
|
20
|
+
- `modifier`: modifiers like “size = small,medium,large” or “color=red,blue,etc”
|
|
21
|
+
- `parts`: matching sub-elements that are part of a component
|
|
22
|
+
- `compound`: compound modifiers - styles that match multiple modifiers
|
|
23
|
+
|
|
24
|
+
## Basic Example
|
|
25
|
+
In the example below, Stencils allow you to dynamically style elements or components based on properties.
|
|
26
|
+
|
|
27
|
+
<ExampleCodeBlock code={CreateStencil} />
|
|
28
|
+
|
|
29
|
+
## When to Use `createStencil`
|
|
30
|
+
- When you're styling parts of a component that rely on dynamic properties.
|
|
31
|
+
- When you want to create a reusable component with dynamic styles.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Use a Stencil when building reusable components that have dynamic styles and properties.
|
|
35
|
+
|
|
36
|
+
## Concepts
|
|
37
|
+
|
|
38
|
+
### Base styles
|
|
39
|
+
|
|
40
|
+
Base styles are always applied to a Stencil. All your default styles should go here. Base styles
|
|
41
|
+
support psuedo selectors like `:focus-visible` or `:hover` as well as child selectors. Any selector
|
|
42
|
+
supported by `@emotion/css` is valid here. All styles must be static and statically analyzable by
|
|
43
|
+
the tranformer. If you need dynamic styling, look at Variables and Modifiers.
|
|
44
|
+
|
|
45
|
+
### Variables
|
|
46
|
+
|
|
47
|
+
Variables allow some properties to be dynamic. They work by creating
|
|
48
|
+
[CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) with
|
|
49
|
+
unique names and are applied using the
|
|
50
|
+
[style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) property of an element
|
|
51
|
+
to locally scope an override. Since we don't have access to those names, we need a function wrapper
|
|
52
|
+
around our style objects. This includes `base`, `modifiers`, and `compound` modifiers.
|
|
53
|
+
|
|
54
|
+
Here's a simplified example:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
const myStencil = createStencil({
|
|
58
|
+
vars: {
|
|
59
|
+
defaultColor: 'red' // default value
|
|
60
|
+
nonDefaultedColor: '', // will allow for uninitialization
|
|
61
|
+
},
|
|
62
|
+
base: ({defaultColor}) => {
|
|
63
|
+
color: defaultColor // `defaultColor` is '--defaultColor-abc123', not 'red'
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const elemProps = myStencil({color: 'blue'}) // {style: {'--defaultColor-abc123': 'blue'}}
|
|
68
|
+
|
|
69
|
+
<div {...elemProps} />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This will produce the following HTML:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<style>
|
|
76
|
+
.css-abc123 {
|
|
77
|
+
--defaultColor-abc123: red;
|
|
78
|
+
color: var(--defaultColor-abc123);
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
81
|
+
<div class="css-123abc" style="--defaultColor-abc123: blue;"></div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The element will have a `color` property of `'blue'` because the element style is the highest
|
|
85
|
+
specificity and wins over a local class name. In the "Styles" tab of developer tools, it will look
|
|
86
|
+
like the following:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
element.style {
|
|
90
|
+
--defaultColor-abc123: blue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.css-abc123 {
|
|
94
|
+
--defaultColor-abc123: red;
|
|
95
|
+
color: var(--defaultColor-abc123); // blue
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Variables are automatically added to the config of a Stencil. They share the same namespace as
|
|
100
|
+
modifiers, so **do not have a modifier with the same name as a variable**.
|
|
101
|
+
|
|
102
|
+
<InformationHighlight className="sb-unstyled" cs={{marginBlock: system.space.x4,}}>
|
|
103
|
+
<InformationHighlight.Icon />
|
|
104
|
+
<InformationHighlight.Heading>Note</InformationHighlight.Heading>
|
|
105
|
+
<InformationHighlight.Body>
|
|
106
|
+
Variables should be used sparingly. Style properties can be easily overridden without
|
|
107
|
+
variables. Variables are useful if you want to expose changing properties regardless of selectors.
|
|
108
|
+
For example, Buttons use variables for colors of all states (hover, active, focus, disabled, and
|
|
109
|
+
nested icons). Without variables, overriding the focus color would require deeply nested selector
|
|
110
|
+
overrides.
|
|
111
|
+
</InformationHighlight.Body>
|
|
112
|
+
</InformationHighlight>
|
|
113
|
+
|
|
114
|
+
#### Cascading Variables
|
|
115
|
+
|
|
116
|
+
Notice the `nonDefaultedColor` is not included in the base styles like `defaultColor` was. If a
|
|
117
|
+
variable has an empty string, it will can be uninitialized. Stencil variables with a default value
|
|
118
|
+
will create a "cascade barrier". A cascade barrier prevents the variable from "leaking" into the
|
|
119
|
+
component. For example, if a `Card` component was rendered within another `Card` component, the
|
|
120
|
+
variables from the parent `Card` would not leak into the child `Card` component. But there are times
|
|
121
|
+
where a component expects a parent component to set a CSS variable and that it should cascade to the
|
|
122
|
+
component. An example of this is the relationship between `SystemIcon` and `Button`. The `Button`
|
|
123
|
+
components set the `SystemIcon` variables and they should cascade into the `SystemIcon` component.
|
|
124
|
+
|
|
125
|
+
<InformationHighlight className="sb-unstyled" cs={{marginBlock: system.space.x4,}}>
|
|
126
|
+
<InformationHighlight.Icon />
|
|
127
|
+
<InformationHighlight.Heading>Note</InformationHighlight.Heading>
|
|
128
|
+
<InformationHighlight.Body>
|
|
129
|
+
Non-cascade variables _could_ be initialized. If you use uninitialized variables, be sure
|
|
130
|
+
to use a fallback in your styles.
|
|
131
|
+
</InformationHighlight.Body>
|
|
132
|
+
</InformationHighlight>
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
const myStencil = createStencil({
|
|
136
|
+
vars: {
|
|
137
|
+
color: '', // uninitialized
|
|
138
|
+
},
|
|
139
|
+
base({color}) {
|
|
140
|
+
return {
|
|
141
|
+
// provide a fallback. A uninitialized CSS variable will fall back to `initial`.
|
|
142
|
+
// for the `color` CSS property, that's most likely black (default text color)
|
|
143
|
+
color: cssVar(color, 'red'),
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Nested Variables
|
|
150
|
+
|
|
151
|
+
Variables can be nested one level. This can be useful for colors with different psuedo selectors
|
|
152
|
+
like `:hover` or `:focus`. Here's an example:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
const myStencil = createStencil({
|
|
156
|
+
vars: {
|
|
157
|
+
default: {
|
|
158
|
+
color: 'red'
|
|
159
|
+
},
|
|
160
|
+
hover: {
|
|
161
|
+
color: 'blue'
|
|
162
|
+
},
|
|
163
|
+
focus: {
|
|
164
|
+
color: 'orange'
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
base: ({default, hover, focus}) => {
|
|
168
|
+
color: default.color,
|
|
169
|
+
'&:hover': {
|
|
170
|
+
color: hover.color
|
|
171
|
+
},
|
|
172
|
+
'&:focus': {
|
|
173
|
+
color: focus.color
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Modifiers
|
|
180
|
+
|
|
181
|
+
Modifiers are modifications to base styles. It should be used to change the appearance of a base
|
|
182
|
+
style. For example, a button may have a modifier for "primary" or "secondary" which may change the
|
|
183
|
+
visual emphasis of the button. Each modifier has its own CSS class name and the stencil will return
|
|
184
|
+
the correct CSS classes to apply to an element based on what modifiers are active.
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
const buttonStencil = createStencil({
|
|
188
|
+
base: {
|
|
189
|
+
padding: 5
|
|
190
|
+
// base styles
|
|
191
|
+
},
|
|
192
|
+
modifiers: {
|
|
193
|
+
variant: { // modifier name
|
|
194
|
+
primary: {
|
|
195
|
+
background: 'blue'
|
|
196
|
+
},
|
|
197
|
+
secondary: {
|
|
198
|
+
background: 'gray'
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
defaultModifiers: {
|
|
203
|
+
variant: 'secondary'
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const elemProps = myStencil({variant: 'primary'}) // {className: "css-a0 css-a1"}
|
|
208
|
+
|
|
209
|
+
<div {...elemProps} />
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The HTML may look something like this:
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<style>
|
|
216
|
+
.css-a0 {
|
|
217
|
+
padding: 5px;
|
|
218
|
+
}
|
|
219
|
+
.css-a1 {
|
|
220
|
+
background: 'blue';
|
|
221
|
+
}
|
|
222
|
+
.css-a2 {
|
|
223
|
+
background: 'gray';
|
|
224
|
+
}
|
|
225
|
+
</style>
|
|
226
|
+
<div class="css-a0 css-a1"></div>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The optional `defaultModifiers` config property will default modifiers to a value. If a modifier is
|
|
230
|
+
not passed to the stencil, the default will be used.
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
myStencil(); // className will be `'css-a0 css-a2'`
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Compound Modifiers
|
|
237
|
+
|
|
238
|
+
A compound modifier creates a new CSS class for the intersection of two or more modifiers. Each
|
|
239
|
+
modifier can have its own separate CSS class while the intersection is a different CSS class.
|
|
240
|
+
|
|
241
|
+
For example:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
const buttonStencil = createStencil({
|
|
245
|
+
base: {
|
|
246
|
+
padding: 10,
|
|
247
|
+
// base styles
|
|
248
|
+
},
|
|
249
|
+
modifiers: {
|
|
250
|
+
size: {
|
|
251
|
+
// modifier name
|
|
252
|
+
large: {
|
|
253
|
+
padding: 20,
|
|
254
|
+
},
|
|
255
|
+
small: {
|
|
256
|
+
padding: 5,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
iconPosition: {
|
|
260
|
+
start: {
|
|
261
|
+
paddingInlineStart: 5,
|
|
262
|
+
},
|
|
263
|
+
end: {
|
|
264
|
+
paddingInlineEnd: 5,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
compound: [
|
|
269
|
+
{
|
|
270
|
+
modifiers: {size: 'large', position: 'start'},
|
|
271
|
+
styles: {
|
|
272
|
+
paddingInlineStart: 15,
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
modifiers: {size: 'small', position: 'end'},
|
|
277
|
+
styles: {
|
|
278
|
+
paddingInlineEnd: 0,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
<div {...buttonStencil()} />
|
|
285
|
+
<div {...buttonStencil({size: 'small'})} />
|
|
286
|
+
<div {...buttonStencil({size: 'small', iconPosition: 'end'})} />
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The HTML will look something like this:
|
|
290
|
+
|
|
291
|
+
```html
|
|
292
|
+
<style>
|
|
293
|
+
.a0 {
|
|
294
|
+
padding: 10px;
|
|
295
|
+
}
|
|
296
|
+
.a1 {
|
|
297
|
+
padding: 20px;
|
|
298
|
+
}
|
|
299
|
+
.a2 {
|
|
300
|
+
padding: 5px;
|
|
301
|
+
}
|
|
302
|
+
.a3 {
|
|
303
|
+
padding-inline-start: 5px;
|
|
304
|
+
}
|
|
305
|
+
.a4 {
|
|
306
|
+
padding-inline-end: 5px;
|
|
307
|
+
}
|
|
308
|
+
.a5 {
|
|
309
|
+
padding-inline-start: 15px;
|
|
310
|
+
}
|
|
311
|
+
.a6 {
|
|
312
|
+
padding-inline-start: 0px;
|
|
313
|
+
}
|
|
314
|
+
</style>
|
|
315
|
+
<div class="a0"></div>
|
|
316
|
+
<div class="a0 a2"></div>
|
|
317
|
+
<div class="a0 a2 a4 a6"></div>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Notice the stencil adds all the class names that match the base, modifiers, and compound modifiers.
|
|
321
|
+
|
|
322
|
+
### Variables and Modifiers with same keys
|
|
323
|
+
|
|
324
|
+
It is possible to have a variable and modifier sharing the same key. The Stencil will accept either
|
|
325
|
+
the modifier option or a string. The value will be sent as a variable regardless while the modifer
|
|
326
|
+
will only match if it is a valid modifer key.
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
const buttonStencil = createStencil({
|
|
330
|
+
vars: {
|
|
331
|
+
width: '10px',
|
|
332
|
+
},
|
|
333
|
+
base({width}) {
|
|
334
|
+
return {
|
|
335
|
+
width: width,
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
modifiers: {
|
|
339
|
+
width: {
|
|
340
|
+
zero: {
|
|
341
|
+
width: '0', // overrides base styles
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// `'zero'` is part of autocomplete
|
|
348
|
+
myStencil({width: 'zero'});
|
|
349
|
+
// returns {className: 'css-button css-button--width-zero', styles: { '--button-width': 'zero'}}
|
|
350
|
+
|
|
351
|
+
// width also accepts a string
|
|
352
|
+
myStencil({width: '10px'});
|
|
353
|
+
// returns {className: 'css-button', styles: { '--button-width': '10px'}}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Styling Elements via Component Parts
|
|
357
|
+
|
|
358
|
+
The goal of compound components is to expose one component per semantic element. Most of the time
|
|
359
|
+
this means a 1:1 relationship of a component and DOM element. Sometimes a semantic element contains
|
|
360
|
+
non-semantic elements for styling. An example might be a `<button>` with a icon for visual
|
|
361
|
+
reinforcement, and a label for a semantic label. The semantic element is the `<button>` while the
|
|
362
|
+
icon has no semantic value and the label automatically provides the semantic button with an
|
|
363
|
+
accessible name. In order to style the icon and label elements, you have to know the DOM structure
|
|
364
|
+
to target those specific elements in order to style it.
|
|
365
|
+
|
|
366
|
+
```jsx
|
|
367
|
+
import {createStencil} from '@workday/canvas-kit-styling';
|
|
368
|
+
|
|
369
|
+
const myButtonStencil = createStencil({
|
|
370
|
+
base: {
|
|
371
|
+
background: 'transparent',
|
|
372
|
+
i: {
|
|
373
|
+
// ...icon styles
|
|
374
|
+
},
|
|
375
|
+
span: {
|
|
376
|
+
// ...label styles
|
|
377
|
+
},
|
|
378
|
+
':hover': {
|
|
379
|
+
// ...hover button styles
|
|
380
|
+
i: {
|
|
381
|
+
// ...hover icon styles
|
|
382
|
+
},
|
|
383
|
+
span: {
|
|
384
|
+
// ...hover label styles
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const MyButton = ({children, ...elemProps}) => {
|
|
391
|
+
return (
|
|
392
|
+
<button {...handleCsProp(elemProps, myButtonStencil())}>
|
|
393
|
+
<i />
|
|
394
|
+
<span>{children}</span>
|
|
395
|
+
</button>
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Using Component Parts to Style Elements
|
|
401
|
+
|
|
402
|
+
To style elements in the render function, we'll need to choose what elements to add the parts to. In
|
|
403
|
+
the example below, we're able to spread the parts directly to elements. The Stencil will generate
|
|
404
|
+
the type and value most appropriate for the context the part is used. In the Stencil, the part is
|
|
405
|
+
represented by a string that looks like `[data-part="{partValue}"]` and in the render function, it
|
|
406
|
+
is an object that looks like `{'data-part': partValue}`.
|
|
407
|
+
|
|
408
|
+
```jsx
|
|
409
|
+
import {createStencil, handleCsProp} from '@workday/canvas-kit-styling';
|
|
410
|
+
|
|
411
|
+
const myButtonStencil = createStencil({
|
|
412
|
+
parts: {
|
|
413
|
+
icon: 'my-button-icon',
|
|
414
|
+
label: 'my-button-label',
|
|
415
|
+
},
|
|
416
|
+
base: ({iconPart, labelPart}) => ({
|
|
417
|
+
background: 'transparent',
|
|
418
|
+
[iconPart]: {
|
|
419
|
+
// `[data-part="my-button-icon"]`
|
|
420
|
+
// ...icon styles
|
|
421
|
+
},
|
|
422
|
+
[labelPart]: {
|
|
423
|
+
// `[data-part="my-button-label"]`
|
|
424
|
+
// ...label styles
|
|
425
|
+
},
|
|
426
|
+
'&:hover': {
|
|
427
|
+
// ...hover styles for button element
|
|
428
|
+
[iconPart]: {
|
|
429
|
+
// ...hover styles for icon part
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
}),
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const MyButton = ({children, ...elemProps}) => {
|
|
436
|
+
return (
|
|
437
|
+
<button {...handleCsProp(elemProps, myButtonStencil())}>
|
|
438
|
+
<i {...myButtonStencil.parts.icon} /> {/* data-part={my-button-icon} */}
|
|
439
|
+
<span {...myButtonStencil.parts.label}>{children}</span> {/* data-part={my-button-label} */}
|
|
440
|
+
</button>
|
|
441
|
+
);
|
|
442
|
+
};
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
As a reusable component, you can use component parts to style elements that are not exposed in the
|
|
446
|
+
API. Consumers can also use the type safe Stencil to target that element to style it as well. As a
|
|
447
|
+
general rule, a Stencil maps to a component. Multiple Stencils per component usually means nested
|
|
448
|
+
elements that are not targets for style overrides.
|
|
449
|
+
|
|
450
|
+
<InformationHighlight className="sb-unstyled" cs={{marginBlock: system.space.x4,}}>
|
|
451
|
+
<InformationHighlight.Icon />
|
|
452
|
+
<InformationHighlight.Heading>Note</InformationHighlight.Heading>
|
|
453
|
+
<InformationHighlight.Body>
|
|
454
|
+
While component parts are a way to give access to elements in order to style, they
|
|
455
|
+
should be used sparingly. Using component parts increases CSS specificity. A component part should
|
|
456
|
+
not be used on a nested component that has its own Stencil. The result will be any style
|
|
457
|
+
properties defined with a component part will have a higher specificity than other styles.
|
|
458
|
+
</InformationHighlight.Body>
|
|
459
|
+
</InformationHighlight>
|