@cupped/tokens 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @cupped/tokens
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release. DTCG token source under `tokens/` with generated, committed
6
+ targets in `dist/`:
7
+
8
+ - React Native theme object (`@cupped/tokens`) — literal-typed, unitless dp.
9
+ - CSS custom properties (`@cupped/tokens/css`) with alias `var()` chains.
10
+ - Web component classes (`@cupped/tokens/css/components`).
11
+ - Tailwind v4 `@theme` (`@cupped/tokens/tailwind`).
12
+ - Flat and DTCG JSON (`@cupped/tokens/json`, `@cupped/tokens/json/dtcg`).
13
+
14
+ Published to GitHub Packages (`@cupped` scope); also installable as a git
15
+ dependency. Subsequent releases are managed with Changesets — see
16
+ [docs/releasing.md](./docs/releasing.md) and
17
+ [docs/versioning.md](./docs/versioning.md).
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @cupped/tokens
2
+
3
+ Framework-agnostic design tokens for the **Cupped** design system — a
4
+ community-first coffee-logging platform. The canonical source is
5
+ platform-neutral [DTCG](https://www.designtokens.org/TR/drafts/format/)-style
6
+ JSON under [`tokens/`](./tokens); every consumable artifact in
7
+ [`dist/`](./dist) is **generated** from it and committed. The package is
8
+ published to **GitHub Packages** (`@cupped` scope), and because `dist/` is
9
+ committed it also works as a plain git dependency — no registry auth, no
10
+ install-time build — for consumers that prefer that.
11
+
12
+ > Never hard-code a hex or pixel value that exists as a token.
13
+
14
+ ## Exports
15
+
16
+ | Import | What you get |
17
+ |---|---|
18
+ | `@cupped/tokens` | React Native theme object (`tokens`), literal-typed, unitless dp numbers, dual-shape shadows, Reanimated spring params |
19
+ | `@cupped/tokens/css` | CSS custom properties (`--ink`, `--space-base`…), alias `var()` chains preserved, `prefers-reduced-motion` / `prefers-reduced-transparency` blocks built in |
20
+ | `@cupped/tokens/css/components` | Canonical web component classes (`.btn`, `.input`, `.chip-flavor`, `.banner`, `.chrome`…) — optional reference styles |
21
+ | `@cupped/tokens/tailwind` | Tailwind v4 `@theme` file → utilities (`bg-canvas`, `text-ink-secondary`, `rounded-lg`, `shadow-primary`, `p-base`, `text-card-title`…) |
22
+ | `@cupped/tokens/json` | Flat resolved map keyed by CSS names |
23
+ | `@cupped/tokens/json/dtcg` | The merged DTCG source tree |
24
+
25
+ No components, no icons, no font binaries — components stay app-specific;
26
+ fonts are self-hosted per platform (documented).
27
+
28
+ ## Consuming
29
+
30
+ - **Phoenix LiveView (Tailwind v4):** [docs/phoenix.md](./docs/phoenix.md) —
31
+ pinned CSS from the GitHub Release, npm git-dep, or zero-npm vendor mode;
32
+ fonts, lint recipe.
33
+ - **Expo / React Native (SDK 53+):** [docs/expo.md](./docs/expo.md) — the
34
+ `theme.ts` pattern, expo-font, ESLint recipe.
35
+ - **Brand & a11y invariants:** [docs/rules.md](./docs/rules.md).
36
+
37
+ Complete copy-paste examples live in [`examples/`](./examples).
38
+
39
+ ## Architecture
40
+
41
+ ```
42
+ tokens/primitive/*.tokens.json raw palettes & scales (never consumed directly)
43
+ tokens/semantic/*.tokens.json the public API — every token aliases a primitive
44
+
45
+ ▼ Style Dictionary v5 + custom formats (build/)
46
+ dist/css/tokens.css var() chains preserved (--xp: var(--sweet))
47
+ dist/tailwind/theme.css @theme namespaces, fully resolved
48
+ dist/native/index.{js,cjs,d.ts} resolved literals, as-const types
49
+ dist/json/tokens.{flat,dtcg}.json
50
+ ```
51
+
52
+ Aliases use DTCG `{dot.path}` references; the build fails on unresolved or
53
+ circular references. Accessibility requirements are encoded as data
54
+ (`$extensions["app.cupped"].contrast`) and machine-checked in CI, alongside
55
+ name-collision checks and a parity test against the frozen
56
+ [`source-export/`](./source-export) (the refreshed design-system export this
57
+ package mirrors — kept as reference, never edited).
58
+
59
+ ## Developing
60
+
61
+ Building requires **Node 22+** (the build uses `node:fs/promises` `glob`).
62
+ Consuming the published package has no Node version requirement.
63
+
64
+ ```bash
65
+ npm install
66
+ npm run build # tokens/ → dist/ (dist is committed)
67
+ npm test # parity + contrast + collisions + snapshots + type-checks
68
+ npm run check # build, fail on dist/ drift, then test — what CI runs
69
+ ```
70
+
71
+ ## Releasing & versioning
72
+
73
+ Releases are automated with [Changesets](https://github.com/changesets/changesets)
74
+ and published to GitHub Packages; each release also attaches version-pinned CSS
75
+ to its GitHub Release. Add a changeset with `npx changeset` in any PR that
76
+ changes tokens.
77
+
78
+ - **Release mechanics & first-publish runbook:** [docs/releasing.md](./docs/releasing.md).
79
+ - **What's a major/minor/patch (the token contract):** [docs/versioning.md](./docs/versioning.md).
@@ -0,0 +1,183 @@
1
+ /* ════════════════════════════════════════════════════════════════
2
+ CUPPED COMPONENTS · v0.4
3
+ Canonical component classes. Load AFTER tokens.css:
4
+ <link rel="stylesheet" href="tokens.css">
5
+ <link rel="stylesheet" href="components.css">
6
+
7
+ These are the real design-system components — a prototype's button
8
+ IS this button, not a lookalike. Colors, type, spacing and elevation
9
+ reference tokens (var(--…)); a few structural primitives (hairline
10
+ borders, icon box sizes) stay as raw px where no token exists.
11
+
12
+ Hand-maintained web reference (src/css/components.css in
13
+ @cupped/tokens); shipped via the "/css/components" export.
14
+ ════════════════════════════════════════════════════════════════ */
15
+
16
+ /* ——— Buttons ——— */
17
+ .btn {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ gap: var(--space-sm);
22
+ font-family: var(--ff-sans);
23
+ font-size: 15px;
24
+ font-weight: 600;
25
+ letter-spacing: -0.005em;
26
+ border-radius: var(--radius-md);
27
+ border: none;
28
+ cursor: pointer;
29
+ padding: 12px 20px;
30
+ min-height: var(--hit-target-min);
31
+ transition: transform .12s, background .15s, box-shadow .2s;
32
+ }
33
+ .btn:active { transform: scale(0.95); opacity: 0.8; }
34
+ .btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
35
+ .btn[disabled] { background: var(--canvas-border-subtle); color: var(--ink-muted); box-shadow: none; cursor: not-allowed; transform: none; }
36
+ .btn-primary {
37
+ background: var(--primary-strong);
38
+ color: var(--ink-inverse);
39
+ box-shadow: var(--shadow-primary);
40
+ }
41
+ .btn-primary:hover { background: var(--primary-strong-hover); }
42
+ .btn-secondary {
43
+ background: var(--card);
44
+ color: var(--ink);
45
+ border: 1px solid var(--canvas-border);
46
+ }
47
+ .btn-secondary:hover { border-color: var(--ink); }
48
+ .btn-ghost {
49
+ background: transparent;
50
+ color: var(--primary-strong);
51
+ }
52
+ .btn-ghost:hover { background: var(--primary-light); }
53
+ .btn-full { width: 100%; }
54
+
55
+ .btn-stack { display: flex; flex-direction: column; gap: var(--space-sm); align-items: flex-start; }
56
+ .btn-stack .btn { align-self: stretch; }
57
+ .btn-row { display: flex; gap: var(--space-sm); flex-wrap: wrap; }
58
+
59
+ /* ——— Flavor chips (light · text on white) ———
60
+ Set --flavor to a flavor TEXT-band token, e.g.
61
+ style="--flavor: var(--floral-accessible)" */
62
+ .chip-flavor {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ gap: 6px;
66
+ padding: 6px 12px;
67
+ border-radius: var(--radius-full);
68
+ font-size: 13px;
69
+ font-weight: 500;
70
+ background: color-mix(in srgb, var(--flavor) 10%, white);
71
+ color: var(--flavor);
72
+ border: 1px solid color-mix(in srgb, var(--flavor) 25%, white);
73
+ }
74
+ .chip-flavor .dot {
75
+ width: 8px; height: 8px;
76
+ border-radius: 50%;
77
+ background: var(--flavor);
78
+ }
79
+ .chip-stack { display: flex; flex-wrap: wrap; gap: 6px; }
80
+
81
+ /* ——— Flavor tags (solid · on photos/cards) ———
82
+ Set background to a flavor FILL token; the Espresso band
83
+ guarantees white text passes AA for every flavor. */
84
+ .tag-flavor {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ font-size: 12px;
88
+ font-weight: 500;
89
+ padding: 4px 8px;
90
+ border-radius: var(--radius-full);
91
+ color: #fff;
92
+ }
93
+
94
+ /* ——— Inputs ——— */
95
+ .input {
96
+ width: 100%;
97
+ padding: 12px 14px;
98
+ border: 1px solid var(--canvas-border);
99
+ border-radius: var(--radius-md);
100
+ background: var(--card);
101
+ font-family: var(--ff-sans);
102
+ font-size: 15px;
103
+ color: var(--ink);
104
+ transition: border-color .15s, box-shadow .15s;
105
+ }
106
+ .input::placeholder { color: var(--ink-muted); }
107
+ .input:focus {
108
+ outline: none;
109
+ border-color: var(--primary);
110
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
111
+ }
112
+ .input.error {
113
+ border-color: var(--error);
114
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 12%, transparent);
115
+ }
116
+ .input[disabled] { background: var(--canvas); color: var(--ink-muted); cursor: not-allowed; }
117
+ .input-row { display: flex; flex-direction: column; gap: 6px; }
118
+ .input-label { font-size: 12px; font-weight: 500; color: var(--ink-secondary); }
119
+ .input-hint { font-size: 12px; color: var(--ink-secondary); }
120
+ .input-hint.error { color: var(--error-ink); }
121
+
122
+ /* ——— Cards ——— */
123
+ .card {
124
+ background: var(--card);
125
+ border: 1px solid var(--canvas-border);
126
+ border-radius: var(--radius-lg);
127
+ box-shadow: var(--shadow-sm);
128
+ }
129
+ .card-pad { padding: var(--space-lg); }
130
+
131
+ /* ——— Rating stars ——— */
132
+ .rating { display: inline-flex; gap: 4px; color: var(--xp); }
133
+ .rating svg { width: 20px; height: 20px; }
134
+ .rating .empty { color: var(--canvas-border); }
135
+
136
+ /* ——— Feedback banners ———
137
+ The -ink variant tints the icon (≥3:1); the fill tints the border;
138
+ words stay ink / ink-secondary. */
139
+ .banner {
140
+ display: flex;
141
+ align-items: flex-start;
142
+ gap: var(--space-md);
143
+ padding: var(--space-md) var(--space-base);
144
+ border-radius: var(--radius-md);
145
+ border: 1px solid;
146
+ margin-bottom: var(--space-sm);
147
+ }
148
+ .banner .icon { width: 20px; height: 20px; flex-shrink: 0; margin-top: 1px; }
149
+ .banner .body { font-size: 14px; }
150
+ .banner .title { font-weight: 600; margin-bottom: 2px; }
151
+ .banner.success { background: var(--success-light); border-color: color-mix(in srgb, var(--success) 25%, transparent); color: var(--success-ink); }
152
+ .banner.error { background: var(--error-light); border-color: color-mix(in srgb, var(--error) 25%, transparent); color: var(--error-ink); }
153
+ .banner.warning { background: var(--warning-light); border-color: color-mix(in srgb, var(--warning) 25%, transparent); color: var(--warning-ink); }
154
+ .banner.info { background: var(--info-light); border-color: color-mix(in srgb, var(--info) 25%, transparent); color: var(--info-ink); }
155
+ .banner .body .title { color: var(--ink); }
156
+ .banner .body p { margin: 0; color: var(--ink-secondary); font-size: 13px; }
157
+
158
+ /* ——— Chrome (floating bar/header material) ———
159
+ The sole sanctioned translucent surface — tab bar and sticky screen
160
+ headers only, one chrome layer per screen. Content surfaces are
161
+ always opaque. prefers-reduced-transparency collapses this to an
162
+ opaque card via the material tokens. */
163
+ .chrome {
164
+ background: var(--material-chrome-bg);
165
+ -webkit-backdrop-filter: var(--material-chrome-blur);
166
+ backdrop-filter: var(--material-chrome-blur);
167
+ }
168
+
169
+ /* ——— Spinner (button loading state) ——— */
170
+ .spinner {
171
+ width: 14px; height: 14px;
172
+ border: 2px solid rgba(255,255,255,0.35);
173
+ border-top-color: #fff;
174
+ border-radius: 50%;
175
+ animation: cupped-spin 0.8s linear infinite;
176
+ flex-shrink: 0;
177
+ }
178
+ @keyframes cupped-spin { to { transform: rotate(360deg); } }
179
+
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .btn { transition: none; }
182
+ .spinner { animation: none; }
183
+ }
@@ -0,0 +1,144 @@
1
+ /* ════════════════════════════════════════════════════════════════
2
+ CUPPED · DESIGN TOKENS
3
+ GENERATED FILE — do not edit. Canonical source: the DTCG token files
4
+ under tokens/ (npm run build in @cupped/tokens).
5
+
6
+ · Components reference semantic tokens, never raw hexes.
7
+ · --primary (#E07A5F) is ~3.0:1 on white. It NEVER carries text
8
+ below 24px. Small colored text + text-bearing fills use
9
+ --primary-strong (4.6:1, WCAG AA).
10
+ · Aliases stay live var() chains — change the source, every
11
+ alias follows.
12
+ ════════════════════════════════════════════════════════════════ */
13
+
14
+ :root {
15
+ /* ── Color ── */
16
+ --canvas: #F8FAFC;
17
+ --card: #FFFFFF;
18
+ --canvas-border: #E2E8F0;
19
+ --canvas-border-subtle: #F1F5F9;
20
+ --ink: #0F172A;
21
+ --ink-secondary: #475569;
22
+ --ink-muted: #94A3B8;
23
+ --ink-inverse: #F8FAFC;
24
+ --primary: #E07A5F;
25
+ --primary-strong: #C05539;
26
+ --primary-strong-hover: #AD4A30;
27
+ --primary-hover: #D16A4F;
28
+ --primary-light: #FDF2F0;
29
+ --primary-muted: rgba(224, 122, 95, 0.12);
30
+ --success: #22C55E;
31
+ --success-light: #F0FDF4;
32
+ --success-ink: #15803D;
33
+ --error: #EF4444;
34
+ --error-light: #FEF2F2;
35
+ --error-ink: #B91C1C;
36
+ --warning: #F59E0B;
37
+ --warning-light: #FFFBEB;
38
+ --warning-ink: #B45309;
39
+ --info: #3B82F6;
40
+ --info-light: #EFF6FF;
41
+ --info-ink: #1D4ED8;
42
+ --fruity: #B44C54;
43
+ --floral: #865CB2;
44
+ --nutty: #876F49;
45
+ --chocolate: #916951;
46
+ --spice: #B4503A;
47
+ --sweet: #8E6E09;
48
+ --citrus: #A65F02;
49
+ --green: #4E8429;
50
+ --berry: #AC4D7B;
51
+ --roasted: #896B5E;
52
+ --fruity-accessible: #9B494E;
53
+ --berry-accessible: #94496D;
54
+ --citrus-accessible: #925512;
55
+ --sweet-accessible: #7E6207;
56
+ --floral-accessible: #755498;
57
+ --green-accessible: #49742E;
58
+ --spice-accessible: #9B4C3A;
59
+ --nutty-accessible: #79623E;
60
+ --chocolate-accessible: #825C46;
61
+ --roasted-accessible: #7B5F52;
62
+ --on-flavor: #FFFFFF;
63
+ --xp: var(--sweet);
64
+ --xp-accessible: var(--sweet-accessible);
65
+ --ember: #F97316;
66
+ --ember-accessible: #C2410C;
67
+ --streak: var(--ember);
68
+ --streak-accessible: var(--ember-accessible);
69
+ --badge: var(--floral);
70
+ --badge-accessible: var(--floral-accessible);
71
+ --zone-ideal: var(--success);
72
+ --zone-under: var(--info);
73
+ --zone-over: var(--error);
74
+ --zone-strong: var(--warning);
75
+ --zone-weak: var(--ink-muted);
76
+ --chart-sca: var(--primary);
77
+ --chart-ucdavis: var(--floral);
78
+ --chart-grid: var(--canvas-border);
79
+
80
+ /* ── Typography ── */
81
+ --ff-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
82
+ --ff-serif: 'Instrument Serif', Georgia, serif;
83
+ --ff-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
84
+ --text-screen-title: 32px;
85
+ --text-sheet-title: 24px;
86
+ --text-card-title: 20px;
87
+ --text-body: 16px;
88
+ --text-body-sm: 14px;
89
+ --text-caption: 12px;
90
+
91
+ /* ── Spacing ── */
92
+ --space-xs: 4px;
93
+ --space-sm: 8px;
94
+ --space-md: 12px;
95
+ --space-base: 16px;
96
+ --space-lg: 20px;
97
+ --space-xl: 24px;
98
+ --space-xxl: 32px;
99
+
100
+ /* ── Radii ── */
101
+ --radius-sm: 8px;
102
+ --radius-md: 12px;
103
+ --radius-lg: 16px;
104
+ --radius-xl: 24px;
105
+ --radius-full: 9999px;
106
+
107
+ /* ── Elevation ── */
108
+ --shadow-sm: 0 1px 2px rgba(15,23,42,0.04), 0 1px 1px rgba(15,23,42,0.03);
109
+ --shadow-md: 0 4px 12px rgba(15,23,42,0.06), 0 1px 3px rgba(15,23,42,0.04);
110
+ --shadow-lg: 0 12px 32px rgba(15,23,42,0.08), 0 4px 8px rgba(15,23,42,0.04);
111
+ --shadow-primary: 0 12px 28px rgba(224,122,95,0.28), 0 4px 10px rgba(224,122,95,0.18);
112
+
113
+ /* ── Motion & interaction ── */
114
+ --spring-response: 0.55s;
115
+ --spring-damping: 0.75;
116
+ --tap-scale: 0.95;
117
+ --tap-opacity: 0.8;
118
+ --stagger-delay: 0.08s;
119
+ --shimmer-duration: 1.5s;
120
+ --motion-reduced: 120ms;
121
+ --focus-ring: 0 0 0 2px var(--card), 0 0 0 4px var(--primary-strong);
122
+ --hit-target-min: 44px;
123
+
124
+ /* ── Materials ── */
125
+ --material-chrome-bg: color-mix(in srgb, var(--card) 95%, transparent);
126
+ --material-chrome-blur: saturate(1.4) blur(20px);
127
+ --scrim: rgba(15, 23, 42, 0.45);
128
+ }
129
+
130
+ /* Motion respects user preference: keep the state change, drop travel. */
131
+ @media (prefers-reduced-motion: reduce) {
132
+ :root {
133
+ --spring-response: var(--motion-reduced);
134
+ --stagger-delay: 0s;
135
+ }
136
+ }
137
+
138
+ /* Transparency respects user preference: chrome collapses to opaque card. */
139
+ @media (prefers-reduced-transparency: reduce) {
140
+ :root {
141
+ --material-chrome-bg: var(--card);
142
+ --material-chrome-blur: none;
143
+ }
144
+ }