@workday/canvas-kit-react 16.0.0-alpha.0499-next.0 → 16.0.0-alpha.0500-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/common/lib/CanvasProvider.tsx +156 -324
- package/common/lib/theming/README.md +49 -6
- package/common/lib/theming/brandScope.ts +566 -0
- package/common/lib/theming/index.ts +1 -0
- package/common/lib/theming/sanaTheme.ts +133 -0
- package/common/lib/theming/types.ts +292 -0
- package/dist/commonjs/common/lib/CanvasProvider.d.ts +296 -5
- package/dist/commonjs/common/lib/CanvasProvider.d.ts.map +1 -1
- package/dist/commonjs/common/lib/CanvasProvider.js +105 -287
- package/dist/commonjs/common/lib/theming/brandScope.d.ts +64 -0
- package/dist/commonjs/common/lib/theming/brandScope.d.ts.map +1 -0
- package/dist/commonjs/common/lib/theming/brandScope.js +499 -0
- package/dist/commonjs/common/lib/theming/index.d.ts +1 -0
- package/dist/commonjs/common/lib/theming/index.d.ts.map +1 -1
- package/dist/commonjs/common/lib/theming/index.js +1 -0
- package/dist/commonjs/common/lib/theming/sanaTheme.d.ts +33 -0
- package/dist/commonjs/common/lib/theming/sanaTheme.d.ts.map +1 -0
- package/dist/commonjs/common/lib/theming/sanaTheme.js +130 -0
- package/dist/commonjs/common/lib/theming/types.d.ts +162 -0
- package/dist/commonjs/common/lib/theming/types.d.ts.map +1 -1
- package/dist/commonjs/common/lib/theming/types.js +81 -1
- package/dist/commonjs/pill/lib/Pill.d.ts +2 -2
- package/dist/commonjs/popup/lib/hooks/usePopupStack.d.ts.map +1 -1
- package/dist/commonjs/popup/lib/hooks/usePopupStack.js +18 -16
- package/dist/commonjs/testing/lib/StaticStates.d.ts +2 -2
- package/dist/commonjs/testing/lib/StaticStates.d.ts.map +1 -1
- package/dist/commonjs/testing/lib/StaticStates.js +5 -2
- package/dist/es6/common/lib/CanvasProvider.d.ts +296 -5
- package/dist/es6/common/lib/CanvasProvider.d.ts.map +1 -1
- package/dist/es6/common/lib/CanvasProvider.js +82 -288
- package/dist/es6/common/lib/theming/brandScope.d.ts +64 -0
- package/dist/es6/common/lib/theming/brandScope.d.ts.map +1 -0
- package/dist/es6/common/lib/theming/brandScope.js +483 -0
- package/dist/es6/common/lib/theming/index.d.ts +1 -0
- package/dist/es6/common/lib/theming/index.d.ts.map +1 -1
- package/dist/es6/common/lib/theming/index.js +1 -0
- package/dist/es6/common/lib/theming/sanaTheme.d.ts +33 -0
- package/dist/es6/common/lib/theming/sanaTheme.d.ts.map +1 -0
- package/dist/es6/common/lib/theming/sanaTheme.js +127 -0
- package/dist/es6/common/lib/theming/types.d.ts +162 -0
- package/dist/es6/common/lib/theming/types.d.ts.map +1 -1
- package/dist/es6/common/lib/theming/types.js +78 -0
- package/dist/es6/pill/lib/Pill.d.ts +2 -2
- package/dist/es6/popup/lib/hooks/usePopupStack.d.ts.map +1 -1
- package/dist/es6/popup/lib/hooks/usePopupStack.js +13 -11
- package/dist/es6/testing/lib/StaticStates.d.ts +2 -2
- package/dist/es6/testing/lib/StaticStates.d.ts.map +1 -1
- package/dist/es6/testing/lib/StaticStates.js +6 -3
- package/package.json +4 -4
- package/popup/lib/hooks/usePopupStack.ts +13 -11
- package/testing/lib/StaticStates.tsx +11 -9
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import {colorSpace, maybeWrapCSSVariables} from '@workday/canvas-kit-styling';
|
|
4
|
+
import {brand, system} from '@workday/canvas-tokens-web';
|
|
5
|
+
|
|
6
|
+
import {defaultCanvasTheme} from './theme';
|
|
7
|
+
import {
|
|
8
|
+
CanvasActionBrandRamp,
|
|
9
|
+
CanvasBrandRamp,
|
|
10
|
+
CanvasNeutralBrandRamp,
|
|
11
|
+
CanvasNumericalBrandTheme,
|
|
12
|
+
CanvasProviderTheme,
|
|
13
|
+
CanvasTheme,
|
|
14
|
+
PartialEmotionCanvasTheme,
|
|
15
|
+
isNumericalTheme,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
/** True when a semantic theme intentionally passes a non-empty `canvas.palette`. */
|
|
19
|
+
export function hasExplicitSemanticPalette(
|
|
20
|
+
theme: CanvasProviderTheme
|
|
21
|
+
): theme is PartialEmotionCanvasTheme {
|
|
22
|
+
return (
|
|
23
|
+
!isNumericalTheme(theme) &&
|
|
24
|
+
!!theme.canvas?.palette &&
|
|
25
|
+
Object.keys(theme.canvas.palette).length > 0
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Tokens that change when a consumer sets only their primary brand color. */
|
|
30
|
+
export const BRAND_SCOPE_PRIMARY_BUNDLE = {
|
|
31
|
+
action: ['base', 'dark', 'darkest', 'accent'] as const,
|
|
32
|
+
accent: ['primary', 'action'] as const,
|
|
33
|
+
selected: {
|
|
34
|
+
fg: system.color.brand.fg.selected,
|
|
35
|
+
surface: system.color.brand.surface.selected,
|
|
36
|
+
},
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Independently brandable tokens — writable when explicitly provided,
|
|
41
|
+
* but NEVER derived from primary.main / brand.primary.600.
|
|
42
|
+
*/
|
|
43
|
+
export const BRAND_SCOPE_INDEPENDENT = {
|
|
44
|
+
focus: {
|
|
45
|
+
primary: system.color.brand.focus.primary,
|
|
46
|
+
brandToken: brand.primary500,
|
|
47
|
+
semanticKey: 'focusOutline' as const,
|
|
48
|
+
},
|
|
49
|
+
border: {
|
|
50
|
+
primary: system.color.brand.border.primary,
|
|
51
|
+
brandToken: brand.primary500,
|
|
52
|
+
semanticKey: 'focusOutline' as const,
|
|
53
|
+
},
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
const commonTokenMapping = {
|
|
57
|
+
focusOutline: brand.common.focus,
|
|
58
|
+
alertInner: brand.common.caution.inner,
|
|
59
|
+
alertOuter: brand.common.caution.outer,
|
|
60
|
+
errorInner: brand.common.critical,
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
type BrandColor = 'primary' | 'critical' | 'caution' | 'positive' | 'neutral';
|
|
64
|
+
|
|
65
|
+
const brandColorMapping: Record<string, BrandColor> = {
|
|
66
|
+
primary: 'primary',
|
|
67
|
+
error: 'critical',
|
|
68
|
+
success: 'positive',
|
|
69
|
+
alert: 'caution',
|
|
70
|
+
neutral: 'neutral',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Brand ramp keys defined in Sana CSS but not yet exported from canvas-tokens-web. */
|
|
74
|
+
const EXTENDED_BRAND_TOKEN_MAP: Record<string, string> = {
|
|
75
|
+
neutral150: '--cnvs-brand-neutral-150',
|
|
76
|
+
neutral850: '--cnvs-brand-neutral-850',
|
|
77
|
+
neutralA150: '--cnvs-brand-neutral-a150',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const setStyleVar = (style: React.CSSProperties, token: string, value: string) => {
|
|
81
|
+
// @ts-ignore - CSS custom property key
|
|
82
|
+
style[token] = maybeWrapCSSVariables(value);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Maps a numerical brand ramp onto `brand.<color><rampKey>` CSS variables. */
|
|
86
|
+
export function writeNumericalBrandRamp(
|
|
87
|
+
color: BrandColor | 'action',
|
|
88
|
+
ramp: CanvasBrandRamp | CanvasNeutralBrandRamp | CanvasActionBrandRamp | undefined,
|
|
89
|
+
style: React.CSSProperties,
|
|
90
|
+
options?: {skipKeys?: Set<string>}
|
|
91
|
+
) {
|
|
92
|
+
if (!ramp) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
(
|
|
96
|
+
Object.keys(ramp) as Array<
|
|
97
|
+
keyof (CanvasBrandRamp | CanvasNeutralBrandRamp | CanvasActionBrandRamp)
|
|
98
|
+
>
|
|
99
|
+
).forEach(rampKey => {
|
|
100
|
+
if (options?.skipKeys?.has(String(rampKey))) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const value = ramp[rampKey as keyof typeof ramp];
|
|
104
|
+
if (value == null) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const token =
|
|
108
|
+
color === 'action'
|
|
109
|
+
? brand.action[rampKey as keyof typeof brand.action]
|
|
110
|
+
: // @ts-ignore - dynamic token lookup
|
|
111
|
+
(brand[`${color}${rampKey}`] ?? EXTENDED_BRAND_TOKEN_MAP[`${color}${rampKey}`]);
|
|
112
|
+
if (token) {
|
|
113
|
+
setStyleVar(style, token, value);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Called when consumer sets only `primary.main` or `brand.primary['600']`. */
|
|
119
|
+
export function applyPrimaryBrandBundle(primaryColor: string, style: React.CSSProperties) {
|
|
120
|
+
const value = maybeWrapCSSVariables(primaryColor);
|
|
121
|
+
|
|
122
|
+
setStyleVar(style, brand.primary.base, value);
|
|
123
|
+
setStyleVar(style, brand.primary600, value);
|
|
124
|
+
setStyleVar(style, brand.action.base, value);
|
|
125
|
+
|
|
126
|
+
if (system.color.brand.accent.primary) {
|
|
127
|
+
setStyleVar(style, system.color.brand.accent.primary, value);
|
|
128
|
+
}
|
|
129
|
+
if (system.color.brand.accent.action) {
|
|
130
|
+
setStyleVar(style, system.color.brand.accent.action, value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const selectedFg = colorSpace.pressed({
|
|
134
|
+
color: `var(${brand.primary600})`,
|
|
135
|
+
fallback: value,
|
|
136
|
+
colorType: 'accent',
|
|
137
|
+
});
|
|
138
|
+
const selectedSurface = colorSpace.darken({
|
|
139
|
+
color: system.color.bg.default,
|
|
140
|
+
fallback: 'white',
|
|
141
|
+
mixinColor: `var(${brand.primary600})`,
|
|
142
|
+
mixinValue: '0.11',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
setStyleVar(style, brand.primary700, selectedFg);
|
|
146
|
+
setStyleVar(style, brand.primary.dark, selectedFg);
|
|
147
|
+
setStyleVar(style, brand.primaryA50, selectedSurface);
|
|
148
|
+
|
|
149
|
+
if (BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg) {
|
|
150
|
+
setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg, selectedFg);
|
|
151
|
+
}
|
|
152
|
+
if (BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface) {
|
|
153
|
+
setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface, selectedSurface);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const hoverColor = colorSpace.hover({
|
|
157
|
+
color: `var(${brand.action.base})`,
|
|
158
|
+
fallback: value,
|
|
159
|
+
colorType: 'accent',
|
|
160
|
+
});
|
|
161
|
+
const pressedColor = colorSpace.pressed({
|
|
162
|
+
color: `var(${brand.action.base})`,
|
|
163
|
+
fallback: value,
|
|
164
|
+
colorType: 'accent',
|
|
165
|
+
});
|
|
166
|
+
setStyleVar(style, brand.action.dark, hoverColor);
|
|
167
|
+
setStyleVar(style, brand.action.darkest, pressedColor);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Called when consumer sets only `error.main` or `brand.critical['600']`. */
|
|
171
|
+
export function applyCriticalBrandBundle(criticalColor: string, style: React.CSSProperties) {
|
|
172
|
+
const value = maybeWrapCSSVariables(criticalColor);
|
|
173
|
+
|
|
174
|
+
setStyleVar(style, brand.error.base, value);
|
|
175
|
+
setStyleVar(style, brand.critical600, value);
|
|
176
|
+
setStyleVar(style, brand.critical500, value);
|
|
177
|
+
|
|
178
|
+
if (system.color.brand.accent.critical) {
|
|
179
|
+
setStyleVar(style, system.color.brand.accent.critical, value);
|
|
180
|
+
}
|
|
181
|
+
if (system.color.brand.fg.critical?.default) {
|
|
182
|
+
setStyleVar(style, system.color.brand.fg.critical.default, value);
|
|
183
|
+
}
|
|
184
|
+
if (system.color.brand.border.critical) {
|
|
185
|
+
setStyleVar(style, system.color.brand.border.critical, value);
|
|
186
|
+
}
|
|
187
|
+
if (system.color.brand.focus.critical) {
|
|
188
|
+
setStyleVar(style, system.color.brand.focus.critical, value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Called when consumer sets only `alert.main` or `brand.caution['400']`. */
|
|
193
|
+
export function applyCautionBrandBundle(cautionColor: string, style: React.CSSProperties) {
|
|
194
|
+
const value = maybeWrapCSSVariables(cautionColor);
|
|
195
|
+
|
|
196
|
+
setStyleVar(style, brand.alert.base, value);
|
|
197
|
+
setStyleVar(style, brand.caution400, value);
|
|
198
|
+
setStyleVar(style, brand.caution500, value);
|
|
199
|
+
|
|
200
|
+
if (system.color.brand.accent.caution) {
|
|
201
|
+
setStyleVar(style, system.color.brand.accent.caution, value);
|
|
202
|
+
}
|
|
203
|
+
if (system.color.brand.fg.caution?.default) {
|
|
204
|
+
setStyleVar(style, system.color.brand.fg.caution.default, value);
|
|
205
|
+
}
|
|
206
|
+
if (system.color.brand.border.caution) {
|
|
207
|
+
setStyleVar(style, system.color.brand.border.caution, value);
|
|
208
|
+
}
|
|
209
|
+
if (system.color.brand.focus.caution?.inner) {
|
|
210
|
+
setStyleVar(style, system.color.brand.focus.caution.inner, value);
|
|
211
|
+
}
|
|
212
|
+
if (system.color.brand.focus.caution?.outer) {
|
|
213
|
+
setStyleVar(style, system.color.brand.focus.caution.outer, value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Called when consumer sets only `success.main` or `brand.positive['600']`. */
|
|
218
|
+
export function applyPositiveBrandBundle(positiveColor: string, style: React.CSSProperties) {
|
|
219
|
+
const value = maybeWrapCSSVariables(positiveColor);
|
|
220
|
+
|
|
221
|
+
setStyleVar(style, brand.success.base, value);
|
|
222
|
+
setStyleVar(style, brand.positive600, value);
|
|
223
|
+
|
|
224
|
+
if (system.color.brand.accent.positive) {
|
|
225
|
+
setStyleVar(style, system.color.brand.accent.positive, value);
|
|
226
|
+
}
|
|
227
|
+
if (system.color.brand.fg.positive?.default) {
|
|
228
|
+
setStyleVar(style, system.color.brand.fg.positive.default, value);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Called when consumer sets only `neutral.main` or `brand.neutral['600']`. */
|
|
233
|
+
export function applyNeutralBrandBundle(neutralColor: string, style: React.CSSProperties) {
|
|
234
|
+
const value = maybeWrapCSSVariables(neutralColor);
|
|
235
|
+
|
|
236
|
+
setStyleVar(style, brand.neutral.base, value);
|
|
237
|
+
setStyleVar(style, brand.neutral600, value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Writes first-class selected shortcuts from numerical theme input. */
|
|
241
|
+
export function writeSelectedShortcuts(
|
|
242
|
+
selected: CanvasNumericalBrandTheme['selected'] | undefined,
|
|
243
|
+
style: React.CSSProperties
|
|
244
|
+
) {
|
|
245
|
+
if (!selected) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (selected.fg && BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg) {
|
|
249
|
+
setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg, selected.fg);
|
|
250
|
+
}
|
|
251
|
+
if (selected.surface && BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface) {
|
|
252
|
+
setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface, selected.surface);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function writeFocusBorderBundle(value: string, style: React.CSSProperties) {
|
|
257
|
+
setStyleVar(style, brand.common.focusOutline, value);
|
|
258
|
+
setStyleVar(style, brand.primary500, value);
|
|
259
|
+
if (BRAND_SCOPE_INDEPENDENT.focus.primary) {
|
|
260
|
+
setStyleVar(style, BRAND_SCOPE_INDEPENDENT.focus.primary, value);
|
|
261
|
+
}
|
|
262
|
+
if (BRAND_SCOPE_INDEPENDENT.border.primary) {
|
|
263
|
+
setStyleVar(style, BRAND_SCOPE_INDEPENDENT.border.primary, value);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Writes focus/border from explicit common or brand.primary.500 input — never from primary.main. */
|
|
268
|
+
export function writeIndependentBrandTokens(
|
|
269
|
+
theme: CanvasProviderTheme | undefined,
|
|
270
|
+
style: React.CSSProperties
|
|
271
|
+
) {
|
|
272
|
+
if (!theme) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (isNumericalTheme(theme)) {
|
|
277
|
+
const focusValue = theme.brand?.primary?.['500'];
|
|
278
|
+
if (focusValue) {
|
|
279
|
+
writeFocusBorderBundle(focusValue, style);
|
|
280
|
+
}
|
|
281
|
+
if (theme.system) {
|
|
282
|
+
writeSystemBrandOverrides(theme.system, style);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const common = theme.canvas?.palette?.common;
|
|
288
|
+
if (!common) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
(['focusOutline', 'alertInner', 'alertOuter', 'errorInner'] as const).forEach(key => {
|
|
293
|
+
const rawValue = common[key];
|
|
294
|
+
if (rawValue == null) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (rawValue === defaultCanvasTheme.palette.common[key]) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const value = maybeWrapCSSVariables(rawValue);
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
setStyleVar(style, brand.common[key], value);
|
|
304
|
+
// @ts-ignore
|
|
305
|
+
setStyleVar(style, commonTokenMapping[key], value);
|
|
306
|
+
|
|
307
|
+
if (key === 'focusOutline') {
|
|
308
|
+
writeFocusBorderBundle(value, style);
|
|
309
|
+
}
|
|
310
|
+
if (key === 'alertInner' && system.color.brand.focus.caution?.inner) {
|
|
311
|
+
setStyleVar(style, system.color.brand.focus.caution.inner, value);
|
|
312
|
+
}
|
|
313
|
+
if (key === 'alertOuter' && system.color.brand.border.caution) {
|
|
314
|
+
setStyleVar(style, system.color.brand.border.caution, value);
|
|
315
|
+
}
|
|
316
|
+
if (key === 'errorInner') {
|
|
317
|
+
if (system.color.brand.focus.critical) {
|
|
318
|
+
setStyleVar(style, system.color.brand.focus.critical, value);
|
|
319
|
+
}
|
|
320
|
+
if (system.color.brand.border.critical) {
|
|
321
|
+
setStyleVar(style, system.color.brand.border.critical, value);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Walks nested system override object and sets matching system CSS vars. */
|
|
328
|
+
export function writeSystemBrandOverrides(
|
|
329
|
+
systemOverrides: {color?: {brand?: Record<string, unknown>}} | undefined,
|
|
330
|
+
style: React.CSSProperties
|
|
331
|
+
) {
|
|
332
|
+
const brandOverrides = systemOverrides?.color?.brand;
|
|
333
|
+
if (!brandOverrides) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const walk = (node: Record<string, unknown>, path: string[]) => {
|
|
338
|
+
Object.entries(node).forEach(([key, val]) => {
|
|
339
|
+
if (val && typeof val === 'object') {
|
|
340
|
+
walk(val as Record<string, unknown>, [...path, key]);
|
|
341
|
+
} else if (typeof val === 'string') {
|
|
342
|
+
let tokenNode: unknown = system.color.brand;
|
|
343
|
+
for (const seg of [...path, key]) {
|
|
344
|
+
// @ts-ignore
|
|
345
|
+
tokenNode = tokenNode?.[seg];
|
|
346
|
+
}
|
|
347
|
+
if (typeof tokenNode === 'string') {
|
|
348
|
+
const cssVarName = tokenNode.match(/--[\w-]+/)?.[0] ?? tokenNode;
|
|
349
|
+
setStyleVar(style, cssVarName, val);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
walk(brandOverrides, []);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const mappedKeys = {
|
|
358
|
+
lightest: 'lightest',
|
|
359
|
+
lighter: 'lighter',
|
|
360
|
+
light: 'light',
|
|
361
|
+
main: 'base',
|
|
362
|
+
dark: 'dark',
|
|
363
|
+
darkest: 'darkest',
|
|
364
|
+
contrast: 'accent',
|
|
365
|
+
} as const;
|
|
366
|
+
|
|
367
|
+
const numericalTokenMapping = {
|
|
368
|
+
lightest: '25',
|
|
369
|
+
lighter: '50',
|
|
370
|
+
light: '200',
|
|
371
|
+
main: '600',
|
|
372
|
+
dark: '700',
|
|
373
|
+
darkest: '800',
|
|
374
|
+
} as const;
|
|
375
|
+
|
|
376
|
+
/** Full-scope semantic writer — filled palette compared against defaults unless `writeAll`. */
|
|
377
|
+
export function writeSemanticTheme(
|
|
378
|
+
palette: CanvasTheme['palette'],
|
|
379
|
+
style: React.CSSProperties,
|
|
380
|
+
options?: {writeAll?: boolean}
|
|
381
|
+
) {
|
|
382
|
+
const writeAll = options?.writeAll ?? false;
|
|
383
|
+
|
|
384
|
+
(['common', 'primary', 'error', 'alert', 'success', 'neutral'] as const).forEach(color => {
|
|
385
|
+
if (color === 'common') {
|
|
386
|
+
(['focusOutline', 'alertInner', 'alertOuter', 'errorInner'] as const).forEach(key => {
|
|
387
|
+
if (writeAll || palette.common[key] !== defaultCanvasTheme.palette.common[key]) {
|
|
388
|
+
const value = maybeWrapCSSVariables(palette.common[key]);
|
|
389
|
+
// @ts-ignore
|
|
390
|
+
setStyleVar(style, brand.common[key], value);
|
|
391
|
+
// @ts-ignore
|
|
392
|
+
setStyleVar(style, commonTokenMapping[key], value);
|
|
393
|
+
|
|
394
|
+
if (key === 'focusOutline') {
|
|
395
|
+
writeFocusBorderBundle(value, style);
|
|
396
|
+
}
|
|
397
|
+
if (key === 'alertInner' && system.color.brand.focus.caution?.inner) {
|
|
398
|
+
setStyleVar(style, system.color.brand.focus.caution.inner, value);
|
|
399
|
+
}
|
|
400
|
+
if (key === 'alertOuter' && system.color.brand.border.caution) {
|
|
401
|
+
setStyleVar(style, system.color.brand.border.caution, value);
|
|
402
|
+
}
|
|
403
|
+
if (key === 'errorInner') {
|
|
404
|
+
if (system.color.brand.focus.critical) {
|
|
405
|
+
setStyleVar(style, system.color.brand.focus.critical, value);
|
|
406
|
+
}
|
|
407
|
+
if (system.color.brand.border.critical) {
|
|
408
|
+
setStyleVar(style, system.color.brand.border.critical, value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
} else {
|
|
414
|
+
(['lightest', 'lighter', 'light', 'main', 'dark', 'darkest', 'contrast'] as const).forEach(
|
|
415
|
+
key => {
|
|
416
|
+
// @ts-ignore
|
|
417
|
+
if (writeAll || palette[color][key] !== defaultCanvasTheme.palette[color][key]) {
|
|
418
|
+
// @ts-ignore
|
|
419
|
+
const value = maybeWrapCSSVariables(palette[color][key]);
|
|
420
|
+
// @ts-ignore
|
|
421
|
+
setStyleVar(style, brand[color][mappedKeys[key]], value);
|
|
422
|
+
|
|
423
|
+
if (key !== 'contrast' && key in numericalTokenMapping) {
|
|
424
|
+
const newBrandColor = brandColorMapping[color];
|
|
425
|
+
const numericalSuffix =
|
|
426
|
+
numericalTokenMapping[key as keyof typeof numericalTokenMapping];
|
|
427
|
+
// @ts-ignore
|
|
428
|
+
const numericalToken = brand[newBrandColor + numericalSuffix];
|
|
429
|
+
if (numericalToken) {
|
|
430
|
+
setStyleVar(style, numericalToken, value);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (key === 'main') {
|
|
434
|
+
// @ts-ignore
|
|
435
|
+
const systemAccentToken = system.color.brand.accent[newBrandColor];
|
|
436
|
+
if (systemAccentToken) {
|
|
437
|
+
setStyleVar(style, systemAccentToken, value);
|
|
438
|
+
}
|
|
439
|
+
// @ts-ignore
|
|
440
|
+
const systemFgToken = system.color.brand.fg[newBrandColor]?.default;
|
|
441
|
+
if (systemFgToken) {
|
|
442
|
+
setStyleVar(style, systemFgToken, value);
|
|
443
|
+
}
|
|
444
|
+
} else if (key === 'dark') {
|
|
445
|
+
// @ts-ignore
|
|
446
|
+
const systemFgStrongToken = system.color.brand.fg[newBrandColor]?.strong;
|
|
447
|
+
if (systemFgStrongToken) {
|
|
448
|
+
setStyleVar(style, systemFgStrongToken, value);
|
|
449
|
+
}
|
|
450
|
+
if (newBrandColor === 'primary' && system.color.brand.fg.selected) {
|
|
451
|
+
setStyleVar(style, system.color.brand.fg.selected, value);
|
|
452
|
+
}
|
|
453
|
+
if (newBrandColor === 'critical') {
|
|
454
|
+
if (system.color.brand.focus.critical) {
|
|
455
|
+
setStyleVar(style, system.color.brand.focus.critical, value);
|
|
456
|
+
}
|
|
457
|
+
if (system.color.brand.border.critical) {
|
|
458
|
+
setStyleVar(style, system.color.brand.border.critical, value);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (newBrandColor === 'caution') {
|
|
462
|
+
if (system.color.brand.focus.caution?.outer) {
|
|
463
|
+
setStyleVar(style, system.color.brand.focus.caution.outer, value);
|
|
464
|
+
}
|
|
465
|
+
if (system.color.brand.border.caution) {
|
|
466
|
+
setStyleVar(style, system.color.brand.border.caution, value);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
} else if (key === 'lighter') {
|
|
470
|
+
// @ts-ignore
|
|
471
|
+
const surfaceStrongToken = system.color.brand.surface[newBrandColor]?.strong;
|
|
472
|
+
if (surfaceStrongToken) {
|
|
473
|
+
setStyleVar(style, surfaceStrongToken, value);
|
|
474
|
+
}
|
|
475
|
+
if (newBrandColor === 'primary' && system.color.brand.surface.selected) {
|
|
476
|
+
setStyleVar(style, system.color.brand.surface.selected, value);
|
|
477
|
+
}
|
|
478
|
+
} else if (key === 'lightest') {
|
|
479
|
+
// @ts-ignore
|
|
480
|
+
const surfaceDefaultToken = system.color.brand.surface[newBrandColor]?.default;
|
|
481
|
+
if (surfaceDefaultToken) {
|
|
482
|
+
setStyleVar(style, surfaceDefaultToken, value);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Writes numerical theme — brand scope applies per-family shortcuts; remaining keys write 1:1. */
|
|
494
|
+
export function writeNumericalTheme(
|
|
495
|
+
theme: CanvasNumericalBrandTheme,
|
|
496
|
+
style: React.CSSProperties,
|
|
497
|
+
scope: 'brand' | 'full'
|
|
498
|
+
) {
|
|
499
|
+
const brandPalette = theme.brand;
|
|
500
|
+
const skipRamp: Partial<Record<BrandColor | 'action', Set<string>>> = {};
|
|
501
|
+
|
|
502
|
+
if (scope === 'brand' && brandPalette) {
|
|
503
|
+
if (brandPalette.primary?.['600'] && Object.keys(brandPalette.primary).length === 1) {
|
|
504
|
+
applyPrimaryBrandBundle(brandPalette.primary['600'], style);
|
|
505
|
+
skipRamp.primary = new Set(['600']);
|
|
506
|
+
// Do not skip action keys — explicit `brand.action.*` must win over derived shades
|
|
507
|
+
// written by applyPrimaryBrandBundle (writeNumericalBrandRamp runs after).
|
|
508
|
+
}
|
|
509
|
+
// Only the main ramp key triggers the family shortcut. Lone `'500'` (focus/border) must
|
|
510
|
+
// write 1:1 and must not overwrite critical600 / caution400 via the bundle.
|
|
511
|
+
const critical = brandPalette.critical;
|
|
512
|
+
if (critical?.['600'] && Object.keys(critical).length === 1) {
|
|
513
|
+
applyCriticalBrandBundle(critical['600'], style);
|
|
514
|
+
skipRamp.critical = new Set(['600']);
|
|
515
|
+
}
|
|
516
|
+
const caution = brandPalette.caution;
|
|
517
|
+
if (caution?.['400'] && Object.keys(caution).length === 1) {
|
|
518
|
+
applyCautionBrandBundle(caution['400'], style);
|
|
519
|
+
skipRamp.caution = new Set(['400']);
|
|
520
|
+
}
|
|
521
|
+
if (brandPalette.positive?.['600'] && Object.keys(brandPalette.positive).length === 1) {
|
|
522
|
+
applyPositiveBrandBundle(brandPalette.positive['600'], style);
|
|
523
|
+
skipRamp.positive = new Set(['600']);
|
|
524
|
+
}
|
|
525
|
+
if (brandPalette.neutral?.['600'] && Object.keys(brandPalette.neutral).length === 1) {
|
|
526
|
+
applyNeutralBrandBundle(brandPalette.neutral['600'], style);
|
|
527
|
+
skipRamp.neutral = new Set(['600']);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (brandPalette) {
|
|
532
|
+
(['primary', 'critical', 'caution', 'positive', 'neutral', 'action'] as const).forEach(
|
|
533
|
+
color => {
|
|
534
|
+
writeNumericalBrandRamp(color, brandPalette[color], style, {skipKeys: skipRamp[color]});
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
writeSelectedShortcuts(theme.selected, style);
|
|
540
|
+
writeIndependentBrandTokens(theme, style);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** Brand-scope semantic path — reads raw input only, not useTheme-filled palette. */
|
|
544
|
+
export function writeBrandScopeSemantic(
|
|
545
|
+
theme: PartialEmotionCanvasTheme,
|
|
546
|
+
style: React.CSSProperties
|
|
547
|
+
) {
|
|
548
|
+
const palette = theme.canvas?.palette;
|
|
549
|
+
|
|
550
|
+
if (palette?.primary?.main) {
|
|
551
|
+
applyPrimaryBrandBundle(palette.primary.main, style);
|
|
552
|
+
}
|
|
553
|
+
if (palette?.error?.main) {
|
|
554
|
+
applyCriticalBrandBundle(palette.error.main, style);
|
|
555
|
+
}
|
|
556
|
+
if (palette?.alert?.main) {
|
|
557
|
+
applyCautionBrandBundle(palette.alert.main, style);
|
|
558
|
+
}
|
|
559
|
+
if (palette?.success?.main) {
|
|
560
|
+
applyPositiveBrandBundle(palette.success.main, style);
|
|
561
|
+
}
|
|
562
|
+
if (palette?.neutral?.main) {
|
|
563
|
+
applyNeutralBrandBundle(palette.neutral.main, style);
|
|
564
|
+
}
|
|
565
|
+
writeIndependentBrandTokens(theme, style);
|
|
566
|
+
}
|
|
@@ -14,6 +14,7 @@ export {default as styled, type StyleRewriteFn, filterOutProps} from './styled';
|
|
|
14
14
|
* For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
|
|
15
15
|
*/
|
|
16
16
|
export * from './theme';
|
|
17
|
+
export * from './sanaTheme';
|
|
17
18
|
/**
|
|
18
19
|
* @deprecated ⚠️ `useTheme` and `getTheme` are deprecated. Use CSS variables from `@workday/canvas-tokens-web` instead.
|
|
19
20
|
* For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
|