@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
JavaScript
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 };