# Architecture ## Overview `@ampeco/design-tokens` is the single source of truth for visual theming in the EWA (Embedded Web App). It bridges the AMPECO mobile app's design system to the web via CSS custom properties. The package has two consumers: 1. **CSS** -- imports `css/variables.css` for static defaults (colors, spacing, typography, radii, component sizes). 2. **JavaScript** -- calls `applyTheme()` at runtime to set color tokens based on the active theme and any overrides from the mobile app. ## Token Structure Tokens are organized into two layers: ### Themeable color tokens (JS-managed) Defined in `src/tokens.ts` as the `ThemeTokens` interface. These are the tokens that change between light and dark mode. Each token has a corresponding CSS custom property name in `TOKEN_CSS_MAP`. ``` ThemeTokens key CSS variable Category ------------------------------------------------------------ btnPrimaryBg --btn-primary-bg Buttons btnPrimaryText --btn-primary-text Buttons btnPrimaryPressed --btn-primary-pressed Buttons btnSecondaryBg --btn-secondary-bg Buttons btnSecondaryBorder --btn-secondary-border Buttons btnSecondaryText --btn-secondary-text Buttons surfaceBg --surface-bg Surfaces surfaceElevation1 --surface-elevation-1 Surfaces surfaceElevation2 --surface-elevation-2 Surfaces textBase --text-base Text textSupport --text-support Text textPlaceholder --text-placeholder Text feedbackSuccess --feedback-success Feedback feedbackSuccessSubtle --feedback-success-subtle Feedback feedbackError --feedback-error Feedback feedbackErrorSubtle --feedback-error-subtle Feedback feedbackWarning --feedback-warning Feedback feedbackWarningSubtle --feedback-warning-subtle Feedback feedbackInfo --feedback-info Feedback feedbackInfoSubtle --feedback-info-subtle Feedback inputFill --input-fill Input inputBorder --input-border Input inputBorderActive --input-border-active Input inputText --input-text Input inputPlaceholder --input-placeholder Input separator --separator Separator statusAccepted --status-accepted Booking status statusReserved --status-reserved Booking status statusCompleted --status-completed Booking status statusCancelled --status-cancelled Booking status statusNoShow --status-no-show Booking status statusFailed --status-failed Booking status ``` ### Static layout tokens (CSS-only) Defined only in `css/variables.css`. These do not change between themes and are not managed by JS: - **Spacing** -- 9-step scale from `--spacing-xxxs` (2px) to `--spacing-xxxl` (64px) - **Border radius** -- 4-step scale: `--radius-s` (8px), `--radius-m` (12px), `--radius-l` (16px), `--radius-xl` (32px) - **Typography** -- 12 type styles, each with `-size`, `-line-height`, and `-weight` variables. Font family: Inter with system fallbacks. - **Component sizes** -- 4-step scale: `--size-xs` (14px) to `--size-l` (44px) ## Theme System ### Light and dark defaults Two complete `ThemeTokens` objects are exported: - `lightTheme` (`src/light.ts`) -- Green primary (#00A573), white surfaces, black text. - `darkTheme` (`src/dark.ts`) -- Blue primary (#0066FF), black surfaces, white text. Both share identical booking status colors (these are semantic and theme-independent). Values are sourced from the AMPECO mobile app design system at: `mobile-2/src/design_system/migration_file/colors/newColorsFormatExample.ts` ### CSS fallback `css/variables.css` contains `:root` declarations matching the light theme. This ensures the page renders correctly even before JavaScript runs or if `applyTheme()` is never called. ## AMPECO Mobile Token Mapping The AMPECO mobile app sends design tokens via `postMessage` using its own naming convention. The mapping layer (`src/tokenMapping.ts`) translates these to EWA token keys. ### Naming convention translation ``` AMPECO mobile name EWA token key ----------------------------------------------------------- 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 ``` The AMPECO mobile app sends a subset of tokens as `AmpecoDesignTokens` (all fields optional). The EWA has additional tokens (e.g. `btnPrimaryPressed`, `feedbackSuccessSubtle`, `inputBorderActive`, `separator`, all `status*` tokens) that are not mapped from mobile tokens and always come from the base theme. ### mapAmpecoTokens() ```ts function mapAmpecoTokens(tokens: AmpecoDesignTokens): Partial ``` Iterates `AMPECO_TO_EWA_MAP`, looks up each AMPECO key in the input, and includes only truthy values in the result. Returns a partial token set suitable for spreading over a base theme. ## How applyTheme() Works at Runtime `applyTheme()` in `src/apply.ts` is the main entry point for theme application. Here is the exact sequence: ### Step 1: Select base theme ```ts const base = theme === 'DARK' ? darkTheme : lightTheme; ``` The `theme` parameter is `'LIGHT'` or `'DARK'` (matches `AppTheme` type, which corresponds to what the mobile app sends). ### Step 2: Map and merge AMPECO overrides ```ts const overrides = designTokens ? mapAmpecoTokens(designTokens) : {}; const merged: ThemeTokens = { ...base, ...overrides }; ``` Mobile app tokens override the base theme. Tokens not provided by the mobile app retain their base theme defaults. ### Step 3: Set data-theme attribute ```ts document.documentElement.setAttribute('data-theme', theme.toLowerCase()); ``` Sets `data-theme="light"` or `data-theme="dark"` on ``. This enables CSS selectors like `[data-theme="dark"] .card { ... }` for theme-conditional styling that cannot be expressed with CSS custom properties alone. ### Step 4: Write CSS custom properties ```ts 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); } } ``` Every token in the merged set is written as an inline style on ``, which overrides the `:root` defaults from `css/variables.css`. ### Sequence diagram ``` Mobile App EWA DOM | | | |-- postMessage ------>| | | { theme, tokens } | | | |-- applyTheme(theme, t) ->| | | 1. select base | | | 2. map + merge | | | 3. set data-theme | | | 4. set CSS vars | | | | | | [page re-renders | | with new colors] ``` ## CSS Variable Naming Conventions All CSS custom properties follow a flat, kebab-case naming scheme with category prefixes: | Prefix | Category | Examples | |---|---|---| | `--btn-` | Button colors | `--btn-primary-bg`, `--btn-secondary-text` | | `--surface-` | Surface/background | `--surface-bg`, `--surface-elevation-1` | | `--text-` | Text colors | `--text-base`, `--text-support` | | `--feedback-` | Feedback/status colors | `--feedback-error`, `--feedback-info-subtle` | | `--input-` | Form input | `--input-fill`, `--input-border-active` | | `--separator` | Divider lines | `--separator` | | `--status-` | Booking status | `--status-accepted`, `--status-no-show` | | `--spacing-` | Spacing scale | `--spacing-xs`, `--spacing-xxl` | | `--radius-` | Border radius | `--radius-s`, `--radius-xl` | | `--font-` | Typography | `--font-h1-size`, `--font-body-medium-weight` | | `--size-` | Component sizes | `--size-xs`, `--size-l` | No BEM, no nesting, no component-scoped prefixes. Variables are global and intended for use across all components. ## Build System The package uses [tsup](https://tsup.egoist.dev/) (configured in `tsup.config.ts`): - **Entry**: `src/index.ts` - **Formats**: ESM (`dist/index.js`) + CJS (`dist/index.cjs`) - **Types**: `dist/index.d.ts` and `dist/index.d.cts` - **Source maps**: enabled The CSS file is not processed by tsup. It ships as-is from `css/variables.css` and is exposed via the `"./css"` export map entry in `package.json`. ## File Dependency Graph ``` src/index.ts (barrel) |-- src/types.ts AppTheme, AmpecoDesignTokens |-- src/tokens.ts ThemeTokens, TOKEN_CSS_MAP |-- src/light.ts lightTheme (imports ThemeTokens from tokens.ts) |-- src/dark.ts darkTheme (imports ThemeTokens from tokens.ts) |-- src/tokenMapping.ts AMPECO_TO_EWA_MAP, mapAmpecoTokens() | (imports ThemeTokens from tokens.ts, | AmpecoDesignTokens from types.ts) |-- src/apply.ts applyTheme() (imports everything above) css/variables.css standalone, no imports ```