UNPKG

apphouse

Version:

Component library for React that uses observable state management and theme-able components.

442 lines (399 loc) 11 kB
import { CSSProperties } from 'glamor'; import { Palette } from '../Palette'; import { Theme } from '../Theme'; import { Token } from '../Token'; import ColorUtils from './color.utils'; import { ColorDefinition } from './color.interface'; import { CssPropertyStyle, StyleTokenReference } from '../style.interface'; import { tokenTypes } from '../token.interface'; interface ReferencePaletteTokenInfo { paletteId: string; paletteMode: string; colorId: string; } /** * Get palette info from reference * @param reference Token reference * @param selectedTheme light | dark * @returns { paletteId: string; paletteMode: string; colorId: string; } */ export const getPaletteInfoFromReferenceKey = ( reference: StyleTokenReference, selectedTheme?: 'dark' | 'light' ): ReferencePaletteTokenInfo | undefined => { let paletteId; let paletteMode; let colorId; if (reference.type === 'color') { // key can be in 2 different formats // base.primary.color or theme.color const referenceKey = reference.key.split('.'); if (referenceKey.length === 2) { // reference is a themed color const mode = referenceKey[0]; if (mode === 'theme') { paletteId = selectedTheme; paletteMode = selectedTheme; colorId = referenceKey[1]; } } else if (referenceKey.length === 3) { paletteId = referenceKey[1]; paletteMode = referenceKey[0]; colorId = referenceKey[2]; } } if (!paletteId || !paletteMode || !colorId) { return undefined; } return { paletteId, paletteMode, colorId }; }; export const isValidTokenKey = (tokenStringKey: string): boolean => { if (typeof tokenStringKey !== 'string') { // console.warn("key must be of type string"); return false; } const info = tokenStringKey.split('.'); if (info[0] === '0') { return false; } if (info.length < 2) { // console.warn( // `key does not have the minimum length for a reference key (${tokenStringKey})` // ); return false; } if (info[0] === 'base') { if (info.length !== 3) { // console.warn("base references must have 3 tokens"); return false; } } if (info[0] === 'theme') { if (info.length !== 2) { // console.warn("theme references must have 2 tokens"); return false; } } return true; }; export const getPaletteInfoFromStringKey = ( stringKey: string, selectedTheme?: 'dark' | 'light' ): ReferencePaletteTokenInfo | undefined => { if (!stringKey) { return undefined; } if (!isValidTokenKey(stringKey)) { return undefined; } const referenceInfo = stringKey.split('.'); if (referenceInfo.length === 0) { return undefined; } if (referenceInfo.length === 2) { if (referenceInfo[0] === 'theme') { if (!selectedTheme) { console.warn( 'attempted to get a themed token but no theme was provided' ); return undefined; } return { paletteId: selectedTheme, paletteMode: selectedTheme, colorId: referenceInfo[1] }; } } if (referenceInfo.length === 3) { const paletteMode = referenceInfo[0]; if (paletteMode === 'base') { return { paletteId: referenceInfo[1], paletteMode: referenceInfo[0], colorId: referenceInfo[2] }; } } return undefined; }; export const getTokenInfoFromReferenceKey = ( reference: StyleTokenReference ): TokenReferenceInfo | undefined => { let tokenType; let tokenKey; if (reference.type === 'token') { const referenceKey = reference.key.split('.'); if (referenceKey.length === 2) { tokenType = referenceKey[0]; if (!isValidTokenType(tokenType)) { console.log('getTokenInfoFromReferenceKey', reference); return undefined; } tokenKey = referenceKey[1]; } } if (!tokenType || !tokenKey) { return undefined; } const tokenId = getIdForTokenFromValue(tokenType, tokenKey); if (!tokenId) { return undefined; } return { tokenType, tokenKey, tokenId }; }; interface TokenReferenceInfo { tokenType: string; tokenKey: string; tokenId: string; } export const getTokenInfoFromStringKey = ( tokenReferenceString: string ): TokenReferenceInfo | undefined => { let tokenType; let tokenKey; if (tokenReferenceString.startsWith('rgba')) { // this is a string color return undefined; } if (!isValidTokenKey(tokenReferenceString)) { return undefined; } const referenceKey = tokenReferenceString.split('.'); if (referenceKey.length === 2) { tokenType = referenceKey[0]; if (!tokenTypes.includes(tokenType as any)) { return undefined; } if (!isValidTokenType(tokenType)) { return undefined; } tokenKey = referenceKey[1]; } if (!tokenType || !tokenKey) { return undefined; } const tokenId = getIdForTokenFromValue(tokenType, tokenKey); if (!tokenId) { return undefined; } return { tokenType, tokenKey, tokenId }; }; export const getIdForTokenFromValue = (type: string, key: string) => { if (!tokenTypes.includes(type as any)) { console.warn( `INVALID TOKEN VALUE(getIdForTokenFromValue): Attempted to get a token reference with invalid type ${type}` ); return undefined; } return `${type}.${key}`; }; export const isValidTokenType = (tokenType: any): boolean => { if (!tokenTypes.includes(tokenType)) { console.warn( `INVALID TOKEN TYPE(isValidTokenType): Attempted to get a token reference with invalid type ${tokenType}` ); return false; } return true; }; export const getCssWithThemedTokens = ({ value, lookup, withColorsFromPaletteId, withRawValue }: { value: CssPropertyStyle[]; lookup: { [id: string]: string }; withColorsFromPaletteId?: string; // this is actually the paletted id ('dark' or 'light' for themes things defaults) withRawValue?: boolean; }): CSSProperties => { const css: CSSProperties = {}; if (!value) { return css; } value.map((v) => { const property = v.property; if (property) { const camelCaseProperty = Theme.toCamelCase(property); if (typeof v.value === 'string' || typeof v.value === 'number') { // start default value css[camelCaseProperty] = v.value; if (withRawValue === true) { const key = withColorsFromPaletteId !== undefined ? `${v.value}`.replace( 'theme.', `theme.${withColorsFromPaletteId}.` ) : v.value; const referenceValue = lookup[key] || v.value; css[camelCaseProperty] = referenceValue; } } else { css[camelCaseProperty] = getCssWithThemedTokens({ value: v.value, lookup, withColorsFromPaletteId, withRawValue }); } } }); return css; }; export const getValueFromReference = ({ reference, withRaw, lookup, theme }: { reference?: any; theme: 'dark' | 'light' | undefined; withRaw?: boolean | undefined; lookup: { [id: string]: string }; }): string | number | undefined => { if (!reference) { return undefined; } const referenceValue = reference.key; if (!referenceValue) { return undefined; } if (reference?.type === 'palette') { let colorValue = reference.value; // here we need to update the reference value since it might be stale // if we are asking for the raw value we actually want the rgba string if (withRaw) { const paletteInfo = getPaletteInfoFromReferenceKey(reference, theme); // let's get the rgba string from the color palette if (paletteInfo) { const key = `${paletteInfo.paletteMode}.${paletteInfo.paletteId}.${paletteInfo.colorId}`; const color = lookup[key]; // defaulting to reference, missing rgb string colorValue = color; } else { // defaulting to reference colorValue = reference; } } else { const rgbValue = reference.value as ColorDefinition; const potentialColorValue = ColorUtils.toRgbaStringFromRgbaObject( rgbValue?.rgb ); if (potentialColorValue) { colorValue = potentialColorValue; } } return colorValue; } if (reference?.type === 'token') { let value: string | number | ColorDefinition = reference.value; if (withRaw) { value = reference.value; const tokenInfo = getTokenInfoFromReferenceKey(reference); if (tokenInfo) { const token = lookup[tokenInfo.tokenId]; value = token || reference.value; } } if (typeof value !== 'string' && typeof value !== 'number') { return JSON.stringify(value); } return value; } return referenceValue; }; export const getValueFromReferenceString = ({ referenceString, withRaw, tokens, colors, theme }: { referenceString?: any; withRaw?: boolean | undefined; tokens: Record<string, Token>; colors: Record<string, Palette>; theme?: 'dark' | 'light' | undefined; }): string | number | undefined => { if (typeof referenceString !== 'string') { return undefined; } if (referenceString === '0') { return referenceString; } if (isColorReferenceString(referenceString)) { const paletteInfo = getPaletteInfoFromStringKey(referenceString, theme); if (!withRaw) { return referenceString; } if (paletteInfo && withRaw) { let colorValue; // attempt to get value from reference string const palette = colors[paletteInfo.paletteId]; const paletteColors = palette?.colors; if (paletteColors) { const color = paletteColors[paletteInfo.colorId]; console.log('defaulting to reference, missing rgb string'); colorValue = color?.rgbString; } if (colorValue) { return colorValue; } else { console.log('defaulting to property value', referenceString); return referenceString; } } } const tokenInfo = getTokenInfoFromStringKey(referenceString); if (tokenInfo && withRaw) { // attempt to get value from token string const token = tokens[tokenInfo.tokenId]; if (token) { const tokenValue = tokens[tokenInfo.tokenId].value; if (tokenValue) { return tokenValue; } else { return referenceString; } } else { return referenceString; } } return referenceString; }; export const isColorReferenceString = (referenceString: string): boolean => { if (typeof referenceString !== 'string') { return false; } const ref = referenceString.split('.'); if (ref[0].startsWith('rgba')) { return false; } if (ref.length > 0) { return ( ref[0] === 'theme' || ref[0] === 'light' || ref[0] === 'dark' || ref[0] === 'base' ); } return false; };