UNPKG

outsystems-design-tokens

Version:

Store the Design Tokens used on the Ionic Framework and Widgets Library

518 lines (432 loc) 18.3 kB
let variablesPrefix = ''; // Variable that holds the prefix used on all css variables generated let classAndScssPrefix = ''; // Variable that holds the prefix used on all css utility-classes and scss variables generated let scssVariablePrefix = ''; // Variable that holds the prefix specifically for SCSS variable names // Set the variable prefix value export function setVariablePrefixValue(prefix) { variablesPrefix = prefix; return variablesPrefix; } export function setClassesAndScssPrefixValue(prefix) { classAndScssPrefix = prefix; return classAndScssPrefix; } export function setScssVariablePrefix(prefix) { scssVariablePrefix = prefix; return scssVariablePrefix; } // Generates a valid rgba() color export function getRgbaValue(propValue) { // Check if its rgba color const isRgba = hexToRgba(propValue); // If it is, then compose rgba() color, otherwise use the normal color if (isRgba !== null) { return (propValue = `rgba(${isRgba.r}, ${isRgba.g}, ${isRgba.b},${isRgba.a})`); } else { return propValue; } } // Translates an hex color value to rgb export function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), } : null; } // Translates an hex color value to rgba function hexToRgba(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), a: Math.round((parseInt(result[4], 16) * 100) / 255) / 100, } : null; } // Utility function to remove consecutive repeated words export function removeConsecutiveRepeatedWords(str) { return str.replace(/(\b\w+\b)(-\1)+/g, '$1'); } // Helper to replace the CSS prefix with SCSS prefix in a name function replaceWithScssPrefix(name) { // If scssVariablePrefix is the same as variablesPrefix, no replacement needed if (scssVariablePrefix === variablesPrefix || !scssVariablePrefix) { return name; } // If name starts with the CSS prefix, replace it with the SCSS prefix if (variablesPrefix && name.startsWith(`${variablesPrefix}-`)) { return name.replace(`${variablesPrefix}-`, `${scssVariablePrefix}-`); } // If no CSS prefix was set, add the SCSS prefix if (!variablesPrefix && scssVariablePrefix) { return `${scssVariablePrefix}-${name}`; } return name; } // Helper function to create SCSS variable name (propName already has prefix from Style Dictionary) function scssVar(name) { return `$${replaceWithScssPrefix(name)}`; } // Helper function to create CSS custom property name (propName already has prefix from Style Dictionary) function cssVar(name) { return `--${name}`; } // Helper function to create class name (propName already has prefix from Style Dictionary) function className(name) { return `.${name}`; } // Helper function to create CSS custom property name (for CSS vars in utility classes) function cssPropVar(name) { return `--${name}`; } // Generates a reference variable for an alias token type // (e.g., $ion-border-default: var(--ion-border-default, #d5d5d5) → $ion-border-default: var(--ion-border-default, $ion-primitives-neutral-400)) export function getAliasReferenceVariable(prop) { if (typeof prop.$value === 'string' && prop.$value.startsWith('{') && prop.$value.endsWith('}')) { // Remove curly braces and replace dots with dashes let ref = prop.$value.slice(1, -1).replace(/\./g, '-'); // Remove consecutive repeated words (e.g., border-border-radius-0 → border-radius-0) ref = removeConsecutiveRepeatedWords(ref); // Add prefix if it exists const fullRef = variablesPrefix ? `${variablesPrefix}-${ref}` : ref; return scssVar(fullRef); } return null; } // Generates a valid box-shadow value from a shadow Design Token structure for CSS custom properties export function generateCssShadowValue(prop, propName) { const cssShadow = prop.$value.map(shadow => { const color = getRgbaValue(shadow.color); return `${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.spread}px ${color}`; }).join(', '); return `--${propName}: ${cssShadow};`; } // Generates a valid box-shadow value from a shadow Design Token structure for SCSS variables export function generateShadowValue(prop, propName) { const cssShadow = prop.$value.map(shadow => { const color = getRgbaValue(shadow.color); return `${shadow.x}px ${shadow.y}px ${shadow.blur}px ${shadow.spread}px ${color}`; }).join(', '); return `${scssVar(propName)}: var(${cssVar(propName)}, ${cssShadow});`; } // Generates a valid font-size value from a font-size Design Token structure export function generateFontSizeValue(prop, propName, variableType = 'css') { if (variableType === 'scss') { return `${scssVar(propName)}: var(${cssVar(propName)}, ${prop.$value});`; } else { // For CSS custom properties, just return the pixel value return `--${propName}: ${prop.$value};`; } } // Generates a valid font-family value from a font-family Design Token structure export function generateFontFamilyValue(prop, propName, variableType = 'css') { // Remove the last word from the token, as it contains the name of the font, which we don't want to be included on the generated variables const _propName = propName.split('-').slice(0, -1).join('-'); return variableType === 'scss' ? `${scssVar(_propName)}: var(${cssVar(_propName)}, ${prop.$value});` : `--${_propName}: ${prop.$value};`; } // Generates a reference variable name for CSS custom properties (for aliases) function getCssAliasReferenceVariable(prop) { if (typeof prop.$value === 'string' && prop.$value.startsWith('{') && prop.$value.endsWith('}')) { // Remove curly braces and replace dots with dashes let ref = prop.$value.slice(1, -1).replace(/\./g, '-'); // Remove consecutive repeated words (e.g., border-border-radius-0 → border-radius-0) ref = removeConsecutiveRepeatedWords(ref); return `--${ref}`; } return null; } // Generates CSS custom properties for the :root format with alias support export function generateCssValue(prop, propName) { // Check if this token is an alias (references another token) const isAlias = typeof prop.original.$value === 'string' && prop.original.$value.startsWith('{') && prop.original.$value.endsWith('}'); const value = prop.$value; let mainValue; if (isAlias) { // Extract the referenced token name let ref = prop.original.$value.slice(1, -1).replace(/\./g, '-'); ref = removeConsecutiveRepeatedWords(ref); const refVarName = variablesPrefix ? `--${variablesPrefix}-${ref}` : `--${ref}`; // Use var() with the resolved value as fallback mainValue = `--${propName}: var(${refVarName}, ${value});`; } else { // Not an alias, use the value directly mainValue = `--${propName}: ${value};`; } // Always generate the -rgb variable if it's a color const rgb = hexToRgb(prop.$value); let rgbDeclaration = ''; if (rgb) { rgbDeclaration = `\n--${propName}-rgb: ${rgb.r}, ${rgb.g}, ${rgb.b};`; } return `${mainValue}${rgbDeclaration}`; } // Generates SCSS variables with CSS variable fallbacks for aliases export function generateValue(prop, propName) { // Use the original value to detect aliases const aliasVar = getAliasReferenceVariable({ $value: prop.original.$value }); const value = prop.$value; // Always generate the main variable let mainValue; if (aliasVar) { mainValue = `${scssVar(propName)}: var(${cssVar(propName)}, ${aliasVar});`; } else { mainValue = `${scssVar(propName)}: var(${cssVar(propName)}, ${value});`; } // Always generate the -rgb variable if it's a color const rgb = hexToRgb(prop.$value); let rgbDeclaration = ''; if (rgb) { rgbDeclaration = `\n${scssVar(propName + '-rgb')}: var(${cssVar(propName + '-rgb')}, ${rgb.r}, ${rgb.g}, ${rgb.b});`; } return `${mainValue}${rgbDeclaration}`; } // Generates a typography based css utility-class or scss variable from a typography token structure export function generateTypographyOutput(prop, propName, isVariable) { const typography = prop.original.$value; // Extract the part after the last dot and trim any extraneous characters const extractLastPart = (str) => str.split('.').pop().replace(/[^\w-]/g, ''); const _initialWrapper = isVariable ? ': (' : ` {`; const _endWrapper = isVariable ? ')' : `}`; const _prefix = isVariable ? '$' : `.`; const _endChar = isVariable ? ',' : ';'; // propName already has the prefix from Style Dictionary, so use it directly // Build the font property names with the same prefix that's in propName const prefix = variablesPrefix ? `${variablesPrefix}-` : ''; const fontSizeName = `${prefix}font-size-${extractLastPart(typography.fontSize)}`; const fontWeightName = `${prefix}font-weight-${extractLastPart(typography.fontWeight)}`; const letterSpacingName = `${prefix}font-letter-spacing-${extractLastPart(typography.letterSpacing) || 0}`; const lineHeightName = `${prefix}font-line-height-${extractLastPart(typography.lineHeight)}`; // This exact format is needed so that it compiles the tokens with the expected lint rules if (isVariable) { // SCSS variable format (no leading newline, no extra indentation) return `${_prefix}${replaceWithScssPrefix(propName)}${_initialWrapper} font-size: ${scssVar(fontSizeName)}${_endChar} font-style: ${prop.attributes.item?.toLowerCase() === 'italic' ? 'italic' : 'normal'}${_endChar} font-weight: ${scssVar(fontWeightName)}${_endChar} letter-spacing: ${scssVar(letterSpacingName)}${_endChar} line-height: ${scssVar(lineHeightName)}${_endChar} text-transform: ${typography.textCase}${_endChar} text-decoration: ${typography.textDecoration}${_endChar} ${_endWrapper};`; } else { // Utility class format (with leading newline and indentation) return ` ${_prefix}${propName}${_initialWrapper} font-size: $${fontSizeName}${_endChar} font-style: ${prop.attributes.item?.toLowerCase() === 'italic' ? 'italic' : 'normal'}${_endChar} font-weight: $${fontWeightName}${_endChar} letter-spacing: $${letterSpacingName}${_endChar} line-height: $${lineHeightName}${_endChar} text-transform: ${typography.textCase}${_endChar} text-decoration: ${typography.textDecoration}${_endChar} ${_endWrapper}; `; } } // Generates a color based css utility-class from a color Design Token structure export function generateColorUtilityClasses(prop, className) { const isBg = className.includes('bg'); const cssProp = isBg ? 'background-color' : 'color'; return `.${className} { --${cssProp}: #{$${className}}; ${cssProp}: $${className}; }`; } // Generates margin and padding utility classes to match the token-agnostic // utilities provided by the Ionic Framework export function generateDefaultSpaceUtilityClasses() { const zeroMarginPaddingToken = variablesPrefix ? `${variablesPrefix}-space-0` : 'space-0'; const defaultMarginPaddingToken = variablesPrefix ? `${variablesPrefix}-space-400` : 'space-400'; const classPrefix = variablesPrefix ? `${variablesPrefix}-` : ''; const marginPaddingTemplate = (type) => ` .${classPrefix}no-${type} { --${type}-top: #{$${zeroMarginPaddingToken}}; --${type}-end: #{$${zeroMarginPaddingToken}}; --${type}-bottom: #{$${zeroMarginPaddingToken}}; --${type}-start: #{$${zeroMarginPaddingToken}}; ${type}-block-start: $${zeroMarginPaddingToken}; ${type}-inline-end: $${zeroMarginPaddingToken}; ${type}-block-end: $${zeroMarginPaddingToken}; ${type}-inline-start: $${zeroMarginPaddingToken}; } .${classPrefix}${type} { --${type}-top: #{$${defaultMarginPaddingToken}}; --${type}-end: #{$${defaultMarginPaddingToken}}; --${type}-bottom: #{$${defaultMarginPaddingToken}}; --${type}-start: #{$${defaultMarginPaddingToken}}; ${type}-block-start: $${defaultMarginPaddingToken}; ${type}-inline-end: $${defaultMarginPaddingToken}; ${type}-block-end: $${defaultMarginPaddingToken}; ${type}-inline-start: $${defaultMarginPaddingToken}; } .${classPrefix}${type}-top { --${type}-top: #{$${defaultMarginPaddingToken}}; ${type}-block-start: $${defaultMarginPaddingToken}; } .${classPrefix}${type}-end { --${type}-end: #{$${defaultMarginPaddingToken}}; ${type}-inline-end: $${defaultMarginPaddingToken}; } .${classPrefix}${type}-bottom { --${type}-bottom: #{$${defaultMarginPaddingToken}}; ${type}-block-end: $${defaultMarginPaddingToken}; } .${classPrefix}${type}-start { --${type}-start: #{$${defaultMarginPaddingToken}}; ${type}-inline-start: $${defaultMarginPaddingToken}; } .${classPrefix}${type}-vertical { --${type}-top: #{$${defaultMarginPaddingToken}}; --${type}-bottom: #{$${defaultMarginPaddingToken}}; ${type}-block-start: $${defaultMarginPaddingToken}; ${type}-block-end: $${defaultMarginPaddingToken}; } .${classPrefix}${type}-horizontal { --${type}-start: #{$${defaultMarginPaddingToken}}; --${type}-end: #{$${defaultMarginPaddingToken}}; ${type}-inline-start: $${defaultMarginPaddingToken}; ${type}-inline-end: $${defaultMarginPaddingToken}; } `; return `${marginPaddingTemplate('margin')}\n${marginPaddingTemplate('padding')}`; } // Generates a margin or padding based css utility-class from a space Design Token structure export function generateSpaceUtilityClasses(prop, className) { // className already has the prefix from Style Dictionary (e.g., "token-space-0") // We need to extract the numeric value and remove "space" // From "token-space-0" to "token-gap-0", "token-margin-0", "token-margin-top-0", etc. const extractPrefixAndValue = (name) => { // If there's a prefix, extract it and remove "space" if (variablesPrefix) { const regex = new RegExp(`^${variablesPrefix}-space-(.*)`); const match = name.match(regex); if (match) { return { prefix: variablesPrefix, value: match[1] }; } } // No prefix case - remove "space-" prefix const spaceRemoved = name.replace(/^space-/, ''); return { prefix: '', value: spaceRemoved }; }; const { prefix, value } = extractPrefixAndValue(className); const buildClassName = (type, direction = null) => { // Build class name as: {prefix}-{type}-{direction}-{value} // or {prefix}-{type}-{value} if no direction const parts = []; if (prefix) parts.push(prefix); parts.push(type); if (direction) parts.push(direction); parts.push(value); return parts.join('-'); }; const marginPaddingTemplate = (type) => { const baseClassName = buildClassName(type); const topClassName = buildClassName(type, 'top'); const endClassName = buildClassName(type, 'end'); const bottomClassName = buildClassName(type, 'bottom'); const startClassName = buildClassName(type, 'start'); return ` .${baseClassName} { --${type}-top: #{$${className}}; --${type}-end: #{$${className}}; --${type}-bottom: #{$${className}}; --${type}-start: #{$${className}}; ${type}-block-start: $${className}; ${type}-inline-end: $${className}; ${type}-block-end: $${className}; ${type}-inline-start: $${className}; } .${topClassName} { --${type}-top: #{$${className}}; ${type}-block-start: $${className}; } .${endClassName} { --${type}-end: #{$${className}}; ${type}-inline-end: $${className}; } .${bottomClassName} { --${type}-bottom: #{$${className}}; ${type}-block-end: $${className}; } .${startClassName} { --${type}-start: #{$${className}}; ${type}-inline-start: $${className}; } `; }; // Add gap utility classes for gap tokens const gapClassName = buildClassName('gap'); const generateGapUtilityClasses = () =>` .${gapClassName} { gap: #{$${className}}; } `; return `${generateGapUtilityClasses()}\n${marginPaddingTemplate('margin')}\n${marginPaddingTemplate('padding')}`; } // Generates a valid box-shadow value from a shadow Design Token structure export function generateRadiusUtilityClasses(propName) { // propName already has the prefix from Style Dictionary return `.${propName} { --border-radius: #{$${propName}}; border-radius: $${propName}; }`; } // Generates a border based css utility-class from a font Design Token structure export function generateBorderUtilityClasses(prop, propName) { let attribute; switch (prop.attributes.type) { case 'border-radius': case 'border-style': attribute = prop.attributes.type; break; case 'border-size': attribute = 'border-width'; break; default: attribute = 'border-color'; } return `.${propName} { --${attribute}: #{$${propName}}; ${attribute}: $${propName}; }`; } // Generates a font based css utility-class from a font Design Token structure export function generateFontUtilityClasses(prop, propName) { return `.${propName} {\n ${prop.attributes.type}: $${propName};\n}`; } // Generates a valid box-shadow value from a shadow Design Token structure export function generateShadowUtilityClasses(propName) { // propName already has the prefix from Style Dictionary return `.${propName} { --box-shadow: #{$${propName}}; box-shadow: $${propName}; }`; } // Generates a utility class for a given token category and name export function generateUtilityClasses(tokenCategory, propName){ // propName already has the prefix from Style Dictionary return `.${propName} {\n ${tokenCategory}: $${propName};\n}`; } // Sets the source and target paths based on provided arguments or defaults export function setSourceAndTargetPaths(sourcePath, targetPath) { const defaultSourcePath = ['tokens/**/*.json']; const defaultTargetPath = 'dist/'; return { sourcePath: sourcePath && sourcePath !== 'undefined' ? [sourcePath] : defaultSourcePath, targetPath: targetPath && targetPath !== 'undefined' ? targetPath : defaultTargetPath }; } // Sets the variable type prefix (e.g., '--' for CSS custom properties, '$' for SCSS variables) export function setVariableTypePrefix(prefix) { variablesPrefix = prefix; return variablesPrefix; }