UNPKG

@aws-amplify/ui

Version:

`@aws-amplify/ui` contains low-level logic & styles for stand-alone usage or re-use in framework-specific implementations.

152 lines (149 loc) 5.69 kB
import { defaultTheme } from '../defaultTheme.mjs'; import { deepExtend, setupTokens, flattenProperties, setupToken } from './utils.mjs'; import { createComponentCSS } from './createComponentCSS.mjs'; import '@aws-amplify/core/internals/utils'; import '../../utils/setUserAgent/constants.mjs'; import { isString } from '../../utils/utils.mjs'; import { createColorPalette } from './createColorPalette.mjs'; import { createAnimationCSS } from './createAnimationCSS.mjs'; /** * This will be used like `const myTheme = createTheme({})` * `myTheme` can then be passed to a Provider or the generated CSS * can be passed to a stylesheet at build-time or run-time. * const myTheme = createTheme({}) * const myOtherTheme = createTheme({}, myTheme); */ function createTheme(theme, DefaultTheme = defaultTheme) { // merge theme and DefaultTheme to get a complete theme // deepExtend is an internal Style Dictionary method // that performs a deep merge on n objects. We could change // this to another 3p deep merge solution too. const mergedTheme = deepExtend([ {}, DefaultTheme, { ...theme, components: {}, }, ]); const { primaryColor, secondaryColor } = mergedTheme; // apply primaryColor and secondaryColor if present if (isString(primaryColor)) { mergedTheme.tokens.colors.primary = createColorPalette({ keys: Object.keys(mergedTheme.tokens.colors[primaryColor]), value: primaryColor, }); } if (isString(secondaryColor)) { mergedTheme.tokens.colors.secondary = createColorPalette({ keys: Object.keys(mergedTheme.tokens.colors[secondaryColor]), value: secondaryColor, }); } // Setting up the tokens. This is similar to what Style Dictionary // does. At the end of this, each token should have: // - CSS variable name of itself // - its value (reference to another CSS variable or raw value) const tokens = setupTokens({ tokens: mergedTheme.tokens, setupToken, }); // Setting the type here because setupTokens is recursive const { breakpoints, name } = mergedTheme; // flattenProperties is another internal Style Dictionary function // that creates an array of all tokens. let cssText = `[data-amplify-theme="${name}"] {\n` + flattenProperties(tokens) .map((token) => `${token.name}: ${token.value};`) .join('\n') + `\n}\n`; if (theme?.components) { cssText += createComponentCSS({ theme: { ...mergedTheme, tokens, }, components: theme.components, }); } let overrides = []; if (mergedTheme.animations) { cssText += createAnimationCSS({ animations: mergedTheme.animations, tokens, }); } /** * For each override, we setup the tokens and then generate the CSS. * This allows us to have one single CSS string for all possible overrides * and avoid re-renders in React, but also support other frameworks as well. */ if (mergedTheme.overrides) { overrides = mergedTheme.overrides.map((override) => { const overrideTokens = setupTokens({ tokens: override.tokens, setupToken, }); const customProperties = flattenProperties(overrideTokens) .map((token) => `${token.name}: ${token.value};`) .join('\n'); // Overrides can have a selector, media query, breakpoint, or color mode // for creating the selector if ('selector' in override) { cssText += `\n${override.selector} {\n${customProperties}\n}\n`; } if ('mediaQuery' in override) { cssText += `\n@media (${override.mediaQuery}) { [data-amplify-theme="${name}"] { ${customProperties} } }\n`; } if ('breakpoint' in override) { const breakpoint = mergedTheme.breakpoints.values[override.breakpoint]; cssText += `\n@media (min-width: ${breakpoint}px) { [data-amplify-theme="${name}"] { ${customProperties} } }\n`; } if ('colorMode' in override) { cssText += `\n@media (prefers-color-scheme: ${override.colorMode}) { [data-amplify-theme="${name}"][data-amplify-color-mode="system"] { ${customProperties} color-scheme: ${override.colorMode}; } }\n`; cssText += `\n[data-amplify-theme="${name}"][data-amplify-color-mode="${override.colorMode}"] { ${customProperties} color-scheme: ${override.colorMode}; }\n`; } return { ...override, tokens: overrideTokens, }; }); } return { tokens, breakpoints, name, cssText, containerProps: ({ colorMode } = {}) => { return { 'data-amplify-theme': name, 'data-amplify-color-mode': colorMode, }; }, // keep overrides separate from base theme // this allows web platforms to use plain CSS scoped to a // selector and only override the CSS vars needed. This // means we could generate CSS at build-time in a postcss // plugin, or do it at runtime and inject the CSS into a // style tag. // This also allows RN to dynamically switch themes in a // provider. overrides, }; } export { createTheme };