@carbonid1/design-system 4.1.0 → 4.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbonid1/design-system",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Shared React UI primitives + design tokens (themes, postcss config)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,9 @@
22
22
  "files": [
23
23
  "src",
24
24
  "themes",
25
- "postcss.mjs"
25
+ "postcss.mjs",
26
+ "skills",
27
+ "scripts"
26
28
  ],
27
29
  "dependencies": {
28
30
  "@tailwindcss/postcss": "^4.2.0",
@@ -76,6 +78,7 @@
76
78
  "storybook": "storybook dev -p 6006",
77
79
  "build-storybook": "storybook build",
78
80
  "test": "vitest --run",
79
- "test:watch": "vitest"
81
+ "test:watch": "vitest",
82
+ "postinstall": "node scripts/install-skill.mjs"
80
83
  }
81
84
  }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, rm, symlink } from 'node:fs/promises'
3
+ import { dirname, join, resolve } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const PKG_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..')
7
+ const SKILL_SRC = join(PKG_DIR, 'skills', 'design-system')
8
+
9
+ if (!PKG_DIR.includes(`${process.platform === 'win32' ? '\\' : '/'}node_modules${process.platform === 'win32' ? '\\' : '/'}`)) {
10
+ process.exit(0)
11
+ }
12
+
13
+ const consumerRoot = process.env.INIT_CWD || process.cwd()
14
+ const skillDest = join(consumerRoot, '.claude', 'skills', 'design-system')
15
+
16
+ try {
17
+ await mkdir(dirname(skillDest), { recursive: true })
18
+ await rm(skillDest, { recursive: true, force: true })
19
+ await symlink(SKILL_SRC, skillDest, 'dir')
20
+ console.log(`[@carbonid1/design-system] linked skill → ${skillDest}`)
21
+ } catch (err) {
22
+ console.warn(`[@carbonid1/design-system] could not link skill: ${err.message}`)
23
+ }
@@ -0,0 +1,65 @@
1
+ ---
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.'
4
+ ---
5
+
6
+ # Design System
7
+
8
+ Primitives live in `@carbonid1/design-system`. **Check the package's exports and types before building anything new.** This file is direction and taste, not API reference — the types are the docs.
9
+
10
+ ```ts
11
+ import {
12
+ Button, Kbd, ProgressRing, Slider, Switch,
13
+ ContextMenu, Select, Tooltip,
14
+ Toaster, toast,
15
+ ThemeProvider, ThemeCycler, useTheme,
16
+ cn, getModKey,
17
+ } from '@carbonid1/design-system'
18
+ ```
19
+
20
+ Palettes are chosen by importing one theme CSS per app: `@carbonid1/design-system/themes/reader` (InkVoice) or `/themes/dashboard` (CoI Calculator).
21
+
22
+ If you're editing `@carbonid1/design-system` itself (adding a primitive, changing a token), read that package's `CLAUDE.md` — this file is for *using* the design system, not building it.
23
+
24
+ ## Core rules
25
+
26
+ - Never use native `<select>` — use `Select`
27
+ - Never build a tooltip, context menu, or toast from scratch — use `Tooltip`, `ContextMenu`, `toast()`
28
+ - All icons from `lucide-react` — search `lucide.dev/icons` before creating anything custom. If nothing fits, build via Lucide's `Icon` + `iconNode`, don't reach for another library.
29
+ - All color through semantic tokens — no hardcoded Tailwind color classes. See [references/theming.md](references/theming.md).
30
+ - Tooltip: rely on defaults (`position="top"`, `delay={200}`). Only override when the default is wrong (e.g. header → `position="bottom"`, data-viz progressive disclosure → `delay={600}`). When a tooltip trigger also opens a popover, pass `disabled={open}`.
31
+ - Icon toggle state: `<Icon fill={active ? 'currentColor' : 'none'} />` — not two different components.
32
+
33
+ ## Visual Language
34
+
35
+ - **Discrete elements** — rows/cards use `rounded-lg` with `space-y-1` gaps, never flat lists with hairline dividers
36
+ - **Selection** — `bg-primary/10` tint + `ring-1 ring-primary/20`, never border-left accents
37
+ - **Hover reveal** — action buttons use `opacity-0 group-hover:opacity-100 group-focus-within:opacity-100` with outer row `hover:bg-accent`. The `group-focus-within` is required — without it, keyboard users tab to an invisible button.
38
+ - **Metadata** — badge/chip components, never comma-separated text
39
+ - **Touch targets** — icon buttons `p-1.5` minimum, icons `w-4 h-4` minimum
40
+ - **Avatars** — only with real images, no letter/initial placeholders
41
+
42
+ ## Destructive Actions
43
+
44
+ Two tiers. Pick by how hard the action is to redo, not how scary it feels.
45
+
46
+ | Severity | Pattern | Example |
47
+ | ---------------- | ----------------------------------- | ------------------------------------ |
48
+ | **Hard to redo** | Undo toast (5s + Cmd/Ctrl+Z) | Delete a record, remove history item |
49
+ | **Easy to redo** | No confirmation | Toggle flag, remove a tag |
50
+
51
+ **Undo toast:** action executes immediately → toast with description `${getModKey()}+Z to undo` + "Undo" action button (5s auto-dismiss) → store holds `lastDeleted` for restoration → global `mod+z` hotkey via `react-hotkeys-hook` → sequential deletes overwrite the buffer (only latest is undoable). Never add a modal confirmation for hard-to-redo actions — the undo toast is the confirmation.
52
+
53
+ ## Layout Stability
54
+
55
+ Skeleton → real UI transitions must not shift layout. The skeleton is a dimensional contract.
56
+
57
+ - Skeletons match real UI structure, counts, heights, spacing. Update the skeleton when the real UI changes.
58
+ - Async elements that always render (selectors, user controls) show a same-sized placeholder while loading — never `null`.
59
+ - Doesn't apply to user-triggered UI (modals, drawers, toasts) or conditional banners — these are unpredictable by nature.
60
+
61
+ ## Theming
62
+
63
+ `ThemeProvider` wraps the app (toggles `.dark` via `next-themes`). `ThemeCycler` mounts once to register `Shift+T` for cycling system → light → dark. `useTheme` is the next-themes re-export for programmatic access.
64
+
65
+ Both palettes define the same token names on `:root` + `.dark`, so primitive code and consumer code don't care which palette is active.
@@ -0,0 +1,91 @@
1
+ # Color & Theming
2
+
3
+ ## Philosophy
4
+
5
+ Tokens represent semantic roles, not fixed hues. Each theme picks the best hue for that role in its own context — light and dark themes may use entirely different hue families for the same token. The design system is app-agnostic: swap the token values, everything updates.
6
+
7
+ We use shadcn's token naming convention but own all color values. When pulling a new shadcn component, replace any generated color values with our hand-tuned tokens. This follows shadcn's own approach (~99% token-driven) as validation, not as a dependency.
8
+
9
+ ## Palettes
10
+
11
+ `@carbonid1/design-system` ships multiple palettes — currently `reader` (InkVoice) and `dashboard` (CoI Calculator, admin-style UIs). Both define the same token names on `:root` + `.dark`; consumers choose one by importing the matching CSS:
12
+
13
+ ```ts
14
+ import '@carbonid1/design-system/themes/reader'
15
+ // or
16
+ import '@carbonid1/design-system/themes/dashboard'
17
+ ```
18
+
19
+ Primitives must render correctly under every palette × light/dark combination — that's the contract. Because both palettes hit the same token names, primitive code stays palette-agnostic and consumer code doesn't change when switching.
20
+
21
+ ## Rules
22
+
23
+ - **All color through tokens.** Every color that appears in themed UI must reference a CSS variable. No hardcoded Tailwind color classes (`blue-500`, `amber-400`, `gray-300`) for meaning-bearing colors.
24
+ - **Hardcoded only when physically fixed.** The only exception: values that must be constant regardless of theme (e.g. `bg-black/10` for modal backdrops). These are rare — 2-3 cases in the entire app.
25
+ - **Opacity modifiers work natively.** Tailwind v4 supports `bg-token/20` on any color format including oklch CSS variables. No `color-mix()` workarounds needed.
26
+ - **Use enough sub-variants for depth.** Don't squeeze a semantic color into just 2 tokens (base + foreground) when it serves distinct roles — text, background, and border often need different values to look intentional. Use 4-5 sub-variants (e.g. `foreground`, `muted`, `border`) when it gives the area more visual depth. Opacity modifiers on a single base color are a poor substitute for dedicated tokens tuned per role.
27
+
28
+ ## Token Inventory
29
+
30
+ Tokens are defined in `globals.css` (`:root` + `.dark`), bridged in `@theme inline` block, consumed as Tailwind utilities.
31
+
32
+ ### Base tokens (from shadcn naming)
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 | — |
48
+
49
+ ### Custom tokens
50
+
51
+ | Token | Role | Sub-variants |
52
+ | ------------- | --------------------------------------------- | ------------------------- |
53
+ | `--highlight` | Emphasis accent (e.g. live cursors, selection highlights) | foreground, muted |
54
+ | `--attention` | Warnings, notices, saved-for-later — "look at this" | foreground, muted, border |
55
+ | `--success` | Completion, positive outcomes | foreground |
56
+
57
+ ## How to Add a New Semantic Token
58
+
59
+ 1. **Define values** in `globals.css` — both `:root` (light) and `.dark`. Use oklch for perceptual uniformity. Light and dark values may use different hues if the role reads better that way.
60
+
61
+ ```css
62
+ :root {
63
+ --my-token: oklch(0.65 0.15 45);
64
+ --my-token-foreground: oklch(0.25 0.05 40);
65
+ }
66
+ .dark {
67
+ --my-token: oklch(0.7 0.12 200);
68
+ --my-token-foreground: oklch(0.95 0.02 195);
69
+ }
70
+ ```
71
+
72
+ 2. **Bridge in `@theme inline`** in `globals.css` so Tailwind generates utilities:
73
+
74
+ ```css
75
+ @theme inline {
76
+ --color-my-token: var(--my-token);
77
+ --color-my-token-foreground: var(--my-token-foreground);
78
+ }
79
+ ```
80
+
81
+ 3. **Use in components** via Tailwind classes:
82
+ ```tsx
83
+ <div className="bg-my-token text-my-token-foreground">
84
+ <span className="bg-my-token/10"> // opacity modifier for tints
85
+ ```
86
+
87
+ ## Notes
88
+
89
+ - **`::selection`** uses solid oklch values, not opacity modifiers — semi-transparent backgrounds create visible seams at line boundaries.
90
+ - **Tooltip** uses inverted tokens (`bg-foreground text-background`) since it's a dark-on-light / light-on-dark surface. Kbd badge: `bg-background/15`.
91
+ - **DebugPanel** is excluded from token migration — physically fixed terminal aesthetic.