Initial commit: @ampeco/design-tokens v1.0.0

Design token system for AMPECO Embedded Web Apps with light/dark themes,
CSS custom properties, and runtime theme application via applyTheme().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kaloyan Danchev
2026-03-18 19:34:08 +02:00
commit 8a295c4acf
15 changed files with 2369 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
import type { ThemeTokens } from './tokens';
import { TOKEN_CSS_MAP } from './tokens';
import type { AppTheme, AmpecoDesignTokens } from './types';
import { lightTheme } from './light';
import { darkTheme } from './dark';
import { mapAmpecoTokens } from './tokenMapping';
/**
* Applies the given theme to the document root element.
*
* 1. Selects the base theme (light or dark).
* 2. Merges in any AMPECO design token overrides received from the mobile app.
* 3. Sets the `data-theme` attribute on <html> for CSS selectors.
* 4. Sets each token as a CSS custom property on <html>.
*
* @param theme - The theme mode ('LIGHT' or 'DARK').
* @param designTokens - Optional partial token overrides from the AMPECO mobile app.
*/
export function applyTheme(theme: AppTheme, designTokens?: AmpecoDesignTokens): void {
const base = theme === 'DARK' ? darkTheme : lightTheme;
const overrides = designTokens ? mapAmpecoTokens(designTokens) : {};
const merged: ThemeTokens = { ...base, ...overrides };
document.documentElement.setAttribute('data-theme', theme.toLowerCase());
for (const [key, value] of Object.entries(merged)) {
const cssVar = TOKEN_CSS_MAP[key as keyof ThemeTokens];
if (cssVar && value) {
document.documentElement.style.setProperty(cssVar, value);
}
}
}
+54
View File
@@ -0,0 +1,54 @@
import type { ThemeTokens } from './tokens';
/**
* Dark theme defaults.
* Values sourced from the AMPECO mobile app design system
* (mobile-2/src/design_system/migration_file/colors/newColorsFormatExample.ts).
*/
export const darkTheme: ThemeTokens = {
// Buttons
btnPrimaryBg: '#0066FF',
btnPrimaryText: '#FFFFFF',
btnPrimaryPressed: '#2C4965',
btnSecondaryBg: '#000000',
btnSecondaryBorder: '#0066FF',
btnSecondaryText: '#FFFFFF',
// Surfaces
surfaceBg: '#000000',
surfaceElevation1: '#1A2D3F',
surfaceElevation2: '#2C4965',
// Text
textBase: '#FFFFFF',
textSupport: '#999999',
textPlaceholder: '#999999',
// Feedback
feedbackSuccess: '#61E775',
feedbackSuccessSubtle: '#1D4523',
feedbackError: '#FF5454',
feedbackErrorSubtle: '#4C1919',
feedbackWarning: '#FFC062',
feedbackWarningSubtle: '#4C3A1D',
feedbackInfo: '#3C82EA',
feedbackInfoSubtle: '#122746',
// Input
inputFill: '#1A2D3F',
inputBorder: '#2C4965',
inputBorderActive: '#3B658C',
inputText: '#FFFFFF',
inputPlaceholder: '#999999',
// Separator
separator: '#3B658C',
// Booking status (shared across themes)
statusAccepted: '#2F83FF',
statusReserved: '#24A629',
statusCompleted: '#808080',
statusCancelled: '#FFB23E',
statusNoShow: '#FF6868',
statusFailed: '#B71C1C',
};
+16
View File
@@ -0,0 +1,16 @@
// Types
export type { AppTheme, AmpecoDesignTokens } from './types';
export type { ThemeTokens } from './tokens';
// Token definitions and CSS variable map
export { TOKEN_CSS_MAP } from './tokens';
// Theme defaults
export { lightTheme } from './light';
export { darkTheme } from './dark';
// Token mapping (AMPECO mobile -> EWA)
export { AMPECO_TO_EWA_MAP, mapAmpecoTokens } from './tokenMapping';
// Theme application
export { applyTheme } from './apply';
+54
View File
@@ -0,0 +1,54 @@
import type { ThemeTokens } from './tokens';
/**
* Light theme defaults.
* Values sourced from the AMPECO mobile app design system
* (mobile-2/src/design_system/migration_file/colors/newColorsFormatExample.ts).
*/
export const lightTheme: ThemeTokens = {
// Buttons
btnPrimaryBg: '#00A573',
btnPrimaryText: '#FFFFFF',
btnPrimaryPressed: '#D7E4DC',
btnSecondaryBg: '#FFFFFF',
btnSecondaryBorder: '#00A573',
btnSecondaryText: '#000000',
// Surfaces
surfaceBg: '#FFFFFF',
surfaceElevation1: '#EEF2EF',
surfaceElevation2: '#D7E4DC',
// Text
textBase: '#000000',
textSupport: '#808080',
textPlaceholder: '#808080',
// Feedback
feedbackSuccess: '#24A629',
feedbackSuccessSubtle: '#BDE4BF',
feedbackError: '#FF6868',
feedbackErrorSubtle: '#FFD2D2',
feedbackWarning: '#FFB23E',
feedbackWarningSubtle: '#FFE8C5',
feedbackInfo: '#2F83FF',
feedbackInfoSubtle: '#C1DAFF',
// Input
inputFill: '#EEF2EF',
inputBorder: '#D7E4DC',
inputBorderActive: '#C8DAD7',
inputText: '#000000',
inputPlaceholder: '#808080',
// Separator
separator: '#C8DAD7',
// Booking status (shared across themes)
statusAccepted: '#2F83FF',
statusReserved: '#24A629',
statusCompleted: '#808080',
statusCancelled: '#FFB23E',
statusNoShow: '#FF6868',
statusFailed: '#B71C1C',
};
+45
View File
@@ -0,0 +1,45 @@
import type { ThemeTokens } from './tokens';
import type { AmpecoDesignTokens } from './types';
/**
* Maps AMPECO mobile app token property names to EWA ThemeTokens keys.
* The mobile app sends tokens using its own naming convention; this map
* translates them to the EWA internal token names.
*/
export const AMPECO_TO_EWA_MAP: Record<string, keyof ThemeTokens> = {
buttonsButtonPrimaryBackground: 'btnPrimaryBg',
buttonsButtonPrimaryText: 'btnPrimaryText',
buttonsButtonSecondaryBackground: 'btnSecondaryBg',
buttonsButtonSecondaryBorder: 'btnSecondaryBorder',
buttonsButtonSecondaryText: 'btnSecondaryText',
surfaceSurfaceBackground: 'surfaceBg',
surfaceSurfaceElevation1: 'surfaceElevation1',
surfaceSurfaceElevation2: 'surfaceElevation2',
textsTextBase: 'textBase',
textsTextSupport: 'textSupport',
textsTextPlaceholder: 'textPlaceholder',
feedbackColorFeedbackSuccess: 'feedbackSuccess',
feedbackColorFeedbackError: 'feedbackError',
feedbackColorFeedbackWarning: 'feedbackWarning',
feedbackColorFeedbackInfo: 'feedbackInfo',
inputFormsInputBorder: 'inputBorder',
inputFormsInputFill: 'inputFill',
inputFormsInputTextLabels: 'inputText',
};
/**
* Converts AMPECO mobile design tokens into the EWA ThemeTokens format.
* Only tokens that are present and have a truthy value are included in the result.
*/
export function mapAmpecoTokens(tokens: AmpecoDesignTokens): Partial<ThemeTokens> {
const result: Partial<ThemeTokens> = {};
for (const [ampecoKey, ewaKey] of Object.entries(AMPECO_TO_EWA_MAP)) {
const value = tokens[ampecoKey as keyof AmpecoDesignTokens];
if (value) {
result[ewaKey] = value;
}
}
return result;
}
+102
View File
@@ -0,0 +1,102 @@
/**
* Complete set of EWA theme tokens.
* Each key maps to a CSS custom property via TOKEN_CSS_MAP.
*/
export interface ThemeTokens {
// Buttons
btnPrimaryBg: string;
btnPrimaryText: string;
btnPrimaryPressed: string;
btnSecondaryBg: string;
btnSecondaryBorder: string;
btnSecondaryText: string;
// Surfaces
surfaceBg: string;
surfaceElevation1: string;
surfaceElevation2: string;
// Text
textBase: string;
textSupport: string;
textPlaceholder: string;
// Feedback
feedbackSuccess: string;
feedbackSuccessSubtle: string;
feedbackError: string;
feedbackErrorSubtle: string;
feedbackWarning: string;
feedbackWarningSubtle: string;
feedbackInfo: string;
feedbackInfoSubtle: string;
// Input
inputFill: string;
inputBorder: string;
inputBorderActive: string;
inputText: string;
inputPlaceholder: string;
// Separator
separator: string;
// Booking status
statusAccepted: string;
statusReserved: string;
statusCompleted: string;
statusCancelled: string;
statusNoShow: string;
statusFailed: string;
}
/**
* Maps each ThemeTokens key to the corresponding CSS custom property name.
*/
export const TOKEN_CSS_MAP: Record<keyof ThemeTokens, string> = {
// Buttons
btnPrimaryBg: '--btn-primary-bg',
btnPrimaryText: '--btn-primary-text',
btnPrimaryPressed: '--btn-primary-pressed',
btnSecondaryBg: '--btn-secondary-bg',
btnSecondaryBorder: '--btn-secondary-border',
btnSecondaryText: '--btn-secondary-text',
// Surfaces
surfaceBg: '--surface-bg',
surfaceElevation1: '--surface-elevation-1',
surfaceElevation2: '--surface-elevation-2',
// Text
textBase: '--text-base',
textSupport: '--text-support',
textPlaceholder: '--text-placeholder',
// Feedback
feedbackSuccess: '--feedback-success',
feedbackSuccessSubtle: '--feedback-success-subtle',
feedbackError: '--feedback-error',
feedbackErrorSubtle: '--feedback-error-subtle',
feedbackWarning: '--feedback-warning',
feedbackWarningSubtle: '--feedback-warning-subtle',
feedbackInfo: '--feedback-info',
feedbackInfoSubtle: '--feedback-info-subtle',
// Input
inputFill: '--input-fill',
inputBorder: '--input-border',
inputBorderActive: '--input-border-active',
inputText: '--input-text',
inputPlaceholder: '--input-placeholder',
// Separator
separator: '--separator',
// Booking status
statusAccepted: '--status-accepted',
statusReserved: '--status-reserved',
statusCompleted: '--status-completed',
statusCancelled: '--status-cancelled',
statusNoShow: '--status-no-show',
statusFailed: '--status-failed',
};
+29
View File
@@ -0,0 +1,29 @@
/**
* Theme mode identifier. Matches the value sent by the AMPECO mobile app.
*/
export type AppTheme = 'LIGHT' | 'DARK';
/**
* Design tokens as received from the AMPECO mobile app via postMessage.
* All fields are optional because the app may send a partial override set.
*/
export interface AmpecoDesignTokens {
buttonsButtonPrimaryBackground?: string;
buttonsButtonPrimaryText?: string;
buttonsButtonSecondaryBackground?: string;
buttonsButtonSecondaryBorder?: string;
buttonsButtonSecondaryText?: string;
surfaceSurfaceBackground?: string;
surfaceSurfaceElevation1?: string;
surfaceSurfaceElevation2?: string;
textsTextBase?: string;
textsTextSupport?: string;
textsTextPlaceholder?: string;
feedbackColorFeedbackSuccess?: string;
feedbackColorFeedbackError?: string;
feedbackColorFeedbackWarning?: string;
feedbackColorFeedbackInfo?: string;
inputFormsInputBorder?: string;
inputFormsInputFill?: string;
inputFormsInputTextLabels?: string;
}