@vectramindnpm/unified-design-system 0.2.5 → 0.2.6

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
@@ -1,258 +1,258 @@
1
- # unified-design-system
2
-
3
- Standalone, publishable UI component library extracted from Firstpass-Neo's
4
- `src/designSystem` (design tokens) and `src/components/ui` (29 components), with the
5
- app-specific couplings removed so it can be installed as a plain npm dependency in any
6
- React app — and, later, browsed in its own Storybook.
7
-
8
- ## What's here
9
-
10
- ```
11
- src/
12
- tokens/ ← designTokens.js → semanticTokens.js → componentTokens.js (+ index.js barrel)
13
- lib/utils.ts ← cn() (clsx + tailwind-merge)
14
- types/view-mode.ts ← local EViewMode enum (stand-in for the app's models/utilitiesModel)
15
- components/ui/ ← all 29 components — implementation only (.tsx/.ts/.css), no stories
16
- stories/ ← one *.stories.tsx per component, kept separate from components/ui/
17
- styles/
18
- tokens.css ← design-token CSS custom properties (from the app's variables.css)
19
- shadcn-base.css ← the shadcn-style base HSL vars a few components use
20
- theme.css ← Tailwind v4 `@theme inline` bridge (auto-generated, see below)
21
- components.css ← custom classNames the components render beyond tokens/Tailwind
22
- icon-font.css ← the `fp-neo` icon font (icon-neo-*) — see below
23
- fonts.css ← Inter variable-font @font-face
24
- fonts/ ← the actual font files (fp-neo.*, Inter .woff2)
25
- index.css ← styles.css entry: imports all of the above
26
- index.ts ← the package's public barrel export
27
- tailwind-tokens.cjs ← flat Tailwind v3 color/radius/spacing map (from the app's tailwind.tokens.cjs)
28
- tailwind-preset.js ← Tailwind v3 preset for consumers still on Tailwind v3
29
- ```
30
-
31
- The Rsbuild app in the rest of this repo (`src/App.tsx`, `.storybook/`) is a local dev
32
- harness / smoke test for the same `src/` — it is **not** what gets published.
33
-
34
- ## What changed vs. the original app code
35
-
36
- Per [docs/design-system-npm-storybook-guide.md](../../../GITHUB/Firstpass-Neo/docs/design-system-npm-storybook-guide.md)'s
37
- Phase 1 decoupling, plus a few couplings that guide's scan didn't catch:
38
-
39
- - **`semanticTokens`/`designTokens`/`componentTokens`** — every `ui/*` file that imported
40
- these through `../../helpers/helpersIndex` now imports directly from `../../tokens`.
41
- - **i18n removed entirely** (per explicit request, not just made optional) — `icon.tsx`,
42
- `buttonIcon.tsx`, `delete-action.tsx` no longer import `react-i18next` or
43
- `getTranslationKey`. Tooltip/aria labels fall back to a **static string** (the icon
44
- `name`, or a hardcoded label like `"Delete"`/`"Refresh"`) instead of a translation
45
- lookup. If you need i18n later, wrap the exported components in your own thin
46
- wrapper that passes a translated `tooltipText` prop — every component that shows a
47
- label already accepts one.
48
- - **`view-toggle.tsx`** — dropped `getSessionStateKey`/`getViewType` (app's
49
- `internaljscontrols.ts`) and `useSelector` (Redux). `ViewToggle` is now a controlled
50
- component (`viewMode` + `onViewModeChange` props); `useViewMode()` is a plain
51
- `useState` + `localStorage` hook with no Redux/app dependency.
52
- - **`truncated-text.tsx`** — same Redux/session coupling removed; takes a `viewMode` prop
53
- instead of reading it from the app's store.
54
- - **`toast.tsx`** — the `Toast` component itself was already prop-driven. Its
55
- `useToast()` hook (which dispatched into the app's own Redux alert store) was
56
- app-specific state wiring, not a reusable UI primitive, so it's not included — drive
57
- `<Toast onClose={...} />` from your own state instead.
58
- - **`loader.tsx`** — dropped the `useSelector` read of the app's `Layout.loading` Redux
59
- state; `Loader` now just uses its own `isLoading` prop. The loading GIF is bundled
60
- into the package (inlined as a data URI at build time) instead of importing from the
61
- app's `src/icons/`.
62
- - **`time-picker.scss` → `time-picker.css`** — the original Sass file was nested under
63
- the app's `#fpneo` Tailwind-important selector (would never match elsewhere) and used
64
- Sass-only nesting. Flattened to plain, framework-agnostic CSS with its `@apply`
65
- utilities inlined as literal styles, so it needs no Sass or Tailwind build step at all.
66
- - **`models/utilitiesModel.EViewMode`** → copied locally as `types/view-mode.ts` (it was
67
- a two-value enum with no other app dependency).
68
- - A couple of components (`dropdown-menu.tsx`, `popover.tsx`, `tooltip.tsx`,
69
- `expandable-search.tsx`, `table.tsx`) needed small, behavior-preserving TypeScript
70
- fixes (a stricter `useRef`/`RefObject` generic, a `type`-only import,
71
- `JSX.Element` → `React.JSX.Element`) to type-check as an isolated package — same
72
- runtime behavior, just satisfies a standalone `tsup` build.
73
-
74
- **Untouched:** all component markup, class names, and visual variants — this was a
75
- decoupling pass, not a redesign.
76
-
77
- ### Component-level styles (beyond tokens)
78
-
79
- Several components render classNames that are neither Tailwind utilities nor covered by
80
- `tokens.css`/`shadcn-base.css` — plain custom classes like `.app-card`, `.switch-pill`,
81
- `.view-toggle__btn`, `.dropdownHead`, `.item-table__head`, `.fp-loader-overlay`, and the
82
- `icon-neo-*` icon font every Icon/ButtonIcon/DeleteAction/Toast/TimePicker glyph depends
83
- on. These lived in the source app's `src/styles/common.scss`, `mainstyles.css`, and
84
- `font-icons.css` — SCSS nested under that app's `#fpneo` Tailwind-important wrapper, with
85
- `@apply` directives, so they'd neither match nor build here as-is.
86
-
87
- - **`src/styles/components.css`** — every one of those classes, flattened to plain CSS
88
- (`@apply` resolved to literal declarations, `#fpneo`/SCSS nesting stripped). Page- and
89
- business-specific rules were left out on purpose: Table's per-page `*-cols` grid-column
90
- layouts (`.flows-cols`, `.appt-cols`, ...) are data-shape-specific, not reusable design
91
- system material — bring your own to match your own columns. (Two of `table.tsx`'s own
92
- variant keys, `users` → `.users-cols` and `devices` → `.devices-cols`, turned out to
93
- have no corresponding CSS anywhere in the source app either — likely dead/mismatched
94
- keys there already, not something introduced here.)
95
- - **`src/styles/icon-font.css`** + **`src/styles/fonts.css`** — the `fp-neo` icon font
96
- (`icon-neo-*`) and Inter variable font `@font-face` rules, copied verbatim (already
97
- plain CSS) with only their `url()` paths adjusted to this package's own
98
- `src/styles/fonts/` copy of the four `fp-neo.*` files and the Inter `.woff2`.
99
- - All of the above are wired into `src/styles/index.css` (and so into `styles.css`),
100
- alongside `tokens.css`/`shadcn-base.css`/`theme.css` — importing that one file is
101
- enough. Each is also independently `exports`-mapped (`./components.css`,
102
- `./icon-font.css`, `./fonts.css`) if you'd rather cherry-pick.
103
-
104
- Verified via `npm run build-storybook` and `npm run build` (the demo app): Rsbuild/Rspack's
105
- asset pipeline picks up and correctly hashes all five font files from the `url()`
106
- references in this CSS — proof the paths resolve. (No headless browser was available in
107
- this environment to capture an actual rendered screenshot; that's the one thing not
108
- independently confirmed visually.)
109
-
110
- ### Button variant redesign
111
-
112
- `Button` originally had **19 `variant` values** conflating four unrelated things: color/
113
- emphasis, shape (pill vs. rounded), fixed footer sizing, and one-off page-specific skins.
114
- Redesigned into independent, composable axes instead:
115
-
116
- ```tsx
117
- variant: "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" // 6 semantic colors
118
- | "borderPrimary" | "deptedit" | "submenu" // page-context presets
119
- | "cancel" | "save" | "close"
120
- | "formButtonSubmit" | "formButtonCancel" | "formButtonBack"
121
- size: "default" | "sm" | "lg" | "icon" | "iconLg" // unchanged
122
- shape?: "default" | "pill" // NEW
123
- selected?: boolean // unchanged
124
- ```
125
-
126
- Originally shipped (v0.1.x) with only the 6 semantic colors — the 9 "preset" variants below
127
- were deliberately left out, on the theory that fixed page-context styling (specific sizing +
128
- a one-off color skin) belonged in a consumer's own `className`, not the shared component.
129
- That held up fine in isolation, but once a real consumer (Firstpass-Neo) migrated ~90 call
130
- sites, the same `className` string ended up duplicated across dozens of files with no single
131
- place to update it. Re-added in v0.2.0 as named variants for exactly that reason — each is a
132
- self-contained preset (see `PRESET_BASE`/`PRESET_OVERRIDE` in `button.tsx`) that doesn't
133
- interact with `shape`/`selected` at all, so it's safe to combine with them if you ever need to
134
- (you generally won't - just use the variant alone).
135
-
136
- Mapping from the original 19 to the current API:
137
-
138
- | Old `variant` | Current equivalent | Note |
139
- |---|---|---|
140
- | `default` (purple) **and** `primary` (blue) | `variant="primary"` | Consolidated to one color — the token-driven `interactive-brand` (purple), not the hardcoded blue. The two hex values are ~identical anyway (`#5D62B5` vs `#5c62bc`), so this is not a visible change. |
141
- | `secondary`, `destructive`, `outline`, `ghost` | same name, unchanged | No visual change. |
142
- | `lang` | `variant="ghost"` | One-off page skin, not worth a dedicated preset for its single call site — the only difference from `ghost` was a missing hover effect, not worth preserving. |
143
- | `pill` | `shape="pill"` (with any `variant`) | Combine, e.g. `variant="outline" shape="pill"`. |
144
- | `borderPrimary`, `deptedit`, `submenu`, `cancel`, `save`, `close`, `formButtonSubmit`, `formButtonCancel`, `formButtonBack` | same name, now a real `variant` again | Just `variant="cancel"` etc. — no `shape`/`className` needed, each preset is fully self-contained. `deptedit` no longer includes the legacy `dept-edit-btn` className-sniffing hack (pass the variant explicitly). |
145
- | `refreshIcon` | **removed** — use `<ButtonIcon variant="refresh" />` | That's already `ButtonIcon`'s job; `Button` doesn't need to duplicate it. |
146
- | `selectedIcon` | **removed** — use `<ButtonIcon variant="filled" />` or `<Button variant="primary" size="icon">` | Same reasoning. |
147
-
148
- `link` is genuinely new (no old equivalent) — a text-only, underline-on-hover variant with
149
- no padding/height, the one common pattern the original 19 never actually had.
150
-
151
- ## Using it in this repo right now
152
-
153
- ```tsx
154
- import { Button, Badge, Card } from './index'; // see src/App.tsx for a working example
155
- ```
156
-
157
- ## Building the package
158
-
159
- ```bash
160
- npm run build:lib # tsup → dist/{index.cjs,index.mjs,index.d.ts} + dist/*.css
161
- ```
162
-
163
- This has been verified end-to-end: `npm run build:lib` succeeds, `npm pack` produces a
164
- clean ~390 KB tarball, and installing that tarball into a scratch project and
165
- `require()`-ing it resolves all exports correctly (with `react`, `react-dom`, and
166
- `formik` installed as peers).
167
-
168
- ## Installing it in another app
169
-
170
- ```bash
171
- npm install /path/to/unified-design-system-0.1.0.tgz # or from your registry once published
172
- ```
173
-
174
- ```tsx
175
- import { Button, Icon, Table } from 'unified-design-system';
176
- ```
177
-
178
- Peer dependencies you must have installed: `react`, `react-dom`, and `formik` (4 of the
179
- 29 components — `FormField`, `Input` with `as="field"`, `Textarea` with `as="field"`,
180
- `ScrollToFieldError` — call real Formik APIs at runtime, so it's a required peer, not
181
- optional).
182
-
183
- **Styling** — pick one based on your Tailwind version:
184
-
185
- - **Tailwind v4**: `@import "unified-design-system/styles.css";` in your global CSS,
186
- alongside your own `@import "tailwindcss";`. This one import already includes the icon
187
- font, component classes, and tokens.
188
- - **Tailwind v3**: add `presets: [require("unified-design-system/tailwind-preset")]` to
189
- your `tailwind.config.js`, and `@import "unified-design-system/tokens.css";` +
190
- `@import "unified-design-system/shadcn-base.css";` + `@import "unified-design-system/components.css";`
191
- + `@import "unified-design-system/icon-font.css";` + `@import "unified-design-system/fonts.css";`
192
- in your global CSS.
193
- - **No Tailwind**: same four `@import`s as above (skip `tokens.css`/`shadcn-base.css`
194
- only if you don't need the CSS-variable-driven components) — most components read CSS
195
- custom properties (`var(--color-...)`) rather than Tailwind utility classes.
196
-
197
- Any icon (`<Icon name="icon-neo-trash" />`, etc.) needs `icon-font.css` loaded — without
198
- it the glyph classes resolve to nothing and icons render blank.
199
-
200
- ## Publishing (once you're ready)
201
-
202
- 1. `npm run build:lib`
203
- 2. `npm version patch|minor|major`
204
- 3. `npm publish` (add `--registry` for a private/internal registry)
205
- 4. Tag the release in git.
206
-
207
- This package has no consumers yet, so no versioning discipline is enforced — once
208
- something depends on it, breaking changes need a major bump.
209
-
210
- ## Storybook
211
-
212
- Set up via the official installer (`npm create storybook@latest`, framework
213
- auto-detected as `react-rsbuild`), which added `@storybook/addon-a11y` and
214
- `@storybook/addon-docs` to `.storybook/main.ts` and to `devDependencies`.
215
-
216
- The generic Storybook onboarding tutorial (the original `src/stories/` — its demo
217
- Button/Header/Page components, their `.css`, `Configure.mdx`, and the screenshot/icon
218
- images under `assets/`) was removed entirely; it wasn't part of this design system. That
219
- same `src/stories/` path has since been repurposed to hold this design system's own
220
- stories instead (see below) — a fresh, unrelated folder with the same name.
221
-
222
- **Every one of the 29 components has its own `*.stories.tsx` in `src/stories/`** — kept
223
- in a separate folder from `src/components/ui/` (which holds only `.tsx`/`.ts`/`.css`
224
- implementation files, no stories), and importing from there
225
- (`import { Button } from "../components/ui/button"`). `.storybook/main.ts`'s
226
- `../src/**/*.stories.tsx` glob picks them up regardless of where under `src/` they live,
227
- so this split needed no config change. Each story has a `Default` plus variant/state
228
- stories and, for the compound components (Dialog, DropdownMenu, Popover, Tooltip, Table,
229
- AppList, TimePicker, Toast, ViewToggle, ...), a composed working example; components with
230
- a real `cva` variant/size enum (Button, Badge, Icon, ButtonIcon, Checkbox, RadioButton,
231
- Tab) also get an `AllVariants`/`AllColors`/`AllSizes`-style story enumerating every value
232
- from their actual source, cross-checked against it rather than sampled. Every meta also
233
- carries `tags: ["autodocs"]`, a one-line `parameters.docs.description.component`
234
- explaining what the component is/isn't (e.g. Icon vs. ButtonIcon, Popover/Tooltip/DropdownMenu
235
- being hand-built rather than Radix-based), and a `layout` parameter (`"centered"` for
236
- atomic controls, `"padded"` for larger/compound ones) so each renders sensibly on its own
237
- Docs page.
238
-
239
- One correctness fix worth knowing about: `.storybook/preview.tsx` originally imported
240
- `src/styles/index.css` directly, which never triggers Tailwind itself — same as
241
- `src/App.css` needing its own `@import 'tailwindcss';` alongside the design-system
242
- styles. Without it, Storybook's build silently produced zero Tailwind-generated utility
243
- classes (`bg-interactive-brand`, `rounded-md`, `inline-flex`, ...), so every
244
- utility-class-driven style rendered as if unstyled even though the underlying CSS
245
- variables were present. Fixed via `.storybook/preview.css` (`@import 'tailwindcss';`
246
- + the design-system styles) — verified by diffing Storybook's compiled CSS against the
247
- demo app's byte-for-byte, not just a clean build.
248
-
249
- Verified working: `npm run build-storybook` produces a static build with exactly 29
250
- autodocs pages (one per component) alongside 106 individual story variants — confirmed by
251
- inspecting the build's `index.json`, not just a successful exit code. `npm run storybook`
252
- boots and serves at `http://localhost:6006/`. `npm run lint` reports zero errors across
253
- the story files.
254
-
255
- ```bash
256
- npm run storybook # dev server
257
- npm run build-storybook # static build, e.g. for Chromatic/GitHub Pages
258
- ```
1
+ # unified-design-system
2
+
3
+ Standalone, publishable UI component library extracted from Firstpass-Neo's
4
+ `src/designSystem` (design tokens) and `src/components/ui` (29 components), with the
5
+ app-specific couplings removed so it can be installed as a plain npm dependency in any
6
+ React app — and, later, browsed in its own Storybook.
7
+
8
+ ## What's here
9
+
10
+ ```
11
+ src/
12
+ tokens/ ← designTokens.js → semanticTokens.js → componentTokens.js (+ index.js barrel)
13
+ lib/utils.ts ← cn() (clsx + tailwind-merge)
14
+ types/view-mode.ts ← local EViewMode enum (stand-in for the app's models/utilitiesModel)
15
+ components/ui/ ← all 29 components — implementation only (.tsx/.ts/.css), no stories
16
+ stories/ ← one *.stories.tsx per component, kept separate from components/ui/
17
+ styles/
18
+ tokens.css ← design-token CSS custom properties (from the app's variables.css)
19
+ shadcn-base.css ← the shadcn-style base HSL vars a few components use
20
+ theme.css ← Tailwind v4 `@theme inline` bridge (auto-generated, see below)
21
+ components.css ← custom classNames the components render beyond tokens/Tailwind
22
+ icon-font.css ← the `fp-neo` icon font (icon-neo-*) — see below
23
+ fonts.css ← Inter variable-font @font-face
24
+ fonts/ ← the actual font files (fp-neo.*, Inter .woff2)
25
+ index.css ← styles.css entry: imports all of the above
26
+ index.ts ← the package's public barrel export
27
+ tailwind-tokens.cjs ← flat Tailwind v3 color/radius/spacing map (from the app's tailwind.tokens.cjs)
28
+ tailwind-preset.js ← Tailwind v3 preset for consumers still on Tailwind v3
29
+ ```
30
+
31
+ The Rsbuild app in the rest of this repo (`src/App.tsx`, `.storybook/`) is a local dev
32
+ harness / smoke test for the same `src/` — it is **not** what gets published.
33
+
34
+ ## What changed vs. the original app code
35
+
36
+ Per [docs/design-system-npm-storybook-guide.md](../../../GITHUB/Firstpass-Neo/docs/design-system-npm-storybook-guide.md)'s
37
+ Phase 1 decoupling, plus a few couplings that guide's scan didn't catch:
38
+
39
+ - **`semanticTokens`/`designTokens`/`componentTokens`** — every `ui/*` file that imported
40
+ these through `../../helpers/helpersIndex` now imports directly from `../../tokens`.
41
+ - **i18n removed entirely** (per explicit request, not just made optional) — `icon.tsx`,
42
+ `buttonIcon.tsx`, `delete-action.tsx` no longer import `react-i18next` or
43
+ `getTranslationKey`. Tooltip/aria labels fall back to a **static string** (the icon
44
+ `name`, or a hardcoded label like `"Delete"`/`"Refresh"`) instead of a translation
45
+ lookup. If you need i18n later, wrap the exported components in your own thin
46
+ wrapper that passes a translated `tooltipText` prop — every component that shows a
47
+ label already accepts one.
48
+ - **`view-toggle.tsx`** — dropped `getSessionStateKey`/`getViewType` (app's
49
+ `internaljscontrols.ts`) and `useSelector` (Redux). `ViewToggle` is now a controlled
50
+ component (`viewMode` + `onViewModeChange` props); `useViewMode()` is a plain
51
+ `useState` + `localStorage` hook with no Redux/app dependency.
52
+ - **`truncated-text.tsx`** — same Redux/session coupling removed; takes a `viewMode` prop
53
+ instead of reading it from the app's store.
54
+ - **`toast.tsx`** — the `Toast` component itself was already prop-driven. Its
55
+ `useToast()` hook (which dispatched into the app's own Redux alert store) was
56
+ app-specific state wiring, not a reusable UI primitive, so it's not included — drive
57
+ `<Toast onClose={...} />` from your own state instead.
58
+ - **`loader.tsx`** — dropped the `useSelector` read of the app's `Layout.loading` Redux
59
+ state; `Loader` now just uses its own `isLoading` prop. The loading GIF is bundled
60
+ into the package (inlined as a data URI at build time) instead of importing from the
61
+ app's `src/icons/`.
62
+ - **`time-picker.scss` → `time-picker.css`** — the original Sass file was nested under
63
+ the app's `#fpneo` Tailwind-important selector (would never match elsewhere) and used
64
+ Sass-only nesting. Flattened to plain, framework-agnostic CSS with its `@apply`
65
+ utilities inlined as literal styles, so it needs no Sass or Tailwind build step at all.
66
+ - **`models/utilitiesModel.EViewMode`** → copied locally as `types/view-mode.ts` (it was
67
+ a two-value enum with no other app dependency).
68
+ - A couple of components (`dropdown-menu.tsx`, `popover.tsx`, `tooltip.tsx`,
69
+ `expandable-search.tsx`, `table.tsx`) needed small, behavior-preserving TypeScript
70
+ fixes (a stricter `useRef`/`RefObject` generic, a `type`-only import,
71
+ `JSX.Element` → `React.JSX.Element`) to type-check as an isolated package — same
72
+ runtime behavior, just satisfies a standalone `tsup` build.
73
+
74
+ **Untouched:** all component markup, class names, and visual variants — this was a
75
+ decoupling pass, not a redesign.
76
+
77
+ ### Component-level styles (beyond tokens)
78
+
79
+ Several components render classNames that are neither Tailwind utilities nor covered by
80
+ `tokens.css`/`shadcn-base.css` — plain custom classes like `.app-card`, `.switch-pill`,
81
+ `.view-toggle__btn`, `.dropdownHead`, `.item-table__head`, `.fp-loader-overlay`, and the
82
+ `icon-neo-*` icon font every Icon/ButtonIcon/DeleteAction/Toast/TimePicker glyph depends
83
+ on. These lived in the source app's `src/styles/common.scss`, `mainstyles.css`, and
84
+ `font-icons.css` — SCSS nested under that app's `#fpneo` Tailwind-important wrapper, with
85
+ `@apply` directives, so they'd neither match nor build here as-is.
86
+
87
+ - **`src/styles/components.css`** — every one of those classes, flattened to plain CSS
88
+ (`@apply` resolved to literal declarations, `#fpneo`/SCSS nesting stripped). Page- and
89
+ business-specific rules were left out on purpose: Table's per-page `*-cols` grid-column
90
+ layouts (`.flows-cols`, `.appt-cols`, ...) are data-shape-specific, not reusable design
91
+ system material — bring your own to match your own columns. (Two of `table.tsx`'s own
92
+ variant keys, `users` → `.users-cols` and `devices` → `.devices-cols`, turned out to
93
+ have no corresponding CSS anywhere in the source app either — likely dead/mismatched
94
+ keys there already, not something introduced here.)
95
+ - **`src/styles/icon-font.css`** + **`src/styles/fonts.css`** — the `fp-neo` icon font
96
+ (`icon-neo-*`) and Inter variable font `@font-face` rules, copied verbatim (already
97
+ plain CSS) with only their `url()` paths adjusted to this package's own
98
+ `src/styles/fonts/` copy of the four `fp-neo.*` files and the Inter `.woff2`.
99
+ - All of the above are wired into `src/styles/index.css` (and so into `styles.css`),
100
+ alongside `tokens.css`/`shadcn-base.css`/`theme.css` — importing that one file is
101
+ enough. Each is also independently `exports`-mapped (`./components.css`,
102
+ `./icon-font.css`, `./fonts.css`) if you'd rather cherry-pick.
103
+
104
+ Verified via `npm run build-storybook` and `npm run build` (the demo app): Rsbuild/Rspack's
105
+ asset pipeline picks up and correctly hashes all five font files from the `url()`
106
+ references in this CSS — proof the paths resolve. (No headless browser was available in
107
+ this environment to capture an actual rendered screenshot; that's the one thing not
108
+ independently confirmed visually.)
109
+
110
+ ### Button variant redesign
111
+
112
+ `Button` originally had **19 `variant` values** conflating four unrelated things: color/
113
+ emphasis, shape (pill vs. rounded), fixed footer sizing, and one-off page-specific skins.
114
+ Redesigned into independent, composable axes instead:
115
+
116
+ ```tsx
117
+ variant: "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" // 6 semantic colors
118
+ | "borderPrimary" | "deptedit" | "submenu" // page-context presets
119
+ | "cancel" | "save" | "close"
120
+ | "formButtonSubmit" | "formButtonCancel" | "formButtonBack"
121
+ size: "default" | "sm" | "lg" | "icon" | "iconLg" // unchanged
122
+ shape?: "default" | "pill" // NEW
123
+ selected?: boolean // unchanged
124
+ ```
125
+
126
+ Originally shipped (v0.1.x) with only the 6 semantic colors — the 9 "preset" variants below
127
+ were deliberately left out, on the theory that fixed page-context styling (specific sizing +
128
+ a one-off color skin) belonged in a consumer's own `className`, not the shared component.
129
+ That held up fine in isolation, but once a real consumer (Firstpass-Neo) migrated ~90 call
130
+ sites, the same `className` string ended up duplicated across dozens of files with no single
131
+ place to update it. Re-added in v0.2.0 as named variants for exactly that reason — each is a
132
+ self-contained preset (see `PRESET_BASE`/`PRESET_OVERRIDE` in `button.tsx`) that doesn't
133
+ interact with `shape`/`selected` at all, so it's safe to combine with them if you ever need to
134
+ (you generally won't - just use the variant alone).
135
+
136
+ Mapping from the original 19 to the current API:
137
+
138
+ | Old `variant` | Current equivalent | Note |
139
+ |---|---|---|
140
+ | `default` (purple) **and** `primary` (blue) | `variant="primary"` | Consolidated to one color — the token-driven `interactive-brand` (purple), not the hardcoded blue. The two hex values are ~identical anyway (`#5D62B5` vs `#5c62bc`), so this is not a visible change. |
141
+ | `secondary`, `destructive`, `outline`, `ghost` | same name, unchanged | No visual change. |
142
+ | `lang` | `variant="ghost"` | One-off page skin, not worth a dedicated preset for its single call site — the only difference from `ghost` was a missing hover effect, not worth preserving. |
143
+ | `pill` | `shape="pill"` (with any `variant`) | Combine, e.g. `variant="outline" shape="pill"`. |
144
+ | `borderPrimary`, `deptedit`, `submenu`, `cancel`, `save`, `close`, `formButtonSubmit`, `formButtonCancel`, `formButtonBack` | same name, now a real `variant` again | Just `variant="cancel"` etc. — no `shape`/`className` needed, each preset is fully self-contained. `deptedit` no longer includes the legacy `dept-edit-btn` className-sniffing hack (pass the variant explicitly). |
145
+ | `refreshIcon` | **removed** — use `<ButtonIcon variant="refresh" />` | That's already `ButtonIcon`'s job; `Button` doesn't need to duplicate it. |
146
+ | `selectedIcon` | **removed** — use `<ButtonIcon variant="filled" />` or `<Button variant="primary" size="icon">` | Same reasoning. |
147
+
148
+ `link` is genuinely new (no old equivalent) — a text-only, underline-on-hover variant with
149
+ no padding/height, the one common pattern the original 19 never actually had.
150
+
151
+ ## Using it in this repo right now
152
+
153
+ ```tsx
154
+ import { Button, Badge, Card } from './index'; // see src/App.tsx for a working example
155
+ ```
156
+
157
+ ## Building the package
158
+
159
+ ```bash
160
+ npm run build:lib # tsup → dist/{index.cjs,index.mjs,index.d.ts} + dist/*.css
161
+ ```
162
+
163
+ This has been verified end-to-end: `npm run build:lib` succeeds, `npm pack` produces a
164
+ clean ~390 KB tarball, and installing that tarball into a scratch project and
165
+ `require()`-ing it resolves all exports correctly (with `react`, `react-dom`, and
166
+ `formik` installed as peers).
167
+
168
+ ## Installing it in another app
169
+
170
+ ```bash
171
+ npm install /path/to/unified-design-system-0.1.0.tgz # or from your registry once published
172
+ ```
173
+
174
+ ```tsx
175
+ import { Button, Icon, Table } from 'unified-design-system';
176
+ ```
177
+
178
+ Peer dependencies you must have installed: `react`, `react-dom`, and `formik` (4 of the
179
+ 29 components — `FormField`, `Input` with `as="field"`, `Textarea` with `as="field"`,
180
+ `ScrollToFieldError` — call real Formik APIs at runtime, so it's a required peer, not
181
+ optional).
182
+
183
+ **Styling** — pick one based on your Tailwind version:
184
+
185
+ - **Tailwind v4**: `@import "unified-design-system/styles.css";` in your global CSS,
186
+ alongside your own `@import "tailwindcss";`. This one import already includes the icon
187
+ font, component classes, and tokens.
188
+ - **Tailwind v3**: add `presets: [require("unified-design-system/tailwind-preset")]` to
189
+ your `tailwind.config.js`, and `@import "unified-design-system/tokens.css";` +
190
+ `@import "unified-design-system/shadcn-base.css";` + `@import "unified-design-system/components.css";`
191
+ + `@import "unified-design-system/icon-font.css";` + `@import "unified-design-system/fonts.css";`
192
+ in your global CSS.
193
+ - **No Tailwind**: same four `@import`s as above (skip `tokens.css`/`shadcn-base.css`
194
+ only if you don't need the CSS-variable-driven components) — most components read CSS
195
+ custom properties (`var(--color-...)`) rather than Tailwind utility classes.
196
+
197
+ Any icon (`<Icon name="icon-neo-trash" />`, etc.) needs `icon-font.css` loaded — without
198
+ it the glyph classes resolve to nothing and icons render blank.
199
+
200
+ ## Publishing (once you're ready)
201
+
202
+ 1. `npm run build:lib`
203
+ 2. `npm version patch|minor|major`
204
+ 3. `npm publish` (add `--registry` for a private/internal registry)
205
+ 4. Tag the release in git.
206
+
207
+ This package has no consumers yet, so no versioning discipline is enforced — once
208
+ something depends on it, breaking changes need a major bump.
209
+
210
+ ## Storybook
211
+
212
+ Set up via the official installer (`npm create storybook@latest`, framework
213
+ auto-detected as `react-rsbuild`), which added `@storybook/addon-a11y` and
214
+ `@storybook/addon-docs` to `.storybook/main.ts` and to `devDependencies`.
215
+
216
+ The generic Storybook onboarding tutorial (the original `src/stories/` — its demo
217
+ Button/Header/Page components, their `.css`, `Configure.mdx`, and the screenshot/icon
218
+ images under `assets/`) was removed entirely; it wasn't part of this design system. That
219
+ same `src/stories/` path has since been repurposed to hold this design system's own
220
+ stories instead (see below) — a fresh, unrelated folder with the same name.
221
+
222
+ **Every one of the 29 components has its own `*.stories.tsx` in `src/stories/`** — kept
223
+ in a separate folder from `src/components/ui/` (which holds only `.tsx`/`.ts`/`.css`
224
+ implementation files, no stories), and importing from there
225
+ (`import { Button } from "../components/ui/button"`). `.storybook/main.ts`'s
226
+ `../src/**/*.stories.tsx` glob picks them up regardless of where under `src/` they live,
227
+ so this split needed no config change. Each story has a `Default` plus variant/state
228
+ stories and, for the compound components (Dialog, DropdownMenu, Popover, Tooltip, Table,
229
+ AppList, TimePicker, Toast, ViewToggle, ...), a composed working example; components with
230
+ a real `cva` variant/size enum (Button, Badge, Icon, ButtonIcon, Checkbox, RadioButton,
231
+ Tab) also get an `AllVariants`/`AllColors`/`AllSizes`-style story enumerating every value
232
+ from their actual source, cross-checked against it rather than sampled. Every meta also
233
+ carries `tags: ["autodocs"]`, a one-line `parameters.docs.description.component`
234
+ explaining what the component is/isn't (e.g. Icon vs. ButtonIcon, Popover/Tooltip/DropdownMenu
235
+ being hand-built rather than Radix-based), and a `layout` parameter (`"centered"` for
236
+ atomic controls, `"padded"` for larger/compound ones) so each renders sensibly on its own
237
+ Docs page.
238
+
239
+ One correctness fix worth knowing about: `.storybook/preview.tsx` originally imported
240
+ `src/styles/index.css` directly, which never triggers Tailwind itself — same as
241
+ `src/App.css` needing its own `@import 'tailwindcss';` alongside the design-system
242
+ styles. Without it, Storybook's build silently produced zero Tailwind-generated utility
243
+ classes (`bg-interactive-brand`, `rounded-md`, `inline-flex`, ...), so every
244
+ utility-class-driven style rendered as if unstyled even though the underlying CSS
245
+ variables were present. Fixed via `.storybook/preview.css` (`@import 'tailwindcss';`
246
+ + the design-system styles) — verified by diffing Storybook's compiled CSS against the
247
+ demo app's byte-for-byte, not just a clean build.
248
+
249
+ Verified working: `npm run build-storybook` produces a static build with exactly 29
250
+ autodocs pages (one per component) alongside 106 individual story variants — confirmed by
251
+ inspecting the build's `index.json`, not just a successful exit code. `npm run storybook`
252
+ boots and serves at `http://localhost:6006/`. `npm run lint` reports zero errors across
253
+ the story files.
254
+
255
+ ```bash
256
+ npm run storybook # dev server
257
+ npm run build-storybook # static build, e.g. for Chromatic/GitHub Pages
258
+ ```