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
+3
View File
@@ -0,0 +1,3 @@
node_modules/
dist/
*.tsbuildinfo
+133
View File
@@ -0,0 +1,133 @@
# @ampeco/design-tokens
Design tokens for the AMPECO EWA (Embedded Web App). Provides theme definitions (light/dark), a token-to-CSS-variable mapping, and a runtime `applyTheme()` function that bridges the AMPECO mobile app design system to CSS custom properties.
## Install
```bash
npm install @ampeco/design-tokens
```
## Exports
| Export path | What it provides |
|---|---|
| `@ampeco/design-tokens` | JS/TS: types, theme objects, `applyTheme()`, `mapAmpecoTokens()`, `TOKEN_CSS_MAP` |
| `@ampeco/design-tokens/css` | `css/variables.css` -- light-theme defaults plus spacing, radius, typography tokens |
## Usage
### JavaScript / TypeScript
```ts
import { applyTheme, lightTheme, darkTheme, TOKEN_CSS_MAP } from '@ampeco/design-tokens';
// Apply light theme (sets CSS vars on <html>)
applyTheme('LIGHT');
// Apply dark theme with AMPECO mobile token overrides
applyTheme('DARK', {
buttonsButtonPrimaryBackground: '#FF0000',
surfaceSurfaceBackground: '#111111',
});
// Read a theme object directly
console.log(lightTheme.btnPrimaryBg); // '#00A573'
console.log(darkTheme.surfaceBg); // '#000000'
// Look up the CSS variable name for a token
console.log(TOKEN_CSS_MAP.btnPrimaryBg); // '--btn-primary-bg'
```
### CSS
Import the stylesheet to get light-theme defaults before JS initialises:
```css
@import '@ampeco/design-tokens/css';
```
Then use the custom properties in your styles:
```css
.button-primary {
background: var(--btn-primary-bg);
color: var(--btn-primary-text);
font-size: var(--font-button-size);
font-weight: var(--font-button-weight);
line-height: var(--font-button-line-height);
border-radius: var(--radius-m);
padding: var(--spacing-s) var(--spacing-l);
}
```
### Mapping AMPECO mobile tokens
The mobile app sends tokens using its own naming convention (e.g. `buttonsButtonPrimaryBackground`). Use `mapAmpecoTokens()` to convert them to EWA token keys:
```ts
import { mapAmpecoTokens } from '@ampeco/design-tokens';
const mobileTokens = {
buttonsButtonPrimaryBackground: '#FF0000',
textsTextBase: '#333333',
};
const ewaTokens = mapAmpecoTokens(mobileTokens);
// { btnPrimaryBg: '#FF0000', textBase: '#333333' }
```
## API Reference
### Types
- **`AppTheme`** -- `'LIGHT' | 'DARK'`
- **`AmpecoDesignTokens`** -- Partial set of tokens as sent by the AMPECO mobile app via `postMessage`
- **`ThemeTokens`** -- Complete set of EWA theme tokens (buttons, surfaces, text, feedback, input, separator, booking status)
### Constants
- **`TOKEN_CSS_MAP`** -- `Record<keyof ThemeTokens, string>` mapping each token key to its CSS custom property name (e.g. `btnPrimaryBg` -> `'--btn-primary-bg'`)
- **`lightTheme`** -- `ThemeTokens` with light mode defaults
- **`darkTheme`** -- `ThemeTokens` with dark mode defaults
- **`AMPECO_TO_EWA_MAP`** -- `Record<string, keyof ThemeTokens>` mapping AMPECO mobile token names to EWA token keys
### Functions
- **`applyTheme(theme: AppTheme, designTokens?: AmpecoDesignTokens): void`** -- Selects the base theme, merges AMPECO overrides, sets `data-theme` attribute on `<html>`, and writes all tokens as CSS custom properties on `<html>`.
- **`mapAmpecoTokens(tokens: AmpecoDesignTokens): Partial<ThemeTokens>`** -- Converts AMPECO mobile token names to EWA token keys. Only truthy values are included.
## CSS Variable Categories
The `css/variables.css` file defines variables beyond the themeable color tokens:
| Category | Example variables | Notes |
|---|---|---|
| Spacing | `--spacing-xxxs` (2px) through `--spacing-xxxl` (64px) | 9-step scale |
| Border radius | `--radius-s` (8px) through `--radius-xl` (32px) | 4-step scale |
| Typography | `--font-h1-size`, `--font-body-medium-weight`, etc. | 12 type styles |
| Component sizes | `--size-xs` (14px) through `--size-l` (44px) | 4-step scale |
## Build
```bash
npm run build # one-shot build via tsup (ESM + CJS + .d.ts)
npm run dev # watch mode
```
Output lands in `dist/` (ESM as `index.js`, CJS as `index.cjs`, types as `index.d.ts` and `index.d.cts`).
## Source Files
```
src/
index.ts Public API barrel export
types.ts AppTheme, AmpecoDesignTokens
tokens.ts ThemeTokens interface, TOKEN_CSS_MAP
light.ts lightTheme defaults
dark.ts darkTheme defaults
tokenMapping.ts AMPECO_TO_EWA_MAP, mapAmpecoTokens()
apply.ts applyTheme()
css/
variables.css Static CSS custom properties (light defaults + layout tokens)
```
+141
View File
@@ -0,0 +1,141 @@
/*
* @ampeco/design-tokens - CSS Custom Properties
*
* These variables are set programmatically by applyTheme() at runtime.
* The :root defaults below match the light theme so the page has
* sensible values even before JavaScript initialises.
*/
:root {
/* ─── Buttons ─────────────────────────────────────────────── */
--btn-primary-bg: #00A573;
--btn-primary-text: #FFFFFF;
--btn-primary-pressed: #D7E4DC;
--btn-secondary-bg: #FFFFFF;
--btn-secondary-border: #00A573;
--btn-secondary-text: #000000;
/* ─── Surfaces ────────────────────────────────────────────── */
--surface-bg: #FFFFFF;
--surface-elevation-1: #EEF2EF;
--surface-elevation-2: #D7E4DC;
/* ─── Text ────────────────────────────────────────────────── */
--text-base: #000000;
--text-support: #808080;
--text-placeholder: #808080;
/* ─── Feedback ────────────────────────────────────────────── */
--feedback-success: #24A629;
--feedback-success-subtle: #BDE4BF;
--feedback-error: #FF6868;
--feedback-error-subtle: #FFD2D2;
--feedback-warning: #FFB23E;
--feedback-warning-subtle: #FFE8C5;
--feedback-info: #2F83FF;
--feedback-info-subtle: #C1DAFF;
/* ─── Input ───────────────────────────────────────────────── */
--input-fill: #EEF2EF;
--input-border: #D7E4DC;
--input-border-active: #C8DAD7;
--input-text: #000000;
--input-placeholder: #808080;
/* ─── Separator ───────────────────────────────────────────── */
--separator: #C8DAD7;
/* ─── Booking Status ──────────────────────────────────────── */
--status-accepted: #2F83FF;
--status-reserved: #24A629;
--status-completed: #808080;
--status-cancelled: #FFB23E;
--status-no-show: #FF6868;
--status-failed: #B71C1C;
/* ─── Spacing Scale ───────────────────────────────────────── */
--spacing-xxxs: 2px;
--spacing-xxs: 4px;
--spacing-xs: 6px;
--spacing-s: 8px;
--spacing-m: 12px;
--spacing-l: 16px;
--spacing-xl: 24px;
--spacing-xxl: 32px;
--spacing-xxxl: 64px;
/* ─── Border Radius ───────────────────────────────────────── */
--radius-s: 8px;
--radius-m: 12px;
--radius-l: 16px;
--radius-xl: 32px;
/* ─── Typography ──────────────────────────────────────────── */
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
/* H1 - Semibold 22/26 */
--font-h1-size: 22px;
--font-h1-line-height: 26px;
--font-h1-weight: 600;
/* H2 - Semibold 18/22 */
--font-h2-size: 18px;
--font-h2-line-height: 22px;
--font-h2-weight: 600;
/* H3 - Medium 16/20 */
--font-h3-size: 16px;
--font-h3-line-height: 20px;
--font-h3-weight: 500;
/* H4 - Medium 14/17 */
--font-h4-size: 14px;
--font-h4-line-height: 17px;
--font-h4-weight: 500;
/* Body Large - Regular 16/20 */
--font-body-large-size: 16px;
--font-body-large-line-height: 20px;
--font-body-large-weight: 400;
/* Body Medium - Regular 14/17 */
--font-body-medium-size: 14px;
--font-body-medium-line-height: 17px;
--font-body-medium-weight: 400;
/* Body Large Heavy - Semibold 16/20 */
--font-body-large-heavy-size: 16px;
--font-body-large-heavy-line-height: 20px;
--font-body-large-heavy-weight: 600;
/* Body Medium Heavy - Semibold 14/17 */
--font-body-medium-heavy-size: 14px;
--font-body-medium-heavy-line-height: 17px;
--font-body-medium-heavy-weight: 600;
/* Buttons - Semibold 14/20 */
--font-button-size: 14px;
--font-button-line-height: 20px;
--font-button-weight: 600;
/* Buttons Small - Bold 12/17 */
--font-button-small-size: 12px;
--font-button-small-line-height: 17px;
--font-button-small-weight: 700;
/* Label - Regular 12/15 */
--font-label-size: 12px;
--font-label-line-height: 15px;
--font-label-weight: 400;
/* Caption - Medium 12/15 */
--font-caption-size: 12px;
--font-caption-line-height: 15px;
--font-caption-weight: 500;
/* ─── Component Sizes ─────────────────────────────────────── */
--size-xs: 14px;
--size-s: 20px;
--size-m: 32px;
--size-l: 44px;
}
+228
View File
@@ -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
```
+1475
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "@ampeco/design-tokens",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./css": "./css/variables.css",
"./css/*": "./css/*"
},
"files": [
"dist",
"css"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.4.0"
}
}
+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;
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src",
"lib": ["ES2020", "DOM"]
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
sourcemap: true,
});