diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 62737ee..90aca08 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["es5", "es2015.collection", "dom"], + "lib": ["es2017", "dom"], "types": [], "module": "esnext", "moduleResolution": "node", diff --git a/src/build/index.ts b/src/build/index.ts index e783cd0..758847a 100644 --- a/src/build/index.ts +++ b/src/build/index.ts @@ -13,4 +13,9 @@ export { Value, resolveTheme, resolveThemeWithPaths, + ReferenceTokens, + ColorReferenceTokens, + ColorPaletteInput, + ColorPaletteDefinition, + PaletteStep, } from '../shared/theme'; diff --git a/src/shared/theme/__tests__/builder.test.ts b/src/shared/theme/__tests__/builder.test.ts index 7081672..116e222 100644 --- a/src/shared/theme/__tests__/builder.test.ts +++ b/src/shared/theme/__tests__/builder.test.ts @@ -55,3 +55,26 @@ test('theme adds context', () => { }, }); }); + +test('theme adds reference tokens', () => { + builder.addReferenceTokens({ + color: { + neutral: { 900: '#242E3C' }, + warning: { 400: '#ff9900' }, + }, + }); + + const theme = builder.build(); + + expect(theme.referenceTokens).toMatchObject({ + color: { + neutral: { 900: '#242E3C' }, + warning: { 400: '#ff9900' }, + }, + }); + + expect(theme.tokens).toMatchObject({ + colorNeutral900: '#242E3C', + colorWarning400: '#ff9900', + }); +}); diff --git a/src/shared/theme/builder.ts b/src/shared/theme/builder.ts index c054ca6..4baccfb 100644 --- a/src/shared/theme/builder.ts +++ b/src/shared/theme/builder.ts @@ -1,6 +1,15 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Theme, Context, Mode } from './interfaces'; +import { + Theme, + Context, + Mode, + ReferenceTokens, + ColorReferenceTokens, + ColorPaletteInput, + PaletteStep, + ColorPaletteDefinition, +} from './interfaces'; export type TokenCategory = Record; @@ -46,7 +55,60 @@ export class ThemeBuilder { return this; } + addReferenceTokens(referenceTokens: ReferenceTokens): ThemeBuilder { + this.theme.referenceTokens = referenceTokens; + + // Process reference tokens and add generated tokens to theme + if (referenceTokens.color) { + const generatedTokens = this.processReferenceTokens(referenceTokens.color); + this.theme.tokens = { ...this.theme.tokens, ...generatedTokens }; + } + + return this; + } + build(): Theme { return this.theme; } + + private processReferenceTokens(colorTokens: ColorReferenceTokens): TokenCategory { + const generatedTokens: TokenCategory = {}; + + Object.entries(colorTokens).forEach(([colorName, paletteInput]) => { + const palette = this.processColorPaletteInput(paletteInput); + + // Add generated palette tokens with naming convention: colorPrimary50, colorPrimary600, etc. + Object.entries(palette).forEach(([step, value]) => { + const tokenName = `color${this.capitalize(colorName)}${step}`; + generatedTokens[tokenName] = value; + }); + }); + + return generatedTokens; + } + + // Right now just validates steps, but will also handle seed token color generation in a future PR + private processColorPaletteInput(input: ColorPaletteInput): ColorPaletteDefinition { + const validSteps: number[] = []; + // Add steps 50-1000 in increments of 50 + for (let i = 50; i <= 1000; i += 50) { + validSteps.push(i); + } + + const result: ColorPaletteDefinition = {}; + + // Add explicit step values + Object.entries(input).forEach(([step, value]) => { + const numStep = Number(step); + if (value && validSteps.includes(numStep)) { + result[numStep as PaletteStep] = value; + } + }); + + return result; + } + + private capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); + } } diff --git a/src/shared/theme/index.ts b/src/shared/theme/index.ts index 92a14a0..4c71d4d 100644 --- a/src/shared/theme/index.ts +++ b/src/shared/theme/index.ts @@ -11,6 +11,11 @@ export { GlobalValue, ModeValue, TypedModeValueOverride, + ReferenceTokens, + ColorReferenceTokens, + ColorPaletteInput, + ColorPaletteDefinition, + PaletteStep, } from './interfaces'; export { ThemeBuilder, TokenCategory } from './builder'; export { diff --git a/src/shared/theme/interfaces.ts b/src/shared/theme/interfaces.ts index 6a93e88..0e5d101 100644 --- a/src/shared/theme/interfaces.ts +++ b/src/shared/theme/interfaces.ts @@ -35,8 +35,62 @@ export interface Theme { modes: Record; tokenModeMap: Record; contexts: Record; + referenceTokens?: ReferenceTokens; } +/** + * Reference tokens enable seed-based palette generation and semantic token organization. + */ +export interface ReferenceTokens { + color?: ColorReferenceTokens; +} + +export interface ColorReferenceTokens { + primary?: ColorPaletteInput; + neutral?: ColorPaletteInput; + error?: ColorPaletteInput; + success?: ColorPaletteInput; + warning?: ColorPaletteInput; + info?: ColorPaletteInput; +} + +/** + * Color reference tokens organized by semantic color categories. + * Each category is defined as a palette definition with explicit color values. + */ +export type ColorPaletteInput = ColorPaletteDefinition; + +/** + * Palette steps available across all color types. Different color categories + * may use different subsets of these steps. + */ +export type PaletteStep = + | 50 + | 100 + | 150 + | 200 + | 250 + | 300 + | 350 + | 400 + | 450 + | 500 + | 550 + | 600 + | 650 + | 700 + | 750 + | 800 + | 850 + | 900 + | 950 + | 1000; + +/** + * Color palette definition with explicit color values for palette steps. + */ +export type ColorPaletteDefinition = Partial>; + type Tokens = Partial>; export interface Override {