@carbonid1/design-system 5.0.2 → 5.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/README.md CHANGED
@@ -66,13 +66,16 @@ export default function RootLayout({ children }: { children: React.ReactNode })
66
66
 
67
67
  | Component | Use for |
68
68
  | --- | --- |
69
+ | `Badge` | Compact status/label chip with variants |
69
70
  | `Button` | Standard button primitive with variants (`ghost`, `primary`, `outline`, `destructive`, `attention`, `subtle`, `danger`, `link`) |
70
71
  | `Kbd` | Keyboard key display (`⌘K`, `⇧B`) |
71
72
  | `ProgressRing` | Circular progress indicator |
72
73
  | `Slider` | Range slider |
74
+ | `ContextMenu` | Right-click / long-press menu (compound API) |
73
75
  | `Select` | Custom dropdown — never use native `<select>` |
74
76
  | `Tooltip` | Hover/focus tooltip (wraps a single child) |
75
77
  | `Toaster` | Toast notification root (renders once in layout) |
78
+ | `toast` | Imperative toast trigger (re-export from `sonner`) |
76
79
  | `ThemeProvider` | Wraps `next-themes` — provides dark/light mode |
77
80
  | `ThemeCycler` | `shift+t` hotkey cycles `system → light → dark` with toast |
78
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbonid1/design-system",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
4
  "description": "Shared React UI primitives + design tokens (themes, postcss config)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: design-system
3
- description: 'Shared UI components and design patterns for carbonid1 apps built on @carbonid1/design-system. ALWAYS use when building UI, creating components, adding dropdowns, selects, tooltips, icons, toasts, context menus, or any interactive element. Use when choosing between native HTML elements and custom components. Also use when working with skeletons, loading states, layout shifts, or async-loaded UI elements. Triggers on: dropdown, select, tooltip, tag, badge, icon, component, UI, design system, shared component, skeleton, loading state, layout shift, context menu, right-click menu, button, theme, palette.'
3
+ description: 'Shared UI components and design patterns for carbonid1 apps built on @carbonid1/design-system. ALWAYS use when building UI, creating components, adding dropdowns, selects, tooltips, icons, toasts, context menus, badges, or any interactive element. Use when choosing between native HTML elements and custom components. Also covers visual conventions for layout stability when wiring async UI (skeletons are consumer-owned, but the rules for them live here). Triggers on: dropdown, select, tooltip, tag, badge, icon, component, UI, design system, shared component, layout shift, context menu, right-click menu, button, theme, palette.'
4
4
  ---
5
5
 
6
6
  # Design System
@@ -9,7 +9,7 @@ Primitives live in `@carbonid1/design-system`. **Check the package's exports and
9
9
 
10
10
  ```ts
11
11
  import {
12
- Button, Kbd, ProgressRing, Slider,
12
+ Badge, Button, Kbd, ProgressRing, Slider,
13
13
  ContextMenu, Select, Tooltip,
14
14
  Toaster, toast,
15
15
  ThemeProvider, ThemeCycler, useTheme,
@@ -39,6 +39,23 @@ If you're editing `@carbonid1/design-system` itself (adding a primitive, changin
39
39
  - **Touch targets** — icon buttons `p-1.5` minimum, icons `w-4 h-4` minimum
40
40
  - **Avatars** — only with real images, no letter/initial placeholders
41
41
 
42
+ ## Elevation
43
+
44
+ Four-tier contract — every themed surface picks one of these tokens. Layering is recessed: cards are sheets on the desk, controls are carved INTO sheets, popovers lift OFF the desk.
45
+
46
+ | Token | Role | Pair with |
47
+ | ------------------ | ---------------------------------------- | ---------------------- |
48
+ | `bg-background` | Page (the desk) | — |
49
+ | `bg-card` | Raised paper on the desk — peak surface | `shadow-card` |
50
+ | `bg-surface-inset` | Inset block carved into a card | `inset-shadow-surface` |
51
+ | `bg-popover` | Floating layer above everything | `shadow-popover` |
52
+
53
+ `bg-surface-inset` covers form inputs, preview boxes, quote blocks, and language tabs. `bg-popover` covers drawers, menus, and command palettes.
54
+
55
+ **`bg-muted`, `bg-accent`, `bg-secondary` are RESERVED for interaction states** — hover backgrounds, skeleton placeholders, badge fills, soft tints. They are never the background of a card or an inset block. Mixing them in breaks the layering contract; see [references/theming.md](references/theming.md) for the deeper rationale.
56
+
57
+ **Empty placeholders** — when a slot is meant to be filled (`AddBookCard`, file drop zone, audio preview slot before recording), render a transparent or background-matched surface with `border-dashed`. The empty state reads as a missing piece on its parent surface — never as a recessed input or a card. Once filled, swap to a solid border and the surface adopts its real elevation.
58
+
42
59
  ## Destructive Actions
43
60
 
44
61
  Two tiers. Pick by how hard the action is to redo, not how scary it feels.
@@ -31,20 +31,33 @@ Tokens are defined in `globals.css` (`:root` + `.dark`), bridged in `@theme inli
31
31
 
32
32
  ### Base tokens (from shadcn naming)
33
33
 
34
- | Token | Role | Sub-variants |
35
- | --------------- | --------------------------------------------------------------- | ------------ |
36
- | `--background` | Page background | — |
37
- | `--foreground` | Default text | — |
38
- | `--card` | Card surfaces | foreground |
39
- | `--popover` | Popover surfaces | foreground |
40
- | `--primary` | Interactive color (buttons, links, selections, focus, progress) | foreground |
41
- | `--secondary` | Supporting surfaces | foreground |
42
- | `--muted` | De-emphasized surfaces and text | foreground |
43
- | `--accent` | Hover/expanded tints | foreground |
44
- | `--destructive` | Errors, danger | |
45
- | `--border` | Default borders | — |
46
- | `--input` | Input borders | — |
47
- | `--ring` | Focus rings | — |
34
+ | Token | Role | Sub-variants |
35
+ | ------------------ | --------------------------------------------------------------- | ------------ |
36
+ | `--background` | Page background (the desk) | — |
37
+ | `--foreground` | Default text | — |
38
+ | `--card` | Raised card surfaces | foreground |
39
+ | `--popover` | Floating popover/drawer/menu surfaces | foreground |
40
+ | `--surface-inset` | Inset blocks carved into a card (inputs, preview boxes) | |
41
+ | `--primary` | Interactive color (buttons, links, selections, focus, progress) | foreground |
42
+ | `--secondary` | Supporting surfaces | foreground |
43
+ | `--muted` | De-emphasized surfaces and text | foreground |
44
+ | `--accent` | Hover/expanded tints | foreground |
45
+ | `--destructive` | Errors, danger | — |
46
+ | `--border` | Default borders | — |
47
+ | `--input` | Input borders | — |
48
+ | `--ring` | Focus rings | — |
49
+
50
+ ### Elevation shadows
51
+
52
+ Each elevated surface pairs with a tuned shadow. Tailwind utilities: `shadow-card`, `inset-shadow-surface`, `shadow-popover`.
53
+
54
+ | Token | Pairs with | Notes |
55
+ | ------------------------ | ------------------ | ---------------------------------------------------- |
56
+ | `--card-shadow` | `bg-card` | Subtle drop, neutral or hue-tinted per theme |
57
+ | `--surface-inset-shadow` | `bg-surface-inset` | Hairline inset top in light, `none` in dark |
58
+ | `--popover-shadow` | `bg-popover` | Stronger drop with larger geometry in dark |
59
+
60
+ Why dark mode skips the inset shadow: top-edge highlights fake a "light source from above" convention that reads as a sharp border artifact on dark surfaces, not as a soft recess. Tonal contrast (a wider inset-vs-card lightness delta) carries the elevation instead.
48
61
 
49
62
  ### Custom tokens
50
63
 
@@ -84,6 +97,32 @@ Tokens are defined in `globals.css` (`:root` + `.dark`), bridged in `@theme inli
84
97
  <span className="bg-my-token/10"> // opacity modifier for tints
85
98
  ```
86
99
 
100
+ ## Surfaces & Elevation
101
+
102
+ Four-tier contract that every themed surface picks from. Layering is *recessed* (cards are sheets on the desk; inputs are carved INTO sheets; popovers lift OFF the desk) — the inverse of Material's monotonic stack.
103
+
104
+ ```
105
+ bg-background page (the desk)
106
+ bg-card raised paper on the desk → shadow-card
107
+ bg-surface-inset inset INTO a card → inset-shadow-surface (light only)
108
+ bg-popover floats above everything → shadow-popover
109
+ ```
110
+
111
+ **`bg-muted`, `bg-accent`, `bg-secondary` are RESERVED for interaction states** — hover backgrounds, skeleton placeholders, badge fills, soft tints. They are NEVER the background of a card or an inset block. The four tokens above are the only source of truth for surface elevation; mixing the interaction-state tokens in breaks the layering contract.
112
+
113
+ ### Empty-slot convention
114
+
115
+ When a region is meant to be filled (`AddBookCard`, audio drop zone, preview slot before recording), render it as a *background-matched or transparent* surface with `border-dashed` and a foreground border token (`border-border` or similar). The slot reads as a missing piece on its parent surface — never as a recessed input or a card. Once filled, the dashed border switches to solid and the surface adopts its real elevation.
116
+
117
+ ```tsx
118
+ // Empty slot on a card surface
119
+ <div className="border-border bg-card rounded-lg border-2 border-dashed p-4">
120
+ <PlusIcon /> Add a book
121
+ </div>
122
+ ```
123
+
124
+ Don't use `bg-surface-inset` for empty placeholders — that signals "a control lives here", not "a slot is missing".
125
+
87
126
  ## Notes
88
127
 
89
128
  - **`::selection`** uses solid oklch values, not opacity modifiers — semi-transparent backgrounds create visible seams at line boundaries.
@@ -3,5 +3,5 @@
3
3
  import { Toaster as Sonner, type ToasterProps } from 'sonner'
4
4
 
5
5
  export const Toaster = (props: ToasterProps) => (
6
- <Sonner theme="system" className="toaster group" position="bottom-right" {...props} />
6
+ <Sonner theme="system" position="bottom-right" {...props} />
7
7
  )
@@ -1,9 +1,14 @@
1
1
  /* @carbonid1/tailwind-config/dashboard
2
2
  *
3
- * Dashboard theme: semantic color tokens for data-dense UIs (calculators,
4
- * admin panels, production-chain visualizers). Currently identical tokens
5
- * to `reader` distinct palette pending visual design work.
6
- * Pulls in tailwindcss itself so consumers only need this single import.
3
+ * Dashboard theme for data-dense UIs (CoI Calculator, admin panels,
4
+ * production-chain visualizers). Visual register: neutral-cool surfaces
5
+ * with subtle elevation (high-information UIs benefit from low-noise
6
+ * chrome that lets data take focus).
7
+ *
8
+ * Same elevation contract as reader.css — see that file for the full table.
9
+ * Surfaces here are neutral (chroma 0 in light, ~hue 260 in dark) instead
10
+ * of warm paper, but the layering rules (background < card; surface-inset
11
+ * recessed into card; popover floats with shadow) are identical.
7
12
  *
8
13
  * Consumer responsibilities:
9
14
  * - Optionally import tw-animate-css for animate utilities.
@@ -25,6 +30,10 @@
25
30
  --color-card-foreground: var(--card-foreground);
26
31
  --color-popover: var(--popover);
27
32
  --color-popover-foreground: var(--popover-foreground);
33
+ --color-surface-inset: var(--surface-inset);
34
+ --shadow-card: var(--card-shadow);
35
+ --shadow-popover: var(--popover-shadow);
36
+ --inset-shadow-surface: var(--surface-inset-shadow);
28
37
  --color-primary: var(--primary);
29
38
  --color-primary-foreground: var(--primary-foreground);
30
39
  --color-primary-muted: var(--primary-muted);
@@ -63,6 +72,11 @@
63
72
  --card-foreground: oklch(0.145 0 0);
64
73
  --popover: oklch(1 0 0);
65
74
  --popover-foreground: oklch(0.145 0 0);
75
+ --surface-inset: oklch(0.965 0 0);
76
+ --card-shadow: 0 1px 2px 0 oklch(0.2 0 0 / 0.06);
77
+ --popover-shadow:
78
+ 0 8px 24px -4px oklch(0.2 0 0 / 0.1), 0 4px 8px -2px oklch(0.2 0 0 / 0.06);
79
+ --surface-inset-shadow: inset 0 1px 1px 0 oklch(0.2 0 0 / 0.04);
66
80
  --primary: oklch(0.546 0.245 262.881);
67
81
  --primary-foreground: oklch(1 0 0);
68
82
  --primary-muted: oklch(0.97 0.014 254);
@@ -96,6 +110,10 @@
96
110
  --card-foreground: oklch(0.967 0.003 265);
97
111
  --popover: oklch(0.278 0.03 257);
98
112
  --popover-foreground: oklch(0.967 0.003 265);
113
+ --surface-inset: oklch(0.225 0.028 261);
114
+ --card-shadow: 0 1px 2px 0 oklch(0 0 0 / 0.2);
115
+ --popover-shadow: 0 12px 32px -4px oklch(0 0 0 / 0.4), 0 4px 8px -2px oklch(0 0 0 / 0.2);
116
+ --surface-inset-shadow: none;
99
117
  --primary: oklch(0.78 0.13 256);
100
118
  --primary-foreground: oklch(1 0 0);
101
119
  --primary-muted: oklch(0.282 0.091 267 / 0.2);
package/themes/reader.css CHANGED
@@ -1,8 +1,33 @@
1
1
  /* @carbonid1/tailwind-config/reader
2
2
  *
3
- * Reader theme: semantic color tokens, dark variant, @theme bridge.
4
- * Includes the keyframes required by the theme's --animate-* tokens.
5
- * Pulls in tailwindcss itself so consumers only need this single import.
3
+ * Reader theme for reading-app consumers (InkVoice, future longform/journal
4
+ * apps). Visual register: warm paper layered on a desk. Light mode is a
5
+ * hue-75 warm-paper palette with tonal layering and recessed inputs; dark
6
+ * mode is a cool ink palette (~hue 260) with the same elevation hierarchy.
7
+ *
8
+ * --- Elevation contract (use this to pick the right surface token) --------
9
+ *
10
+ * Token Role Light Dark
11
+ * ───────────────── ────────────────────────────────────── ───────── ─────────
12
+ * bg-background page (the desk) warm off cool dark
13
+ * bg-card raised paper on the desk brightest peak
14
+ * bg-surface-inset inset block carved INTO a card darker darker than card
15
+ * (form inputs, preview boxes,
16
+ * quote blocks, language tabs)
17
+ * bg-popover floating layer above everything = card = card
18
+ * (drawers, menus, command palette) + shadow + shadow
19
+ *
20
+ * --- Shadow contract (paired with the surface above) ----------------------
21
+ *
22
+ * shadow-card on bg-card subtle drop
23
+ * inset-shadow-surface on bg-surface-inset hairline inset top (light only)
24
+ * shadow-popover on bg-popover strong drop
25
+ *
26
+ * Usage rules (which tokens are surfaces, empty-slot convention, etc.) live
27
+ * in the design-system skill — see SKILL.md / references/theming.md.
28
+ *
29
+ * TODO: dark mode currently mirrors the existing cool palette. A dedicated
30
+ * warm-dark "ink on cream" pass is deferred — separate visual decision.
6
31
  *
7
32
  * Consumer responsibilities:
8
33
  * - Import tw-animate-css and shadcn base layer separately if needed.
@@ -27,6 +52,10 @@
27
52
  --color-card-foreground: var(--card-foreground);
28
53
  --color-popover: var(--popover);
29
54
  --color-popover-foreground: var(--popover-foreground);
55
+ --color-surface-inset: var(--surface-inset);
56
+ --shadow-card: var(--card-shadow);
57
+ --shadow-popover: var(--popover-shadow);
58
+ --inset-shadow-surface: var(--surface-inset-shadow);
30
59
  --color-primary: var(--primary);
31
60
  --color-primary-foreground: var(--primary-foreground);
32
61
  --color-primary-muted: var(--primary-muted);
@@ -59,12 +88,19 @@
59
88
 
60
89
  /* Light mode tokens */
61
90
  :root {
62
- --background: oklch(1 0 0);
91
+ --background: oklch(0.985 0.005 75);
63
92
  --foreground: oklch(0.145 0 0);
64
- --card: oklch(1 0 0);
93
+ --card: oklch(0.995 0.003 75);
65
94
  --card-foreground: oklch(0.145 0 0);
66
- --popover: oklch(1 0 0);
95
+ --popover: oklch(0.995 0.003 75);
67
96
  --popover-foreground: oklch(0.145 0 0);
97
+ --surface-inset: oklch(0.962 0.007 75);
98
+ /* Shadows tinted with the warm hue so they read as ambient occlusion on
99
+ * paper, not pure black smudges. */
100
+ --card-shadow: 0 1px 2px 0 oklch(0.2 0.02 75 / 0.06);
101
+ --popover-shadow:
102
+ 0 8px 24px -4px oklch(0.2 0.02 75 / 0.1), 0 4px 8px -2px oklch(0.2 0.02 75 / 0.06);
103
+ --surface-inset-shadow: inset 0 1px 1px 0 oklch(0.2 0.02 75 / 0.04);
68
104
  --primary: oklch(0.546 0.245 262.881);
69
105
  --primary-foreground: oklch(1 0 0);
70
106
  --primary-muted: oklch(0.97 0.014 254);
@@ -98,6 +134,15 @@
98
134
  --card-foreground: oklch(0.967 0.003 265);
99
135
  --popover: oklch(0.278 0.03 257);
100
136
  --popover-foreground: oklch(0.967 0.003 265);
137
+ /* Inset/card delta is wider than light mode — lightness differences read
138
+ * as weaker in the dark range, so the recess needs more room to land. */
139
+ --surface-inset: oklch(0.225 0.028 261);
140
+ /* Inset shadow disabled: top-edge highlights fake a light-source-from-above
141
+ * convention that reads as a sharp border artifact on dark surfaces rather
142
+ * than a soft recess. The tonal delta above carries the elevation instead. */
143
+ --card-shadow: 0 1px 2px 0 oklch(0 0 0 / 0.2);
144
+ --popover-shadow: 0 12px 32px -4px oklch(0 0 0 / 0.4), 0 4px 8px -2px oklch(0 0 0 / 0.2);
145
+ --surface-inset-shadow: none;
101
146
  --primary: oklch(0.78 0.13 256);
102
147
  --primary-foreground: oklch(1 0 0);
103
148
  --primary-muted: oklch(0.282 0.091 267 / 0.2);