@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.
Files changed (51) hide show
  1. package/common/lib/CanvasProvider.tsx +156 -324
  2. package/common/lib/theming/README.md +49 -6
  3. package/common/lib/theming/brandScope.ts +566 -0
  4. package/common/lib/theming/index.ts +1 -0
  5. package/common/lib/theming/sanaTheme.ts +133 -0
  6. package/common/lib/theming/types.ts +292 -0
  7. package/dist/commonjs/common/lib/CanvasProvider.d.ts +296 -5
  8. package/dist/commonjs/common/lib/CanvasProvider.d.ts.map +1 -1
  9. package/dist/commonjs/common/lib/CanvasProvider.js +105 -287
  10. package/dist/commonjs/common/lib/theming/brandScope.d.ts +64 -0
  11. package/dist/commonjs/common/lib/theming/brandScope.d.ts.map +1 -0
  12. package/dist/commonjs/common/lib/theming/brandScope.js +499 -0
  13. package/dist/commonjs/common/lib/theming/index.d.ts +1 -0
  14. package/dist/commonjs/common/lib/theming/index.d.ts.map +1 -1
  15. package/dist/commonjs/common/lib/theming/index.js +1 -0
  16. package/dist/commonjs/common/lib/theming/sanaTheme.d.ts +33 -0
  17. package/dist/commonjs/common/lib/theming/sanaTheme.d.ts.map +1 -0
  18. package/dist/commonjs/common/lib/theming/sanaTheme.js +130 -0
  19. package/dist/commonjs/common/lib/theming/types.d.ts +162 -0
  20. package/dist/commonjs/common/lib/theming/types.d.ts.map +1 -1
  21. package/dist/commonjs/common/lib/theming/types.js +81 -1
  22. package/dist/commonjs/pill/lib/Pill.d.ts +2 -2
  23. package/dist/commonjs/popup/lib/hooks/usePopupStack.d.ts.map +1 -1
  24. package/dist/commonjs/popup/lib/hooks/usePopupStack.js +18 -16
  25. package/dist/commonjs/testing/lib/StaticStates.d.ts +2 -2
  26. package/dist/commonjs/testing/lib/StaticStates.d.ts.map +1 -1
  27. package/dist/commonjs/testing/lib/StaticStates.js +5 -2
  28. package/dist/es6/common/lib/CanvasProvider.d.ts +296 -5
  29. package/dist/es6/common/lib/CanvasProvider.d.ts.map +1 -1
  30. package/dist/es6/common/lib/CanvasProvider.js +82 -288
  31. package/dist/es6/common/lib/theming/brandScope.d.ts +64 -0
  32. package/dist/es6/common/lib/theming/brandScope.d.ts.map +1 -0
  33. package/dist/es6/common/lib/theming/brandScope.js +483 -0
  34. package/dist/es6/common/lib/theming/index.d.ts +1 -0
  35. package/dist/es6/common/lib/theming/index.d.ts.map +1 -1
  36. package/dist/es6/common/lib/theming/index.js +1 -0
  37. package/dist/es6/common/lib/theming/sanaTheme.d.ts +33 -0
  38. package/dist/es6/common/lib/theming/sanaTheme.d.ts.map +1 -0
  39. package/dist/es6/common/lib/theming/sanaTheme.js +127 -0
  40. package/dist/es6/common/lib/theming/types.d.ts +162 -0
  41. package/dist/es6/common/lib/theming/types.d.ts.map +1 -1
  42. package/dist/es6/common/lib/theming/types.js +78 -0
  43. package/dist/es6/pill/lib/Pill.d.ts +2 -2
  44. package/dist/es6/popup/lib/hooks/usePopupStack.d.ts.map +1 -1
  45. package/dist/es6/popup/lib/hooks/usePopupStack.js +13 -11
  46. package/dist/es6/testing/lib/StaticStates.d.ts +2 -2
  47. package/dist/es6/testing/lib/StaticStates.d.ts.map +1 -1
  48. package/dist/es6/testing/lib/StaticStates.js +6 -3
  49. package/package.json +4 -4
  50. package/popup/lib/hooks/usePopupStack.ts +13 -11
  51. package/testing/lib/StaticStates.tsx +11 -9
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Sana Canvas theme presets.
3
+ *
4
+ * Sana is a distinct theme from classic Canvas — not a delta on `defaultCanvasTheme`.
5
+ * Full visual treatment (fonts, shapes, system surfaces) comes from global CSS:
6
+ * import `@workday/canvas-tokens-web/css/sana/_variables.css` and set
7
+ * `data-theme="sana-canvas"` on `<html>`.
8
+ *
9
+ * These JS presets reference Sana brand CSS variables so `CanvasProvider` can forward
10
+ * them to popup containers (menus, selects, modals). Keep in sync with
11
+ * `@workday/canvas-tokens-web/css/sana/_variables.css`.
12
+ *
13
+ * | Preset | Use case |
14
+ * |--------|----------|
15
+ * | `sanaCanvasNumericalTheme` | Numerical `brand` shape for popup forwarding |
16
+ * | `sanaCanvasProviderTheme` | Same — pass to root `CanvasProvider` when `<html>` is unavailable |
17
+ */
18
+ import {base, brand} from '@workday/canvas-tokens-web';
19
+
20
+ import type {CanvasNumericalBrandTheme} from './types';
21
+
22
+ /** Reference a canvas-tokens CSS variable (resolves under `[data-theme="sana-canvas"]`). */
23
+ const varRef = (token: string) => `var(${token})`;
24
+
25
+ /**
26
+ * Sana extends the neutral ramp with steps not yet exported from canvas-tokens-web JS.
27
+ * Defined in `@workday/canvas-tokens-web/css/sana/_variables.css`.
28
+ */
29
+ const sanaBrandNeutral = {
30
+ '150': '--cnvs-brand-neutral-150',
31
+ '850': '--cnvs-brand-neutral-850',
32
+ A150: '--cnvs-brand-neutral-a150',
33
+ } as const;
34
+
35
+ /**
36
+ * Sana Canvas brand tokens for scoped `CanvasProvider` / popup forwarding.
37
+ * Values are `var()` references to Sana brand variables — not merged from `defaultCanvasTheme`.
38
+ */
39
+ export const sanaCanvasNumericalTheme: CanvasNumericalBrandTheme = {
40
+ // Explicit brand vars only — multi-key ramps write 1:1; no system shortcut bundles run.
41
+ themeScope: 'brand',
42
+ brand: {
43
+ action: {
44
+ base: varRef(brand.neutral975),
45
+ dark: varRef(brand.neutral950),
46
+ darkest: varRef(brand.neutral900),
47
+ accent: varRef(base.neutral0),
48
+ lightest: varRef(brand.neutral25),
49
+ lighter: varRef(brand.neutral50),
50
+ light: varRef(brand.neutral200),
51
+ },
52
+ neutral: {
53
+ '25': varRef(brand.neutral25),
54
+ '50': varRef(brand.neutral50),
55
+ '100': varRef(brand.neutral100),
56
+ '150': varRef(sanaBrandNeutral['150']),
57
+ '200': varRef(brand.neutral200),
58
+ '300': varRef(brand.neutral300),
59
+ '400': varRef(brand.neutral400),
60
+ '500': varRef(brand.neutral500),
61
+ '600': varRef(brand.neutral600),
62
+ '700': varRef(brand.neutral700),
63
+ '800': varRef(brand.neutral800),
64
+ '850': varRef(sanaBrandNeutral['850']),
65
+ '900': varRef(brand.neutral900),
66
+ '950': varRef(brand.neutral950),
67
+ '975': varRef(brand.neutral975),
68
+ A25: varRef(brand.neutralA25),
69
+ A50: varRef(brand.neutralA50),
70
+ A100: varRef(brand.neutralA100),
71
+ A150: varRef(sanaBrandNeutral.A150),
72
+ A200: varRef(brand.neutralA200),
73
+ },
74
+ primary: {
75
+ '500': varRef(brand.primary500),
76
+ '600': varRef(brand.primary600),
77
+ '700': varRef(brand.primary700),
78
+ A25: varRef(brand.primaryA25),
79
+ A50: varRef(brand.primaryA50),
80
+ A100: varRef(brand.primaryA100),
81
+ },
82
+ critical: {
83
+ '500': varRef(brand.critical500),
84
+ '600': varRef(brand.critical600),
85
+ '700': varRef(brand.critical700),
86
+ A25: varRef(brand.criticalA25),
87
+ A50: varRef(brand.criticalA50),
88
+ },
89
+ caution: {
90
+ '400': varRef(brand.caution400),
91
+ '500': varRef(brand.caution500),
92
+ A25: varRef(brand.cautionA25),
93
+ A50: varRef(brand.cautionA50),
94
+ },
95
+ positive: {
96
+ '600': varRef(brand.positive600),
97
+ '800': varRef(brand.positive800),
98
+ A25: varRef(brand.positiveA25),
99
+ A50: varRef(brand.positiveA50),
100
+ },
101
+ },
102
+ selected: {
103
+ fg: varRef(brand.neutralA900),
104
+ surface: varRef(brand.neutralA100),
105
+ },
106
+ };
107
+
108
+ /**
109
+ * Pass to root `CanvasProvider` to forward Sana brand CSS variables onto popup containers
110
+ * (menus, selects, modals, tooltips).
111
+ *
112
+ * **When to use it**
113
+ * - **Required** when you cannot set `data-theme="sana-canvas"` on `<html>` (embedded apps,
114
+ * microfrontends, third-party shells). Popups portal to `document.body` and will not inherit
115
+ * a nested `data-theme` — this preset copies Sana brand vars onto the popup stack container.
116
+ * - Also useful in tests without global Sana CSS, or custom popup hosts outside normal cascade.
117
+ *
118
+ * **When you can skip it**
119
+ * - Prefer setting `data-theme="sana-canvas"` on `<html>` with Sana CSS imported. Popups then
120
+ * inherit brand variables from the document and no `theme` prop is needed.
121
+ *
122
+ * @example
123
+ * ```tsx
124
+ * // Preferred — control <html>
125
+ * import '@workday/canvas-tokens-web/css/sana/_variables.css';
126
+ * // <html data-theme="sana-canvas">
127
+ * <CanvasProvider><App /></CanvasProvider>
128
+ *
129
+ * // No access to <html> — required for popup parity
130
+ * <CanvasProvider theme={sanaCanvasProviderTheme}><App /></CanvasProvider>
131
+ * ```
132
+ */
133
+ export const sanaCanvasProviderTheme = sanaCanvasNumericalTheme;
@@ -281,3 +281,295 @@ declare module '@emotion/react' {
281
281
  * @deprecated ⚠️ `EmotionCanvasTheme` is deprecated. In previous versions of Canvas Kit, we allowed teams to pass a theme object, this supported [Emotion's theming](https://emotion.sh/docs/theming). Now that we're shifting to a global theming approach based on CSS variables, we advise to no longer using the theme prop. For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
282
282
  */
283
283
  export type EmotionCanvasTheme = {canvas: CanvasTheme};
284
+
285
+ /**
286
+ * Numerical brand ramp keys. Each key maps 1:1 to a `--cnvs-brand-{family}-{key}` CSS variable.
287
+ *
288
+ * Common keys:
289
+ * - `'600'` — main accent / button fill / default brand fg
290
+ * - `'700'` — strong fg / selected text (when not using `selected.fg`)
291
+ * - `'500'` — focus rings and border primary (independent of `'600'`)
292
+ * - `'A50'` — selected surface tint (when not using `selected.surface`)
293
+ * - `'25'` / `'A25'` — subtle brand surfaces
294
+ *
295
+ * Neutral-only Sana steps (`'150'`, `'850'`, `'A150'`) live on {@link CanvasNeutralBrandRamp}.
296
+ */
297
+ export type CanvasBrandRamp = Partial<
298
+ Record<
299
+ | '25'
300
+ | '50'
301
+ | '100'
302
+ | '200'
303
+ | '300'
304
+ | '400'
305
+ | '500'
306
+ | '600'
307
+ | '700'
308
+ | '800'
309
+ | '900'
310
+ | '950'
311
+ | '975'
312
+ | 'A25'
313
+ | 'A50'
314
+ | 'A100'
315
+ | 'A200',
316
+ string
317
+ >
318
+ >;
319
+
320
+ /**
321
+ * Neutral brand ramp — includes Sana-only steps (`150` / `850` / `A150`) that are not
322
+ * exported for primary/critical/caution/positive families.
323
+ */
324
+ export type CanvasNeutralBrandRamp = CanvasBrandRamp &
325
+ Partial<Record<'150' | '850' | 'A150', string>>;
326
+
327
+ /** Semantic keys for `brand.action.*` CSS variables (PrimaryButton, etc.). */
328
+ export type CanvasActionBrandRamp = Partial<
329
+ Record<
330
+ 'base' | 'lightest' | 'lighter' | 'light' | 'dark' | 'darkest' | 'darker' | 'accent',
331
+ string
332
+ >
333
+ >;
334
+
335
+ /**
336
+ * Controls how partial theme input is expanded.
337
+ *
338
+ * - `'brand'` (default): predictable, design-aligned behavior. Setting only
339
+ * `brand.primary['600']` themes PrimaryButton and selected list/menu
340
+ * states. Other keys write only their CSS variable — no auto-generated ramps.
341
+ * - `'full'`: legacy behavior for the deprecated `canvas.palette` shape — auto-fills
342
+ * lightest→darkest via `shiftColor` and forwards to many `system.color.brand.*`
343
+ * tokens. On the numerical `brand` shape, `'full'` disables the primary shortcut
344
+ * and writes each ramp key literally.
345
+ */
346
+ export type CanvasThemingScope = 'brand' | 'full';
347
+
348
+ /**
349
+ * Preferred theme input for `CanvasProvider`. Each value maps directly to brand CSS
350
+ * variables unless noted as a shortcut below.
351
+ *
352
+ * @example Minimal — brand buttons + selected states only
353
+ * ```tsx
354
+ * <CanvasProvider theme={{brand: {primary: {'600': base.magenta600}}}} />
355
+ * ```
356
+ *
357
+ * @example Explicit — focus independent of primary
358
+ * ```tsx
359
+ * <CanvasProvider theme={{
360
+ * brand: {primary: {'600': base.red600, '500': base.blue500}},
361
+ * selected: {fg: base.red700, surface: base.redA50},
362
+ * }} />
363
+ * ```
364
+ *
365
+ * @see sanaCanvasProviderTheme when `<html>` is unavailable and popups need Sana brand forwarding
366
+ */
367
+ export interface CanvasNumericalBrandTheme {
368
+ brand?: {
369
+ /**
370
+ * Primary brand ramp (`--cnvs-brand-primary-*`).
371
+ *
372
+ * **Shortcut (brand scope only):** when `'600'` is the only key under `primary`,
373
+ * also sets:
374
+ * - `PrimaryButton` — `brand.action.base`, `accent.primary`, `accent.action`
375
+ * - Selected `Menu.Item` — `system.color.brand.fg.selected`, `surface.selected`
376
+ *
377
+ * Does **not** set focus rings — use `'500'` or `canvas.palette.common.focusOutline`.
378
+ *
379
+ * | Key | CSS variable | Typical consumers |
380
+ * |-----|--------------|-------------------|
381
+ * | `'600'` | `--cnvs-brand-primary-600` | PrimaryButton, brand links, accent.primary |
382
+ * | `'700'` | `--cnvs-brand-primary-700` | Strong primary fg, selected text |
383
+ * | `'500'` | `--cnvs-brand-primary-500` | Focus rings, border primary |
384
+ * | `'A50'` | `--cnvs-brand-primary-A50` | Selected/hover surfaces |
385
+ */
386
+ primary?: CanvasBrandRamp;
387
+
388
+ /**
389
+ * Button-specific ramp (`--cnvs-brand-action-*`). PrimaryButton reads
390
+ * these **before** `brand.primary`.
391
+ *
392
+ * | Key | Typical consumers |
393
+ * |-----|-------------------|
394
+ * | `base` | PrimaryButton background |
395
+ * | `dark` / `darkest` | PrimaryButton hover / pressed |
396
+ * | `accent` | PrimaryButton label color |
397
+ */
398
+ action?: CanvasActionBrandRamp;
399
+
400
+ /**
401
+ * Critical/error ramp (`--cnvs-brand-critical-*`).
402
+ *
403
+ * | Key | Typical consumers |
404
+ * |-----|-------------------|
405
+ * | `'600'` | TextInput error, DeleteButton, critical fg |
406
+ * | `'500'` | Critical focus ring, error border |
407
+ * | `'A25'` / `'A50'` | Error surface tints |
408
+ */
409
+ critical?: CanvasBrandRamp;
410
+
411
+ /**
412
+ * Caution/warning ramp (`--cnvs-brand-caution-*`).
413
+ *
414
+ * | Key | Typical consumers |
415
+ * |-----|-------------------|
416
+ * | `'400'` | Caution accent, TextInput caution |
417
+ * | `'500'` | Caution focus outer, caution border |
418
+ * | `'A25'` / `'A50'` | Caution surface tints |
419
+ */
420
+ caution?: CanvasBrandRamp;
421
+
422
+ /**
423
+ * Positive/success ramp (`--cnvs-brand-positive-*`).
424
+ *
425
+ * | Key | Typical consumers |
426
+ * |-----|-------------------|
427
+ * | `'600'` | Checkbox/Radio checked, success fg |
428
+ * | `'A25'` / `'A50'` | Success surface tints |
429
+ */
430
+ positive?: CanvasBrandRamp;
431
+
432
+ /**
433
+ * Neutral brand ramp (`--cnvs-brand-neutral-*`). Sana Canvas uses `base.neutral*`
434
+ * instead of legacy `base.slate*` — see {@link sanaCanvasNumericalTheme}.
435
+ *
436
+ * Affects brand-neutral text, borders, and surfaces where components reference
437
+ * `brand.neutral.*` or `system.color.brand` tokens tied to neutral.
438
+ */
439
+ neutral?: CanvasNeutralBrandRamp;
440
+ };
441
+
442
+ /**
443
+ * Selected-state shortcuts. Prefer these over indirect `brand.primary['700']` /
444
+ * `brand.primary.A50` when customizing list/menu selection.
445
+ *
446
+ * | Key | CSS variable | Typical consumers |
447
+ * |-----|--------------|-------------------|
448
+ * | `fg` | `--cnvs-sys-color-brand-fg-selected` | Menu.Item, SegmentedControl selected text |
449
+ * | `surface` | `--cnvs-sys-color-brand-surface-selected` | Menu.Item, list selected background |
450
+ */
451
+ selected?: {
452
+ /** Selected text/icon color */
453
+ fg?: string;
454
+ /** Selected row/chip background */
455
+ surface?: string;
456
+ };
457
+
458
+ /**
459
+ * Escape hatch for `system.color.brand.*` tokens not covered by `brand` ramps.
460
+ * Keys mirror the token path, e.g. `{color: {brand: {focus: {primary: '#00f'}}}}`.
461
+ */
462
+ system?: {
463
+ color?: {
464
+ brand?: Record<string, unknown>;
465
+ };
466
+ };
467
+
468
+ /** Text direction for the provider subtree. */
469
+ direction?: ContentDirection;
470
+
471
+ /**
472
+ * @default 'brand'
473
+ * @see CanvasThemingScope
474
+ */
475
+ themeScope?: CanvasThemingScope;
476
+ }
477
+
478
+ /**
479
+ * Theme input accepted by {@link CanvasProvider}.
480
+ * - Numerical `brand` shape (preferred)
481
+ * - Deprecated `canvas.palette` shape (legacy)
482
+ */
483
+ export type CanvasProviderTheme =
484
+ | (PartialEmotionCanvasTheme & {themeScope?: CanvasThemingScope})
485
+ | CanvasNumericalBrandTheme;
486
+
487
+ export function isNumericalTheme(
488
+ theme: CanvasProviderTheme | undefined
489
+ ): theme is CanvasNumericalBrandTheme {
490
+ if (!theme) {
491
+ return false;
492
+ }
493
+ if ('canvas' in theme) {
494
+ return false;
495
+ }
496
+ // `direction` / `themeScope` alone are valid numerical themes (e.g. RTL-only).
497
+ return (
498
+ 'brand' in theme ||
499
+ 'system' in theme ||
500
+ 'selected' in theme ||
501
+ 'direction' in theme ||
502
+ 'themeScope' in theme
503
+ );
504
+ }
505
+
506
+ const EXTENDED_RAMP_KEYS = new Set([
507
+ '25',
508
+ '50',
509
+ '100',
510
+ '200',
511
+ '300',
512
+ '400',
513
+ '500',
514
+ '600',
515
+ '700',
516
+ '800',
517
+ '900',
518
+ '950',
519
+ '975',
520
+ 'A25',
521
+ 'A50',
522
+ 'A100',
523
+ 'A200',
524
+ 'lightest',
525
+ 'lighter',
526
+ 'light',
527
+ 'main',
528
+ 'dark',
529
+ 'darkest',
530
+ 'contrast',
531
+ ]);
532
+
533
+ export function resolveThemingScope(theme: CanvasProviderTheme | undefined): CanvasThemingScope {
534
+ if (!theme) {
535
+ return 'brand';
536
+ }
537
+ if (theme.themeScope) {
538
+ return theme.themeScope;
539
+ }
540
+
541
+ if (isNumericalTheme(theme)) {
542
+ return theme.themeScope ?? 'brand';
543
+ }
544
+
545
+ const palette = theme.canvas?.palette;
546
+ if (!palette) {
547
+ return 'brand';
548
+ }
549
+
550
+ if (palette.common && Object.keys(palette.common).length > 0) {
551
+ const commonOnlyFocus =
552
+ Object.keys(palette.common).length === 1 && palette.common.focusOutline != null;
553
+ if (!commonOnlyFocus) {
554
+ return 'full';
555
+ }
556
+ }
557
+
558
+ for (const color of ['primary', 'error', 'alert', 'success', 'neutral'] as const) {
559
+ const colorPalette = palette[color];
560
+ if (!colorPalette) {
561
+ continue;
562
+ }
563
+ for (const key of Object.keys(colorPalette)) {
564
+ // `main` alone uses brand-scope bundles for every semantic palette color.
565
+ if (key === 'main') {
566
+ continue;
567
+ }
568
+ if (EXTENDED_RAMP_KEYS.has(key)) {
569
+ return 'full';
570
+ }
571
+ }
572
+ }
573
+
574
+ return 'brand';
575
+ }