@react-spectrum/s2
Version:
Spectrum 2 UI components in React
150 lines (132 loc) • 5.8 kB
text/typescript
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// package.json in this directory is not the real package.json. Lint rule not smart enough.
// eslint-disable-next-line rulesdir/imports
import * as tokens from '@adobe/spectrum-tokens/dist/json/variables.json';
export function getToken(name: keyof typeof tokens): string {
return (tokens[name] as any).value;
}
export interface ColorToken {
type: 'color',
light: string,
dark: string,
forcedColors?: string
}
export function colorToken(name: keyof typeof tokens): ColorToken | ColorRef {
let token = tokens[name] as typeof tokens['gray-25'] | typeof tokens['neutral-content-color-default'];
if ('ref' in token) {
return {
type: 'ref',
light: token.ref.slice(1, -1).replace('-color', ''),
dark: token.ref.slice(1, -1).replace('-color', '')
};
}
return {
type: 'color',
light: token.sets.light.value,
dark: token.sets.dark.value
};
}
export function rawColorToken(name: keyof typeof tokens): string {
let token = tokens[name] as typeof tokens['gray-25'];
return `light-dark(${token.sets.light.value}, ${token.sets.dark.value})`;
}
export interface ColorRef {
type: 'ref',
light: string,
dark: string,
forcedColors?: string
}
export function weirdColorToken(name: keyof typeof tokens): ColorRef {
let token = tokens[name] as typeof tokens['accent-background-color-default'];
return {
type: 'ref',
light: token.sets.light.ref.slice(1, -1).replace('-color', ''),
dark: token.sets.dark.ref.slice(1, -1).replace('-color', '')
};
}
type ReplaceColor<S extends string> = S extends `${infer S}-color-${infer N}` ? `${S}-${N}` : S;
export function colorScale<S extends string>(scale: S): Record<ReplaceColor<Extract<keyof typeof tokens, `${S}-${number}`>>, ReturnType<typeof colorToken>> {
let res: any = {};
let re = new RegExp(`^${scale}-\\d+$`);
for (let token in tokens) {
if (re.test(token)) {
res[token.replace('-color', '')] = colorToken(token as keyof typeof tokens);
}
}
return res;
}
export function simpleColorScale<S extends string>(scale: S): Record<Extract<keyof typeof tokens, `${S}-${number}`>, string> {
let res: any = {};
let re = new RegExp(`^${scale}-\\d+$`);
for (let token in tokens) {
if (re.test(token)) {
res[token] = (tokens as any)[token].value;
}
}
return res;
}
function extractOpacity(color: string): number {
return Number(color.match(/^rgba\(\d+, \d+, \d+, ([.\d]+)\)$/)?.[1] ?? 1);
}
/**
* This swaps between white or black based on the background color.
* After testing against all RGB background colors, 49.44 minimizes the number of WCAG 4.5:1 contrast failures.
*/
export function autoStaticColor(bg = 'var(--s2-container-bg)', alpha = 1): string {
return `lch(from ${bg} calc((49.44 - l) * infinity) 0 0 / ${alpha})`;
}
interface ColorScaleValues {
'transparent-overlay-25': string,
'transparent-overlay-50': string,
'transparent-overlay-75': string,
'transparent-overlay-100': string,
'transparent-overlay-200': string,
'transparent-overlay-300': string,
'transparent-overlay-400': string,
'transparent-overlay-500': string,
'transparent-overlay-600': string,
'transparent-overlay-700': string,
'transparent-overlay-800': string,
'transparent-overlay-900': string,
'transparent-overlay-1000': string
}
export function generateOverlayColorScale(bg = 'var(--s2-container-bg)'): ColorScaleValues {
return {
'transparent-overlay-25': autoStaticColor(bg, extractOpacity(getToken('transparent-white-25'))),
'transparent-overlay-50': autoStaticColor(bg, extractOpacity(getToken('transparent-white-50'))),
'transparent-overlay-75': autoStaticColor(bg, extractOpacity(getToken('transparent-white-75'))),
'transparent-overlay-100': autoStaticColor(bg, extractOpacity(getToken('transparent-white-100'))),
'transparent-overlay-200': autoStaticColor(bg, extractOpacity(getToken('transparent-white-200'))),
'transparent-overlay-300': autoStaticColor(bg, extractOpacity(getToken('transparent-white-300'))),
'transparent-overlay-400': autoStaticColor(bg, extractOpacity(getToken('transparent-white-400'))),
'transparent-overlay-500': autoStaticColor(bg, extractOpacity(getToken('transparent-white-500'))),
'transparent-overlay-600': autoStaticColor(bg, extractOpacity(getToken('transparent-white-600'))),
'transparent-overlay-700': autoStaticColor(bg, extractOpacity(getToken('transparent-white-700'))),
'transparent-overlay-800': autoStaticColor(bg, extractOpacity(getToken('transparent-white-800'))),
'transparent-overlay-900': autoStaticColor(bg, extractOpacity(getToken('transparent-white-900'))),
'transparent-overlay-1000': autoStaticColor(bg, extractOpacity(getToken('transparent-white-1000')))
};
}
function pxToRem(px: string | number) {
if (typeof px === 'string') {
px = parseFloat(px);
}
return px / 16 + 'rem';
}
export function fontSizeToken(name: keyof typeof tokens): {default: string, touch: string} {
let token = tokens[name] as typeof tokens['font-size-100'];
return {
default: pxToRem(token.sets.desktop.value),
touch: pxToRem(token.sets.mobile.value)
};
}