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:
@@ -0,0 +1,228 @@
|
||||
# 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<ThemeTokens>
|
||||
```
|
||||
|
||||
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 `<html>`. 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 `<html>`, 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
|
||||
```
|
||||
Reference in New Issue
Block a user