@workday/canvas-kit-react
Version:
The parent module that contains all Workday Canvas Kit React components
115 lines (114 loc) • 11.8 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { CacheProvider, ThemeProvider } from '@emotion/react';
import * as React from 'react';
import { createStyles, getCache } from '@workday/canvas-kit-styling';
import { base, brand, system } from '@workday/canvas-tokens-web';
import { defaultCanvasTheme, getTheme, isNumericalTheme, resolveThemingScope, useTheme, } from './theming';
import { hasExplicitSemanticPalette, writeBrandScopeSemantic, writeNumericalTheme, writeSemanticTheme, } from './theming/brandScope';
import { sanaCanvasProviderTheme } from './theming/sanaTheme';
/**
* Context for providing brand CSS variables to popup containers.
* This carries the resolved CSS variable style map from CanvasProvider
* to usePopupStack for proper popup theming, regardless of whether
* a numerical or legacy theme is used.
*/
export const CanvasBrandStyleContext = React.createContext({});
/**
* Context for providing the `data-theme` value to popup containers.
*
* Token stylesheets scope their variables to an attribute selector (e.g. Sana's
* `[data-theme="sana-canvas"]` block defines ~300 variables — palette, shape, depth, type).
* Portaled popups render under `document.body`, outside the `CanvasProvider` wrapper, so they
* never inherit a nested `data-theme` and none of those variables resolve. Forwarding the
* attribute itself lets the popup container match the same selector, so the whole theme applies
* through normal cascade — rather than trying to mirror every variable as an inline style.
*/
export const CanvasThemeAttributeContext = React.createContext(undefined);
/**
* If you wish to reset the theme to the default, apply this class on the CanvasProvider.
*/
export const defaultBranding = createStyles({ name: "2flm2c", styles: "--cnvs-brand-common-alert-inner:var(--cnvs-base-palette-amber-400);--cnvs-brand-common-alert-outer:var(--cnvs-base-palette-amber-500);--cnvs-brand-common-error-inner:var(--cnvs-base-palette-red-500);--cnvs-brand-common-focus-outline:var(--cnvs-base-palette-blue-500);--cnvs-brand-neutral-accent:var(--cnvs-base-palette-neutral-0);--cnvs-brand-neutral-darkest:var(--cnvs-base-palette-slate-800);--cnvs-brand-neutral-dark:var(--cnvs-base-palette-slate-700);--cnvs-brand-neutral-base:var(--cnvs-base-palette-slate-600);--cnvs-brand-neutral-light:var(--cnvs-base-palette-slate-200);--cnvs-brand-neutral-lighter:var(--cnvs-base-palette-slate-50);--cnvs-brand-neutral-lightest:var(--cnvs-base-palette-slate-25);--cnvs-brand-success-accent:var(--cnvs-base-palette-neutral-0);--cnvs-brand-success-darkest:var(--cnvs-base-palette-green-800);--cnvs-brand-success-dark:var(--cnvs-base-palette-green-700);--cnvs-brand-success-base:var(--cnvs-base-palette-green-600);--cnvs-brand-success-light:var(--cnvs-base-palette-green-200);--cnvs-brand-success-lighter:var(--cnvs-base-palette-green-50);--cnvs-brand-success-lightest:var(--cnvs-base-palette-green-25);--cnvs-brand-error-accent:var(--cnvs-base-palette-neutral-0);--cnvs-brand-error-darkest:var(--cnvs-base-palette-red-800);--cnvs-brand-error-dark:var(--cnvs-base-palette-red-700);--cnvs-brand-error-base:var(--cnvs-base-palette-red-600);--cnvs-brand-error-light:var(--cnvs-base-palette-red-200);--cnvs-brand-error-lighter:var(--cnvs-base-palette-red-50);--cnvs-brand-error-lightest:var(--cnvs-base-palette-red-25);--cnvs-brand-alert-accent:var(--cnvs-base-palette-neutral-900);--cnvs-brand-alert-darkest:var(--cnvs-base-palette-amber-600);--cnvs-brand-alert-dark:var(--cnvs-base-palette-amber-500);--cnvs-brand-alert-base:var(--cnvs-base-palette-amber-400);--cnvs-brand-alert-light:var(--cnvs-base-palette-amber-200);--cnvs-brand-alert-lighter:var(--cnvs-base-palette-amber-50);--cnvs-brand-alert-lightest:var(--cnvs-base-palette-amber-25);--cnvs-brand-primary-accent:var(--cnvs-base-palette-neutral-0);--cnvs-brand-primary-darkest:var(--cnvs-base-palette-blue-800);--cnvs-brand-primary-dark:var(--cnvs-base-palette-blue-700);--cnvs-brand-primary-base:var(--cnvs-base-palette-blue-600);--cnvs-brand-primary-light:var(--cnvs-base-palette-blue-200);--cnvs-brand-primary-lighter:var(--cnvs-base-palette-blue-50);--cnvs-brand-primary-lightest:var(--cnvs-base-palette-blue-25);--cnvs-brand-gradient-primary:linear-gradient(90deg, var(--cnvs-brand-primary-base) 0%, var(--cnvs-brand-primary-dark) 100%);--cnvs-sys-color-brand-focus-primary:var(--cnvs-brand-common-focus-outline);--cnvs-sys-color-brand-border-primary:var(--cnvs-brand-common-focus-outline);--cnvs-sys-color-brand-accent-primary:var(--cnvs-brand-primary-base);--cnvs-sys-color-brand-accent-action:var(--cnvs-brand-primary-base);--cnvs-sys-color-brand-accent-critical:var(--cnvs-brand-error-base);--cnvs-sys-color-brand-accent-caution:var(--cnvs-brand-alert-base);--cnvs-sys-color-brand-accent-positive:var(--cnvs-brand-success-base);--cnvs-sys-color-brand-fg-primary-default:var(--cnvs-brand-primary-base);--cnvs-sys-color-brand-fg-primary-strong:var(--cnvs-brand-primary-dark);--cnvs-sys-color-brand-fg-critical-default:var(--cnvs-brand-error-base);--cnvs-sys-color-brand-fg-critical-strong:var(--cnvs-brand-error-dark);--cnvs-sys-color-brand-fg-caution-default:var(--cnvs-brand-alert-darkest);--cnvs-sys-color-brand-fg-caution-strong:var(--cnvs-brand-alert-darkest);--cnvs-sys-color-brand-fg-positive-default:var(--cnvs-brand-success-base);--cnvs-sys-color-brand-fg-positive-strong:var(--cnvs-brand-success-dark);--cnvs-sys-color-brand-fg-selected:var(--cnvs-brand-primary-dark);--cnvs-sys-color-brand-focus-critical:var(--cnvs-brand-error-dark);--cnvs-sys-color-brand-border-critical:var(--cnvs-brand-error-base);--cnvs-sys-color-brand-focus-caution-inner:var(--cnvs-brand-common-alert-inner);--cnvs-sys-color-brand-focus-caution-outer:var(--cnvs-brand-alert-dark);--cnvs-sys-color-brand-border-caution:var(--cnvs-brand-alert-dark);--cnvs-sys-color-brand-surface-primary-default:var(--cnvs-brand-primary-lightest);--cnvs-sys-color-brand-surface-primary-strong:var(--cnvs-brand-primary-lighter);--cnvs-sys-color-brand-surface-critical-default:var(--cnvs-brand-error-lightest);--cnvs-sys-color-brand-surface-critical-strong:var(--cnvs-brand-error-lighter);--cnvs-sys-color-brand-surface-caution-default:var(--cnvs-brand-alert-lightest);--cnvs-sys-color-brand-surface-caution-strong:var(--cnvs-brand-alert-lighter);--cnvs-sys-color-brand-surface-positive-default:var(--cnvs-brand-success-lightest);--cnvs-sys-color-brand-surface-positive-strong:var(--cnvs-brand-success-lighter);--cnvs-sys-color-brand-surface-selected:var(--cnvs-brand-primary-lighter);" });
/** Pure function — safe to call outside React hooks (e.g. usePopupStack). */
export function canvasThemeToCssVars(theme, elemProps, options) {
var _a, _b;
const className = elemProps.className || '';
const style = { ...(elemProps.style || {}) };
const scope = (_a = options === null || options === void 0 ? void 0 : options.themeScope) !== null && _a !== void 0 ? _a : resolveThemingScope(theme);
if (!theme) {
return { ...elemProps, className, style };
}
if (isNumericalTheme(theme)) {
writeNumericalTheme(theme, style, scope);
}
else if (!hasExplicitSemanticPalette(theme)) {
// `{canvas: {}}` — no inline overrides; global CSS (e.g. Sana) cascades through.
}
else if (scope === 'brand') {
writeBrandScopeSemantic(theme, style);
}
else {
const filledTheme = (_b = options === null || options === void 0 ? void 0 : options.filledSemanticTheme) !== null && _b !== void 0 ? _b : getTheme(theme);
writeSemanticTheme(filledTheme.canvas.palette, style, { writeAll: true });
}
return { ...elemProps, className, style };
}
export const useCanvasThemeToCssVars = (
/**
* @deprecated ⚠️ `theme` 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).
*/
theme, elemProps, themeScope) => {
const filledTheme = useTheme(isNumericalTheme(theme) ? undefined : theme);
const resolvedScope = themeScope !== null && themeScope !== void 0 ? themeScope : resolveThemingScope(theme);
return canvasThemeToCssVars(theme, elemProps, {
themeScope: resolvedScope,
filledSemanticTheme: !isNumericalTheme(theme) && resolvedScope === 'full' ? filledTheme : undefined,
});
};
export const CanvasProvider = ({ children, theme, themeScope, ...props }) => {
var _a, _b;
// Add console warning for unnecessary sanaCanvasProviderTheme usage
React.useEffect(() => {
if (process.env.NODE_ENV !== 'production') {
if (theme === sanaCanvasProviderTheme &&
typeof document !== 'undefined' &&
document.documentElement.getAttribute('data-theme') === 'sana-canvas') {
console.warn('Canvas Kit: You are passing sanaCanvasProviderTheme to CanvasProvider but ' +
'data-theme="sana-canvas" is already set on <html>. The theme prop is not needed ' +
'in this case and can be removed. Keep sanaCanvasProviderTheme when you cannot ' +
'control <html> (embedded apps / microfrontends) so popups still get Sana brand ' +
'variables. See: ' +
'https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs');
}
}
}, [theme]);
// Computed className/style win over consumer props — do not re-spread `props` over them.
const { className, style, ...elemProps } = useCanvasThemeToCssVars(theme, props, themeScope);
const cache = getCache();
// Read parent context to support nested scoped providers
const parentBrandStyle = React.useContext(CanvasBrandStyleContext);
const parentThemeAttribute = React.useContext(CanvasThemeAttributeContext);
// `data-theme` on this provider wins; otherwise inherit from a parent provider so nested
// providers keep forwarding the outer theme attribute to popups.
const themeAttribute = (_a = props['data-theme']) !== null && _a !== void 0 ? _a : parentThemeAttribute;
// Popup forwarding only needs CSS custom properties (not consumer layout styles).
const mergedBrandStyle = React.useMemo(() => {
const merged = {};
for (const [key, value] of Object.entries({ ...parentBrandStyle, ...style })) {
if (key.startsWith('--') && typeof value === 'string') {
// @ts-ignore - CSS custom property key
merged[key] = value;
}
}
return merged;
}, [parentBrandStyle, style]);
const emotionTheme = theme
? isNumericalTheme(theme)
? { canvas: defaultCanvasTheme }
: theme
: undefined;
const content = (_jsx("div", { dir: isNumericalTheme(theme)
? theme.direction || defaultCanvasTheme.direction
: ((_b = theme === null || theme === void 0 ? void 0 : theme.canvas) === null || _b === void 0 ? void 0 : _b.direction) || defaultCanvasTheme.direction, className: className, ...elemProps, style: style, children: children }));
const wrappedContent = (_jsx(CanvasThemeAttributeContext.Provider, { value: themeAttribute, children: _jsx(CanvasBrandStyleContext.Provider, { value: mergedBrandStyle, children: content }) }));
return (_jsx(CacheProvider, { value: cache, children: emotionTheme ? (_jsx(ThemeProvider, { theme: emotionTheme, children: wrappedContent })) : (wrappedContent) }));
};