@aws-amplify/ui
Version:
`@aws-amplify/ui` contains low-level logic & styles for stand-alone usage or re-use in framework-specific implementations.
209 lines (206 loc) • 7.5 kB
JavaScript
import kebabCase from 'lodash/kebabCase.js';
import '@aws-amplify/core/internals/utils';
import '../../utils/setUserAgent/constants.mjs';
import { has, isObject, isString } from '../../utils/utils.mjs';
import { usesReference } from '../../utils/references.mjs';
const CSS_VARIABLE_PREFIX = 'amplify';
/**
* This will take an object like:
* {paddingTop:'20px',color:'{colors.font.primary}'}
* and turn it into a CSS string:
* `padding-top:20px; color: var(--colors-font-primary);`
*/
function propsToString(props) {
return Object.entries(props)
.map(([key, value]) => {
const _value = isDesignToken(value)
? value.toString()
: // @ts-ignore
cssValue({ value });
return `${kebabCase(key)}:${_value}; `;
})
.join(' ');
}
function cssNameTransform({ path = [] }) {
return `${kebabCase([CSS_VARIABLE_PREFIX, ...path].join(' '))}`;
}
// Important: these properties should not be altered in
// order to maintain the expected order of the CSS `box-shadow` property
const SHADOW_PROPERTIES = [
'offsetX',
'offsetY',
'blurRadius',
'spreadRadius',
'color',
];
/**
* Will take a design token in a theme and return its value as CSS
*
* @param token
* @returns
*/
function cssValue(token) {
const { value } = token;
if (isString(value)) {
return referenceValue(value);
}
if (isShadowTokenObject(value)) {
return SHADOW_PROPERTIES.map((property) => {
return referenceValue(
// lookup property against `token` first for custom non-nested value, then lookup
// property against `value` for design token value
isShadowTokenObject(token) ? token[property] : value[property]);
}).join(' ');
}
return value;
}
/**
* Helper function to test if something is a design token or not.
* Used in the React component style props.
*
* @param value - thing to test if it is a design token or not
* @returns boolean
*/
function isDesignToken(value) {
return isObject(value) && has(value, 'value');
}
function isShadowTokenObject(value) {
return isObject(value) && has(value, 'offsetX');
}
/**
* Function that sees if a string contains a design token reference
* and if so will turn that into a CSS variable.
*
* @param {string} value
* @returns string
*/
function referenceValue(value) {
if (!value)
return '';
if (usesReference(value)) {
const path = value.replace(/\{|\}/g, '').replace('.value', '').split('.');
return `var(--${cssNameTransform({ path })})`;
}
return value;
}
/**
* This will take a design token and add some data to it for it
* to be used in JS/CSS. It will create its CSS var name and update
* the value to use a CSS var if it is a reference. It will also
* add a `.toString()` method to make it easier to use in JS.
*
* We should see if there is a way to share this logic with style dictionary...
*/
const setupToken = ({ token, path }) => {
const name = `--${cssNameTransform({ path })}`;
const { value: original } = token;
const value = cssValue(token);
return { name, original, path, value, toString: () => `var(${name})` };
};
/**
* Recursive function that will walk down the token object
* and perform the setupToken function on each token.
* Similar to what Style Dictionary does.
*/
function setupTokens({ tokens, path = [], setupToken, }) {
if (has(tokens, 'value')) {
return setupToken({ token: tokens, path });
}
const output = {};
for (const name in tokens) {
if (has(tokens, name)) {
const value = tokens[name];
const nextTokens = isObject(value) ? value : { value };
output[name] = setupTokens({
tokens: nextTokens,
path: path.concat(name),
setupToken,
});
}
}
return output;
}
// Internal Style Dictionary methods
// copied from amzn/style-dictionary with the owner's permission
/**
* Takes an plain javascript object and will make a flat array of all the leaf nodes.
* A leaf node in this context has a 'value' property. Potentially refactor this to
* be more generic.
* @private
* @param {Object} properties - The plain object you want flattened into an array.
* @param {Array} [to_ret=[]] - Properties array. This function is recursive therefore this is what gets passed along.
* @return {Array}
*/
function flattenProperties(properties, to_ret) {
to_ret = to_ret || [];
for (var name in properties) {
if (has(properties, name)) {
if (isObject(properties[name]) && 'value' in properties[name]) {
to_ret.push(properties[name]);
}
else if (isObject(properties[name])) {
flattenProperties(properties[name], to_ret);
}
}
}
return to_ret;
}
/**
* Performs an deep extend on the objects, from right to left.
* @private
* @param {Object[]} objects - An array of JS objects
* @param {Function} collision - A function to be called when a merge collision happens.
* @param {string[]} path - (for internal use) An array of strings which is the current path down the object when this is called recursively.
* @returns {Object}
*/
function deepExtend(objects, collision, path) {
if (objects == null)
return {};
var src, copyIsArray, copy, name, options, clone, target = objects[0] || {}, i = 1, length = objects.length;
path = path || [];
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object') {
target = {};
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = objects[i]) != null) {
// Extend the base object
for (name in options) {
if (!has(options, name))
continue;
if (name === '__proto__')
continue;
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy) {
continue;
}
// Recurse if we're merging plain objects or arrays
if (copy && (isObject(copy) || (copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
}
else {
clone = src && isObject(src) ? src : {};
}
var nextPath = path.slice(0);
nextPath.push(name);
// Never move original objects, clone them
target[name] = deepExtend([clone, copy], collision, nextPath);
// Don't bring in undefined values
}
else if (copy !== undefined) {
if (src != null && typeof collision == 'function') {
collision({ target: target, copy: options, path: path, key: name });
}
target[name] = copy;
}
}
}
}
return target;
}
export { CSS_VARIABLE_PREFIX, cssNameTransform, cssValue, deepExtend, flattenProperties, isDesignToken, isShadowTokenObject, propsToString, referenceValue, setupToken, setupTokens };