UNPKG

@workday/canvas-kit-react

Version:

The parent module that contains all Workday Canvas Kit React components

493 lines (492 loc) 23 kB
import { colorSpace, maybeWrapCSSVariables } from '@workday/canvas-kit-styling'; import { brand, system } from '@workday/canvas-tokens-web'; import { defaultCanvasTheme } from './theme'; import { isNumericalTheme, } from './types'; /** True when a semantic theme intentionally passes a non-empty `canvas.palette`. */ export function hasExplicitSemanticPalette(theme) { var _a; return (!isNumericalTheme(theme) && !!((_a = theme.canvas) === null || _a === void 0 ? void 0 : _a.palette) && Object.keys(theme.canvas.palette).length > 0); } /** Tokens that change when a consumer sets only their primary brand color. */ export const BRAND_SCOPE_PRIMARY_BUNDLE = { action: ['base', 'dark', 'darkest', 'accent'], accent: ['primary', 'action'], selected: { fg: system.color.brand.fg.selected, surface: system.color.brand.surface.selected, }, }; /** * Independently brandable tokens — writable when explicitly provided, * but NEVER derived from primary.main / brand.primary.600 / brand.primary.500. */ export const BRAND_SCOPE_INDEPENDENT = { focus: { primary: system.color.brand.focus.primary, semanticKey: 'focusOutline', }, border: { primary: system.color.brand.border.primary, semanticKey: 'focusOutline', }, }; const commonTokenMapping = { focusOutline: brand.common.focus, alertInner: brand.common.caution.inner, alertOuter: brand.common.caution.outer, errorInner: brand.common.critical, }; const brandColorMapping = { primary: 'primary', error: 'critical', success: 'positive', alert: 'caution', neutral: 'neutral', }; /** Brand ramp keys defined in Sana CSS but not yet exported from canvas-tokens-web. */ const EXTENDED_BRAND_TOKEN_MAP = { neutral150: '--cnvs-brand-neutral-150', neutral850: '--cnvs-brand-neutral-850', neutralA150: '--cnvs-brand-neutral-a150', neutralA850: '--cnvs-brand-neutral-a850', primaryA300: '--cnvs-brand-primary-a300', criticalA300: '--cnvs-brand-critical-a300', cautionA300: '--cnvs-brand-caution-a300', positiveA300: '--cnvs-brand-positive-a300', }; const setStyleVar = (style, token, value) => { // @ts-ignore - CSS custom property key style[token] = maybeWrapCSSVariables(value); }; /** Maps a numerical brand ramp onto `brand.<color><rampKey>` CSS variables. */ export function writeNumericalBrandRamp(color, ramp, style, options) { if (!ramp) { return; } Object.keys(ramp).forEach(rampKey => { var _a, _b; if ((_a = options === null || options === void 0 ? void 0 : options.skipKeys) === null || _a === void 0 ? void 0 : _a.has(String(rampKey))) { return; } const value = ramp[rampKey]; if (value == null) { return; } const token = color === 'action' ? brand.action[rampKey] : // @ts-ignore - dynamic token lookup ((_b = brand[`${color}${rampKey}`]) !== null && _b !== void 0 ? _b : EXTENDED_BRAND_TOKEN_MAP[`${color}${rampKey}`]); if (token) { setStyleVar(style, token, value); } }); } /** Called when consumer sets only `primary.main` or `brand.primary['600']`. */ export function applyPrimaryBrandBundle(primaryColor, style) { const value = maybeWrapCSSVariables(primaryColor); setStyleVar(style, brand.primary.base, value); setStyleVar(style, brand.primary600, value); setStyleVar(style, brand.action.base, value); if (system.color.brand.accent.primary) { setStyleVar(style, system.color.brand.accent.primary, value); } if (system.color.brand.accent.action) { setStyleVar(style, system.color.brand.accent.action, value); } const selectedFg = colorSpace.pressed({ color: `var(${brand.primary600})`, fallback: value, colorType: 'accent', }); const selectedSurface = colorSpace.darken({ color: system.color.bg.default, fallback: 'white', mixinColor: `var(${brand.primary600})`, mixinValue: '0.11', }); setStyleVar(style, brand.primary700, selectedFg); setStyleVar(style, brand.primary.dark, selectedFg); setStyleVar(style, brand.primaryA50, selectedSurface); if (BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg) { setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg, selectedFg); } if (BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface) { setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface, selectedSurface); } const hoverColor = colorSpace.hover({ color: `var(${brand.action.base})`, fallback: value, colorType: 'accent', }); const pressedColor = colorSpace.pressed({ color: `var(${brand.action.base})`, fallback: value, colorType: 'accent', }); setStyleVar(style, brand.action.dark, hoverColor); setStyleVar(style, brand.action.darkest, pressedColor); } /** Called when consumer sets only `error.main` or `brand.critical['600']`. */ export function applyCriticalBrandBundle(criticalColor, style) { var _a; const value = maybeWrapCSSVariables(criticalColor); setStyleVar(style, brand.error.base, value); setStyleVar(style, brand.critical600, value); setStyleVar(style, brand.critical500, value); if (system.color.brand.accent.critical) { setStyleVar(style, system.color.brand.accent.critical, value); } if ((_a = system.color.brand.fg.critical) === null || _a === void 0 ? void 0 : _a.default) { setStyleVar(style, system.color.brand.fg.critical.default, value); } if (system.color.brand.border.critical) { setStyleVar(style, system.color.brand.border.critical, value); } if (system.color.brand.focus.critical) { setStyleVar(style, system.color.brand.focus.critical, value); } } /** Called when consumer sets only `alert.main` or `brand.caution['400']`. */ export function applyCautionBrandBundle(cautionColor, style) { var _a, _b, _c; const value = maybeWrapCSSVariables(cautionColor); setStyleVar(style, brand.alert.base, value); setStyleVar(style, brand.caution400, value); setStyleVar(style, brand.caution500, value); if (system.color.brand.accent.caution) { setStyleVar(style, system.color.brand.accent.caution, value); } if ((_a = system.color.brand.fg.caution) === null || _a === void 0 ? void 0 : _a.default) { setStyleVar(style, system.color.brand.fg.caution.default, value); } if (system.color.brand.border.caution) { setStyleVar(style, system.color.brand.border.caution, value); } if ((_b = system.color.brand.focus.caution) === null || _b === void 0 ? void 0 : _b.inner) { setStyleVar(style, system.color.brand.focus.caution.inner, value); } if ((_c = system.color.brand.focus.caution) === null || _c === void 0 ? void 0 : _c.outer) { setStyleVar(style, system.color.brand.focus.caution.outer, value); } } /** Called when consumer sets only `success.main` or `brand.positive['600']`. */ export function applyPositiveBrandBundle(positiveColor, style) { var _a; const value = maybeWrapCSSVariables(positiveColor); setStyleVar(style, brand.success.base, value); setStyleVar(style, brand.positive600, value); if (system.color.brand.accent.positive) { setStyleVar(style, system.color.brand.accent.positive, value); } if ((_a = system.color.brand.fg.positive) === null || _a === void 0 ? void 0 : _a.default) { setStyleVar(style, system.color.brand.fg.positive.default, value); } } /** Called when consumer sets only `neutral.main` or `brand.neutral['600']`. */ export function applyNeutralBrandBundle(neutralColor, style) { const value = maybeWrapCSSVariables(neutralColor); setStyleVar(style, brand.neutral.base, value); setStyleVar(style, brand.neutral600, value); } /** Writes first-class selected shortcuts from numerical theme input. */ export function writeSelectedShortcuts(selected, style) { if (!selected) { return; } if (selected.fg && BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg) { setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.fg, selected.fg); } if (selected.surface && BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface) { setStyleVar(style, BRAND_SCOPE_PRIMARY_BUNDLE.selected.surface, selected.surface); } } /** Writes first-class focus shortcuts — never derived from brand.primary. */ export function writeFocusShortcuts(focus, style) { if (focus === null || focus === void 0 ? void 0 : focus.primary) { writeFocusBorderBundle(focus.primary, style); } } /** Writes focus/border system tokens without touching brand.primary500. */ function writeFocusBorderBundle(value, style) { setStyleVar(style, brand.common.focusOutline, value); if (BRAND_SCOPE_INDEPENDENT.focus.primary) { setStyleVar(style, BRAND_SCOPE_INDEPENDENT.focus.primary, value); } if (BRAND_SCOPE_INDEPENDENT.border.primary) { setStyleVar(style, BRAND_SCOPE_INDEPENDENT.border.primary, value); } } /** * Writes focus/border from explicit input — never from primary.main or primary.500. * Numerical themes use {@link writeFocusShortcuts}; legacy uses `common.focusOutline`. */ export function writeIndependentBrandTokens(theme, style) { var _a, _b; if (!theme) { return; } if (isNumericalTheme(theme)) { if (theme.system) { writeSystemBrandOverrides(theme.system, style); } return; } const common = (_b = (_a = theme.canvas) === null || _a === void 0 ? void 0 : _a.palette) === null || _b === void 0 ? void 0 : _b.common; if (!common) { return; } ['focusOutline', 'alertInner', 'alertOuter', 'errorInner'].forEach(key => { var _a; const rawValue = common[key]; if (rawValue == null) { return; } if (rawValue === defaultCanvasTheme.palette.common[key]) { return; } const value = maybeWrapCSSVariables(rawValue); // @ts-ignore setStyleVar(style, brand.common[key], value); // @ts-ignore setStyleVar(style, commonTokenMapping[key], value); if (key === 'focusOutline') { writeFocusBorderBundle(value, style); } if (key === 'alertInner' && ((_a = system.color.brand.focus.caution) === null || _a === void 0 ? void 0 : _a.inner)) { setStyleVar(style, system.color.brand.focus.caution.inner, value); } if (key === 'alertOuter' && system.color.brand.border.caution) { setStyleVar(style, system.color.brand.border.caution, value); } if (key === 'errorInner') { if (system.color.brand.focus.critical) { setStyleVar(style, system.color.brand.focus.critical, value); } if (system.color.brand.border.critical) { setStyleVar(style, system.color.brand.border.critical, value); } } }); } /** Walks nested system override object and sets matching system CSS vars. */ export function writeSystemBrandOverrides(systemOverrides, style) { var _a; const brandOverrides = (_a = systemOverrides === null || systemOverrides === void 0 ? void 0 : systemOverrides.color) === null || _a === void 0 ? void 0 : _a.brand; if (!brandOverrides) { return; } const walk = (node, path) => { Object.entries(node).forEach(([key, val]) => { var _a, _b; if (val && typeof val === 'object') { walk(val, [...path, key]); } else if (typeof val === 'string') { let tokenNode = system.color.brand; for (const seg of [...path, key]) { // @ts-ignore tokenNode = tokenNode === null || tokenNode === void 0 ? void 0 : tokenNode[seg]; } if (typeof tokenNode === 'string') { const cssVarName = (_b = (_a = tokenNode.match(/--[\w-]+/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : tokenNode; setStyleVar(style, cssVarName, val); } } }); }; walk(brandOverrides, []); } const mappedKeys = { lightest: 'lightest', lighter: 'lighter', light: 'light', main: 'base', dark: 'dark', darkest: 'darkest', contrast: 'accent', }; const numericalTokenMapping = { lightest: '25', lighter: '50', light: '200', main: '600', dark: '700', darkest: '800', }; /** Full-scope semantic writer — filled palette compared against defaults unless `writeAll`. */ export function writeSemanticTheme(palette, style, options) { var _a; const writeAll = (_a = options === null || options === void 0 ? void 0 : options.writeAll) !== null && _a !== void 0 ? _a : false; ['common', 'primary', 'error', 'alert', 'success', 'neutral'].forEach(color => { if (color === 'common') { ['focusOutline', 'alertInner', 'alertOuter', 'errorInner'].forEach(key => { var _a; if (writeAll || palette.common[key] !== defaultCanvasTheme.palette.common[key]) { const value = maybeWrapCSSVariables(palette.common[key]); // @ts-ignore setStyleVar(style, brand.common[key], value); // @ts-ignore setStyleVar(style, commonTokenMapping[key], value); if (key === 'focusOutline') { writeFocusBorderBundle(value, style); } if (key === 'alertInner' && ((_a = system.color.brand.focus.caution) === null || _a === void 0 ? void 0 : _a.inner)) { setStyleVar(style, system.color.brand.focus.caution.inner, value); } if (key === 'alertOuter' && system.color.brand.border.caution) { setStyleVar(style, system.color.brand.border.caution, value); } if (key === 'errorInner') { if (system.color.brand.focus.critical) { setStyleVar(style, system.color.brand.focus.critical, value); } if (system.color.brand.border.critical) { setStyleVar(style, system.color.brand.border.critical, value); } } } }); } else { ['lightest', 'lighter', 'light', 'main', 'dark', 'darkest', 'contrast'].forEach(key => { var _a, _b, _c, _d, _e; // @ts-ignore if (writeAll || palette[color][key] !== defaultCanvasTheme.palette[color][key]) { // @ts-ignore const value = maybeWrapCSSVariables(palette[color][key]); // @ts-ignore setStyleVar(style, brand[color][mappedKeys[key]], value); if (key !== 'contrast' && key in numericalTokenMapping) { const newBrandColor = brandColorMapping[color]; const numericalSuffix = numericalTokenMapping[key]; // @ts-ignore const numericalToken = brand[newBrandColor + numericalSuffix]; if (numericalToken) { setStyleVar(style, numericalToken, value); } if (key === 'main') { // @ts-ignore const systemAccentToken = system.color.brand.accent[newBrandColor]; if (systemAccentToken) { setStyleVar(style, systemAccentToken, value); } // @ts-ignore const systemFgToken = (_a = system.color.brand.fg[newBrandColor]) === null || _a === void 0 ? void 0 : _a.default; if (systemFgToken) { setStyleVar(style, systemFgToken, value); } } else if (key === 'dark') { // @ts-ignore const systemFgStrongToken = (_b = system.color.brand.fg[newBrandColor]) === null || _b === void 0 ? void 0 : _b.strong; if (systemFgStrongToken) { setStyleVar(style, systemFgStrongToken, value); } if (newBrandColor === 'primary' && system.color.brand.fg.selected) { setStyleVar(style, system.color.brand.fg.selected, value); } if (newBrandColor === 'critical') { if (system.color.brand.focus.critical) { setStyleVar(style, system.color.brand.focus.critical, value); } if (system.color.brand.border.critical) { setStyleVar(style, system.color.brand.border.critical, value); } } if (newBrandColor === 'caution') { if ((_c = system.color.brand.focus.caution) === null || _c === void 0 ? void 0 : _c.outer) { setStyleVar(style, system.color.brand.focus.caution.outer, value); } if (system.color.brand.border.caution) { setStyleVar(style, system.color.brand.border.caution, value); } } } else if (key === 'lighter') { // @ts-ignore const surfaceStrongToken = (_d = system.color.brand.surface[newBrandColor]) === null || _d === void 0 ? void 0 : _d.strong; if (surfaceStrongToken) { setStyleVar(style, surfaceStrongToken, value); } if (newBrandColor === 'primary' && system.color.brand.surface.selected) { setStyleVar(style, system.color.brand.surface.selected, value); } } else if (key === 'lightest') { // @ts-ignore const surfaceDefaultToken = (_e = system.color.brand.surface[newBrandColor]) === null || _e === void 0 ? void 0 : _e.default; if (surfaceDefaultToken) { setStyleVar(style, surfaceDefaultToken, value); } } } } }); } }); } /** Writes numerical theme — brand scope applies per-family shortcuts; remaining keys write 1:1. */ export function writeNumericalTheme(theme, style, scope) { var _a, _b, _c; const brandPalette = theme.brand; const skipRamp = {}; if (scope === 'brand' && brandPalette) { if (((_a = brandPalette.primary) === null || _a === void 0 ? void 0 : _a['600']) && Object.keys(brandPalette.primary).length === 1) { applyPrimaryBrandBundle(brandPalette.primary['600'], style); skipRamp.primary = new Set(['600']); // Do not skip action keys — explicit `brand.action.*` must win over derived shades // written by applyPrimaryBrandBundle (writeNumericalBrandRamp runs after). } // Only the main ramp key triggers the family shortcut. Lone `'500'` must // write 1:1 and must not overwrite critical600 / caution400 via the bundle. const critical = brandPalette.critical; if ((critical === null || critical === void 0 ? void 0 : critical['600']) && Object.keys(critical).length === 1) { applyCriticalBrandBundle(critical['600'], style); skipRamp.critical = new Set(['600']); } const caution = brandPalette.caution; if ((caution === null || caution === void 0 ? void 0 : caution['400']) && Object.keys(caution).length === 1) { applyCautionBrandBundle(caution['400'], style); skipRamp.caution = new Set(['400']); } if (((_b = brandPalette.positive) === null || _b === void 0 ? void 0 : _b['600']) && Object.keys(brandPalette.positive).length === 1) { applyPositiveBrandBundle(brandPalette.positive['600'], style); skipRamp.positive = new Set(['600']); } if (((_c = brandPalette.neutral) === null || _c === void 0 ? void 0 : _c['600']) && Object.keys(brandPalette.neutral).length === 1) { applyNeutralBrandBundle(brandPalette.neutral['600'], style); skipRamp.neutral = new Set(['600']); } } if (brandPalette) { ['primary', 'critical', 'caution', 'positive', 'neutral', 'action'].forEach(color => { writeNumericalBrandRamp(color, brandPalette[color], style, { skipKeys: skipRamp[color] }); }); } writeSelectedShortcuts(theme.selected, style); writeFocusShortcuts(theme.focus, style); writeIndependentBrandTokens(theme, style); } /** Brand-scope semantic path — reads raw input only, not useTheme-filled palette. */ export function writeBrandScopeSemantic(theme, style) { var _a, _b, _c, _d, _e, _f; const palette = (_a = theme.canvas) === null || _a === void 0 ? void 0 : _a.palette; if ((_b = palette === null || palette === void 0 ? void 0 : palette.primary) === null || _b === void 0 ? void 0 : _b.main) { applyPrimaryBrandBundle(palette.primary.main, style); } if ((_c = palette === null || palette === void 0 ? void 0 : palette.error) === null || _c === void 0 ? void 0 : _c.main) { applyCriticalBrandBundle(palette.error.main, style); } if ((_d = palette === null || palette === void 0 ? void 0 : palette.alert) === null || _d === void 0 ? void 0 : _d.main) { applyCautionBrandBundle(palette.alert.main, style); } if ((_e = palette === null || palette === void 0 ? void 0 : palette.success) === null || _e === void 0 ? void 0 : _e.main) { applyPositiveBrandBundle(palette.success.main, style); } if ((_f = palette === null || palette === void 0 ? void 0 : palette.neutral) === null || _f === void 0 ? void 0 : _f.main) { applyNeutralBrandBundle(palette.neutral.main, style); } writeIndependentBrandTokens(theme, style); }