@nqmcreative/ui 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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +507 -0
  3. package/cli/index.mjs +410 -0
  4. package/dist/actions/anchor.d.ts +29 -0
  5. package/dist/actions/anchor.js +122 -0
  6. package/dist/actions/dismissable.d.ts +44 -0
  7. package/dist/actions/dismissable.js +147 -0
  8. package/dist/components/Accordion.svelte +14 -0
  9. package/dist/components/Accordion.svelte.d.ts +8 -0
  10. package/dist/components/AccordionItem.svelte +46 -0
  11. package/dist/components/AccordionItem.svelte.d.ts +14 -0
  12. package/dist/components/Alert.svelte +88 -0
  13. package/dist/components/Alert.svelte.d.ts +18 -0
  14. package/dist/components/Avatar.svelte +58 -0
  15. package/dist/components/Avatar.svelte.d.ts +16 -0
  16. package/dist/components/AvatarGroup.svelte +17 -0
  17. package/dist/components/AvatarGroup.svelte.d.ts +8 -0
  18. package/dist/components/Badge.svelte +53 -0
  19. package/dist/components/Badge.svelte.d.ts +18 -0
  20. package/dist/components/Breadcrumb.svelte +46 -0
  21. package/dist/components/Breadcrumb.svelte.d.ts +14 -0
  22. package/dist/components/Button.svelte +94 -0
  23. package/dist/components/Button.svelte.d.ts +21 -0
  24. package/dist/components/Calendar.svelte +206 -0
  25. package/dist/components/Calendar.svelte.d.ts +22 -0
  26. package/dist/components/Card.svelte +76 -0
  27. package/dist/components/Card.svelte.d.ts +21 -0
  28. package/dist/components/Checkbox.svelte +69 -0
  29. package/dist/components/Checkbox.svelte.d.ts +15 -0
  30. package/dist/components/CheckboxGroup.svelte +109 -0
  31. package/dist/components/CheckboxGroup.svelte.d.ts +30 -0
  32. package/dist/components/Combobox.svelte +268 -0
  33. package/dist/components/Combobox.svelte.d.ts +29 -0
  34. package/dist/components/CommandPalette.svelte +226 -0
  35. package/dist/components/CommandPalette.svelte.d.ts +32 -0
  36. package/dist/components/ConfirmDialog.svelte +97 -0
  37. package/dist/components/ConfirmDialog.svelte.d.ts +22 -0
  38. package/dist/components/ContextMenu.svelte +90 -0
  39. package/dist/components/ContextMenu.svelte.d.ts +15 -0
  40. package/dist/components/DatePicker.svelte +186 -0
  41. package/dist/components/DatePicker.svelte.d.ts +23 -0
  42. package/dist/components/Divider.svelte +30 -0
  43. package/dist/components/Divider.svelte.d.ts +10 -0
  44. package/dist/components/Drawer.svelte +161 -0
  45. package/dist/components/Drawer.svelte.d.ts +20 -0
  46. package/dist/components/Dropdown.svelte +112 -0
  47. package/dist/components/Dropdown.svelte.d.ts +23 -0
  48. package/dist/components/Dropzone.svelte +226 -0
  49. package/dist/components/Dropzone.svelte.d.ts +32 -0
  50. package/dist/components/EmptyState.svelte +57 -0
  51. package/dist/components/EmptyState.svelte.d.ts +18 -0
  52. package/dist/components/Field.svelte +42 -0
  53. package/dist/components/Field.svelte.d.ts +16 -0
  54. package/dist/components/Footer.svelte +93 -0
  55. package/dist/components/Footer.svelte.d.ts +25 -0
  56. package/dist/components/Input.svelte +60 -0
  57. package/dist/components/Input.svelte.d.ts +18 -0
  58. package/dist/components/InputGroup.svelte +19 -0
  59. package/dist/components/InputGroup.svelte.d.ts +10 -0
  60. package/dist/components/Kbd.svelte +17 -0
  61. package/dist/components/Kbd.svelte.d.ts +8 -0
  62. package/dist/components/Label.svelte +16 -0
  63. package/dist/components/Label.svelte.d.ts +9 -0
  64. package/dist/components/Link.svelte +44 -0
  65. package/dist/components/Link.svelte.d.ts +16 -0
  66. package/dist/components/LocaleProvider.svelte +24 -0
  67. package/dist/components/LocaleProvider.svelte.d.ts +10 -0
  68. package/dist/components/MenuItem.svelte +58 -0
  69. package/dist/components/MenuItem.svelte.d.ts +18 -0
  70. package/dist/components/MenuSeparator.svelte +22 -0
  71. package/dist/components/MenuSeparator.svelte.d.ts +8 -0
  72. package/dist/components/Modal.svelte +114 -0
  73. package/dist/components/Modal.svelte.d.ts +21 -0
  74. package/dist/components/MultiSelect.svelte +292 -0
  75. package/dist/components/MultiSelect.svelte.d.ts +31 -0
  76. package/dist/components/Navbar.svelte +190 -0
  77. package/dist/components/Navbar.svelte.d.ts +33 -0
  78. package/dist/components/NumberInput.svelte +100 -0
  79. package/dist/components/NumberInput.svelte.d.ts +15 -0
  80. package/dist/components/Pagination.svelte +90 -0
  81. package/dist/components/Pagination.svelte.d.ts +15 -0
  82. package/dist/components/PasswordInput.svelte +104 -0
  83. package/dist/components/PasswordInput.svelte.d.ts +12 -0
  84. package/dist/components/Popover.svelte +114 -0
  85. package/dist/components/Popover.svelte.d.ts +19 -0
  86. package/dist/components/Progress.svelte +56 -0
  87. package/dist/components/Progress.svelte.d.ts +17 -0
  88. package/dist/components/Radio.svelte +55 -0
  89. package/dist/components/Radio.svelte.d.ts +15 -0
  90. package/dist/components/RadioGroup.svelte +93 -0
  91. package/dist/components/RadioGroup.svelte.d.ts +27 -0
  92. package/dist/components/SegmentedControl.svelte +88 -0
  93. package/dist/components/SegmentedControl.svelte.d.ts +25 -0
  94. package/dist/components/Select.svelte +61 -0
  95. package/dist/components/Select.svelte.d.ts +20 -0
  96. package/dist/components/Sidebar.svelte +206 -0
  97. package/dist/components/Sidebar.svelte.d.ts +36 -0
  98. package/dist/components/Skeleton.svelte +46 -0
  99. package/dist/components/Skeleton.svelte.d.ts +13 -0
  100. package/dist/components/Slider.svelte +79 -0
  101. package/dist/components/Slider.svelte.d.ts +19 -0
  102. package/dist/components/Spinner.svelte +34 -0
  103. package/dist/components/Spinner.svelte.d.ts +13 -0
  104. package/dist/components/Stat.svelte +49 -0
  105. package/dist/components/Stat.svelte.d.ts +16 -0
  106. package/dist/components/Steps.svelte +136 -0
  107. package/dist/components/Steps.svelte.d.ts +24 -0
  108. package/dist/components/Switch.svelte +64 -0
  109. package/dist/components/Switch.svelte.d.ts +14 -0
  110. package/dist/components/Table.svelte +236 -0
  111. package/dist/components/Table.svelte.d.ts +70 -0
  112. package/dist/components/Tabs.svelte +91 -0
  113. package/dist/components/Tabs.svelte.d.ts +23 -0
  114. package/dist/components/Textarea.svelte +54 -0
  115. package/dist/components/Textarea.svelte.d.ts +12 -0
  116. package/dist/components/ThemeToggle.svelte +133 -0
  117. package/dist/components/ThemeToggle.svelte.d.ts +20 -0
  118. package/dist/components/Toaster.svelte +110 -0
  119. package/dist/components/Toaster.svelte.d.ts +8 -0
  120. package/dist/components/Tooltip.svelte +33 -0
  121. package/dist/components/Tooltip.svelte.d.ts +12 -0
  122. package/dist/date.d.ts +35 -0
  123. package/dist/date.js +108 -0
  124. package/dist/fonts.css +193 -0
  125. package/dist/index.d.ts +105 -0
  126. package/dist/index.js +77 -0
  127. package/dist/locale.svelte.d.ts +93 -0
  128. package/dist/locale.svelte.js +153 -0
  129. package/dist/theme.css +210 -0
  130. package/dist/toast.svelte.d.ts +47 -0
  131. package/dist/toast.svelte.js +60 -0
  132. package/dist/tones.d.ts +48 -0
  133. package/dist/tones.js +209 -0
  134. package/package.json +349 -0
  135. package/registry.json +669 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NQM Creative
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,507 @@
1
+ # @nqmcreative/ui
2
+
3
+ Svelte 5 (runes) + Tailwind CSS v4 component library, styled to match
4
+ [nqmcreative.com](https://nqmcreative.com) — flat surfaces, no shadows, no
5
+ border-radius except pills, brand teal `#1c6358`, Sora/Work Sans/JetBrains Mono.
6
+
7
+ Everything in `src/lib` is the package. Everything in `src/routes` is a local
8
+ dev preview (`bun run dev`) — it is not published.
9
+
10
+ ## What's included
11
+
12
+ ### Design tokens (`theme.css`)
13
+
14
+ A Tailwind v4 `@theme` block. Surfaces (`bg`, `bg-alt`, `bg-inset`, `hairline`,
15
+ `hairline-strong`), text (`text`, `text-secondary`, `text-muted`,
16
+ `text-inverse`), type (`font-sans` Work Sans, `font-heading` Sora, `font-mono`
17
+ JetBrains Mono) and motion (`ease-brand`, `ease-brand-out`).
18
+
19
+ **Colour is tonal.** `brand` (teal `#1c6358`) is the primary, and seven more
20
+ hues follow the exact same four-step shape:
21
+
22
+ | tone | base | used for |
23
+ | --------- | --------- | --------------------------------- |
24
+ | `brand` | `#1c6358` | primary actions, active states |
25
+ | `accent` | `#c2410c` | secondary emphasis, highlights |
26
+ | `violet` | `#6d28d9` | tertiary / categorical |
27
+ | `info` | `#1d4ed8` | informational messaging |
28
+ | `success` | `#15803d` | confirmations, positive deltas |
29
+ | `warning` | `#b45309` | soft warnings, quotas |
30
+ | `danger` | `#b91c1c` | destructive actions, errors |
31
+ | `neutral` | `#475569` | quiet chrome, disabled-ish states |
32
+
33
+ Each one ships four steps — `--color-<tone>`, `-hover`, `-light` (tinted
34
+ surface) and `-border` — so `bg-accent`, `hover:bg-accent-hover`,
35
+ `bg-accent-light` and `border-accent-border` all exist as utilities.
36
+
37
+ Every tonal component takes the same `tone` prop:
38
+
39
+ ```svelte
40
+ <Button tone="danger">Delete</Button>
41
+ <Badge tone="warning" dot>Quota</Badge>
42
+ <Alert tone="success" title="Deployed" />
43
+ <Progress tone="violet" value={40} />
44
+ ```
45
+
46
+ `tones.ts` exports the shared class maps (`toneSolid`, `toneSoft`, `toneText`,
47
+ `toneFill`, `toneRing`, …) plus the `Tone` type and the `TONES` array, so your
48
+ own components can join the same system.
49
+
50
+ ### Components
51
+
52
+ | group | components |
53
+ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
+ | Actions | `Button`, `Link` |
55
+ | Forms | `Input`, `Textarea`, `Select`, `Combobox`, `MultiSelect`, `DatePicker`, `Calendar`, `Checkbox`, `CheckboxGroup`, `Radio`, `RadioGroup`, `SegmentedControl`, `Switch`, `Slider`, `NumberInput`, `PasswordInput`, `InputGroup`, `Dropzone`, `Field`, `Label` |
56
+ | Data display | `Badge`, `Avatar`, `AvatarGroup`, `Card`, `Stat`, `Table`, `Kbd` |
57
+ | Feedback | `Alert`, `Toaster` + `toast`, `Progress`, `Spinner`, `Skeleton`, `EmptyState` |
58
+ | Layout & nav | `Divider`, `Tabs`, `Accordion`, `AccordionItem`, `Breadcrumb`, `Pagination`, `Steps` |
59
+ | App shell | `Navbar`, `Sidebar`, `Footer` |
60
+ | Overlay | `Modal`, `ConfirmDialog`, `Drawer`, `Dropdown` + `MenuItem` + `MenuSeparator`, `ContextMenu`, `CommandPalette`, `Popover`, `Tooltip` |
61
+ | System | `ThemeToggle`, `LocaleProvider` |
62
+
63
+ Notable props:
64
+
65
+ - `Button` — `variant` (`solid` / `soft` / `outline` / `ghost` / `link`),
66
+ `size` (`sm` / `md` / `lg` / `xl`), `tone`, `loading`, `block`, `href`.
67
+ - `Input` — `prefix` / `suffix` snippets, `invalid`, `size`, `tone`.
68
+ - `Field` — wraps any control with `label`, `hint`, `error`, `required`.
69
+ - `Combobox` — searchable select with grouping, keyboard nav and `clearable`.
70
+ - `Dropzone` — drag & drop with `accept`, `maxSize`, `maxFiles`; reports what it
71
+ turned away through `onreject`.
72
+ - `Table` — `columns` + `rows`, per-column `sortable`, `bind:sort`,
73
+ `selectable` with `bind:selected` and `rowKey`, `stickyHeader`, and a `cell`
74
+ snippet for custom rendering.
75
+ - `Modal` / `Drawer` — native `<dialog>`, `bind:open`, `footer` snippet;
76
+ `Drawer` adds `side` and `size`.
77
+ - `Tabs` — `underline` / `pill` / `segmented`, `bind:value`.
78
+ - `Pagination` — `bind:page`, `total`, collapses with `…`.
79
+ - `CommandPalette` — `items` with `group` / `shortcut` / hidden `keywords`;
80
+ opens on ⌘K / Ctrl+K by default (`hotkey={null}` to wire your own trigger).
81
+ - `ConfirmDialog` — awaits `onconfirm`, staying open and busy until it settles.
82
+ - `ContextMenu` — right-click anywhere in `children`; the menu is placed at the
83
+ pointer and flipped near the edges.
84
+ - `CheckboxGroup` — `bind:value` as `string[]`, optional `max`.
85
+ - `MultiSelect` — chips in the field, grouping, `max`, Backspace peels the last
86
+ chip off.
87
+ - `DatePicker` / `Calendar` — value is a `YYYY-MM-DD` string, `min` / `max`, and
88
+ an `isDisabled(date)` predicate for weekends or holidays. Month names, weekday
89
+ order and the input's part order all follow the locale.
90
+ - `Navbar` — desktop links collapse into a `Drawer` below `md`; nested `items`
91
+ become a `Dropdown`.
92
+ - `Sidebar` — `sections` of nested `items`, `bind:value` for the active one,
93
+ `bind:collapsed` for the rail.
94
+ - `Steps` — horizontal or vertical, `bind:current`, per-step `error`, and
95
+ `clickable` to jump back to a finished step.
96
+
97
+ ### Overlays without a dependency
98
+
99
+ `Dropdown`, `Popover` and `Combobox` render into `document.body` and are placed
100
+ by a small in-house action, so no floating-ui and no CSS anchor positioning:
101
+
102
+ - `anchored` — fixed positioning against a trigger, flips to the opposite side
103
+ when it doesn't fit, then clamps inside the viewport; follows scroll (capture
104
+ phase, so nested scrollers count) and resize.
105
+ - `clickOutside`, `focusTrap`, `portal`, `navigateList` — the behaviours every
106
+ overlay needs, exported so your own components can reuse them.
107
+
108
+ The trade-off: no sub-pixel middleware pipeline, no automatic arrow placement,
109
+ and `Tooltip` stays CSS-only (it can clip inside `overflow: hidden`, and doesn't
110
+ flip). Everything else escapes clipping and survives scrolling. Reach for
111
+ `Popover` when a tooltip would be clipped.
112
+
113
+ ### Toasts
114
+
115
+ `toast` is a module-level store, so any file can fire one without prop
116
+ drilling. Mount `<Toaster />` once, near the root of your app:
117
+
118
+ ```svelte
119
+ <script>
120
+ import { Toaster, toast } from '@nqmcreative/ui';
121
+ </script>
122
+
123
+ <Toaster position="bottom-right" />
124
+
125
+ <button onclick={() => toast.success('Project archived')}>Archive</button>
126
+ ```
127
+
128
+ `toast.success` / `error` / `warning` / `info` / `loading`, plus `show`,
129
+ `update(id, …)` for a loading toast that becomes a result, `dismiss(id)` and
130
+ `clear()`.
131
+
132
+ ### Dark mode
133
+
134
+ `theme.css` ships both palettes. Nothing in a component says `dark:` — every
135
+ utility is built on the tokens, so the whole system flips at once. The root
136
+ element decides:
137
+
138
+ | root | result |
139
+ | ---------------------- | ------------------------- |
140
+ | `<html class="dark">` | dark, explicitly chosen |
141
+ | `<html class="light">` | light, explicitly chosen |
142
+ | `<html>` | follows the OS preference |
143
+
144
+ `ThemeToggle` writes that class and remembers the choice under the
145
+ `nqm-theme` key:
146
+
147
+ ```svelte
148
+ <ThemeToggle variant="segmented" />
149
+ <!-- or a single cycling button -->
150
+ <ThemeToggle />
151
+ ```
152
+
153
+ Because the class is applied after hydration, a visitor who chose dark sees one
154
+ light frame first. Set the class before first paint to avoid it — in SvelteKit
155
+ this goes in `src/app.html`, above `%sveltekit.head%`:
156
+
157
+ ```html
158
+ <script>
159
+ const saved = localStorage.getItem('nqm-theme');
160
+ if (saved === 'dark' || saved === 'light') document.documentElement.classList.add(saved);
161
+ </script>
162
+ ```
163
+
164
+ The key has to match what `ThemeToggle` writes. `applyTheme(theme)` and
165
+ `storedTheme()` are exported for the same purpose if you would rather not
166
+ hardcode it.
167
+
168
+ `@custom-variant dark` is registered too, so `dark:` utilities work in your own
169
+ markup when you need a one-off override.
170
+
171
+ ### Dates
172
+
173
+ `DatePicker` and `Calendar` speak `YYYY-MM-DD` strings, never `Date` objects —
174
+ a `Date` is a UTC instant, so `new Date('2026-03-01')` is the last day of
175
+ February for anyone west of Greenwich. The helpers behind them (`toISO`,
176
+ `fromISO`, `addMonths`, `monthGrid`, `parseFormatted`, …) are exported and
177
+ stay in the viewer's local calendar throughout.
178
+
179
+ ### Locale
180
+
181
+ Every string a component renders on its own — ARIA names, `No data`, the words
182
+ in a rejected-file message — comes from one locale object. Nothing is hardcoded
183
+ inside a component.
184
+
185
+ ```js
186
+ // one global locale, the common case
187
+ import { setLocale, idID } from '@nqmcreative/ui';
188
+ setLocale(idID);
189
+ ```
190
+
191
+ ```svelte
192
+ <!-- or scoped to a subtree — the right choice under SSR, where module
193
+ state is shared between requests -->
194
+ <script>
195
+ import { LocaleProvider, idID } from '@nqmcreative/ui';
196
+ </script>
197
+
198
+ <LocaleProvider locale={idID}>
199
+ <App />
200
+ </LocaleProvider>
201
+ ```
202
+
203
+ `enUS` and `idID` ship with the package; pass a partial to change just a few
204
+ keys. Precedence is per-instance prop → provider → global → `enUS`, so
205
+ `<Table empty="…" />` still wins over everything.
206
+
207
+ ### `fonts.css`
208
+
209
+ Optional. Ships the three typefaces as plain `@font-face` rules pointing at
210
+ Google's CDN (Latin + Latin Extended, `font-display: swap`). Skip it if you
211
+ self-host fonts or already load them.
212
+
213
+ It deliberately does _not_ use `@import url('https://fonts.googleapis.com/…')`:
214
+ `@import` is only valid before every other rule, so bundlers drop it when the
215
+ file is pulled in after `tailwindcss` — the page then silently falls back to
216
+ `system-ui` with no error anywhere. `@font-face` works from any position.
217
+
218
+ Add this to your document head so the connection opens before the CSS parses:
219
+
220
+ ```html
221
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
222
+ ```
223
+
224
+ ## Developing
225
+
226
+ ```bash
227
+ bun install
228
+ bun run dev # preview components at localhost:5173
229
+ bun run check # type-check
230
+ bun run test # vitest
231
+ bun run build # svelte-package + publint -> dist/
232
+ ```
233
+
234
+ **`dist/` is committed on purpose — rebuild and commit it with your change.**
235
+ Consuming projects read from `dist/`, not `src/`, and nothing builds it for
236
+ them: bun blocks a dependency's `prepare` script, and even once trusted it
237
+ runs without `node_modules/.bin` on PATH, so `svelte-kit` is not found.
238
+ Shipping the build output is what keeps installing from GitHub working.
239
+
240
+ ```bash
241
+ bun run build && git add dist
242
+ ```
243
+
244
+ `src/routes` is the docs site:
245
+
246
+ | route | what it is |
247
+ | -------------------- | ----------------------------------------------------------- |
248
+ | `/` | landing — palette, install, the full catalogue |
249
+ | `/components` | filterable index of all 57 |
250
+ | `/components/[slug]` | one page per component: live demo, source, what it pulls in |
251
+ | `/playground` | the old kitchen-sink preview |
252
+ | `/playground/shell` | navbar + sidebar + footer at a realistic size |
253
+
254
+ Component pages are generated from `registry.json`, and each demo lives in
255
+ `src/lib/site/demos/<slug>.svelte` — the file is both rendered live and shown
256
+ as its own source. Add a component and it appears in the nav, the index, the
257
+ command palette and the sidebar with no further wiring; only the demo file is
258
+ written by hand.
259
+
260
+ The site is fully prerendered and built with `@sveltejs/adapter-cloudflare`,
261
+ so `.svelte-kit/cloudflare` can be uploaded as-is.
262
+
263
+ Tests cover the logic that is easy to get quietly wrong: the date helpers
264
+ (timezone drift, impossible dates, per-locale parsing), the `anchored` action
265
+ (flip and viewport clamping), `Table` (numeric vs. string sorting, selection
266
+ after a sort), and `Dropzone` (type, size and count rules).
267
+
268
+ ## Installing it in another project
269
+
270
+ > The docs site covers this with live examples — `/docs/installation`,
271
+ > `/docs/theming`, `/docs/locale` and `/docs/adding-components`. Run
272
+ > `bun run dev` to read them locally. What follows is the same ground in
273
+ > short form.
274
+
275
+ **bun is the package manager here**; `bun.lock` is the committed lockfile.
276
+
277
+ ### Option A — npm (recommended)
278
+
279
+ ```bash
280
+ bun add @nqmcreative/ui
281
+ ```
282
+
283
+ ```bash
284
+ bunx nqm-ui init
285
+ ```
286
+
287
+ Update with `bun update @nqmcreative/ui`.
288
+
289
+ ### Option A2 — straight from GitHub
290
+
291
+ Tracks `main` rather than a release.
292
+
293
+ ```bash
294
+ bun add github:mukhsamr/nqmcreative-ui
295
+ ```
296
+
297
+ The repo is public, so this needs no authentication — no SSH key, no token.
298
+ `git+https://github.com/mukhsamr/nqmcreative-ui.git` resolves to the same
299
+ thing; bun rewrites both to `github:owner/repo#sha`.
300
+
301
+ `dist/` is committed, so this works as-is: nothing has to be built at install
302
+ time.
303
+
304
+ > bun caches git dependencies by URL and will not notice new commits. Pin a
305
+ > commit or tag with `#sha`, or clear the cache with `bun pm cache rm`.
306
+ > Installing from npm avoids this entirely.
307
+
308
+ ### Option B — a packed tarball
309
+
310
+ Portable, and needs no repo access — hand it to someone or drop it in a CI
311
+ cache.
312
+
313
+ ```bash
314
+ bun run build && bun pm pack # in this repo
315
+ ```
316
+
317
+ ```bash
318
+ bun add ./nqmcreative-ui-0.1.0.tgz # in your other project
319
+ ```
320
+
321
+ ### Option C — a live symlink, for developing both at once
322
+
323
+ `bun link` and `bun add file:<dir>` both fail on Windows with
324
+ `EBUSY: failed opening cache/package/version dir` — bun copies the whole source
325
+ directory, `node_modules` (160 MB) included, into its cache. npm's symlink is
326
+ the way here:
327
+
328
+ ```bash
329
+ npm install file:../path/to/nqmcreative-ui
330
+ ```
331
+
332
+ `bun run build` in this repo then shows up without reinstalling.
333
+
334
+ ### The CLI
335
+
336
+ `init` does the wiring below for you — the `@source` line, the font preconnect
337
+ and the no-flash theme script are the three steps most easily forgotten:
338
+
339
+ ```bash
340
+ bunx nqm-ui init
341
+ ```
342
+
343
+ It detects SvelteKit or plain Vite, writes `src/app.css`, patches (or creates)
344
+ `src/app.html`, and adds the CSS import to `src/routes/+layout.svelte`. Every
345
+ write is idempotent — run it twice and the second run changes nothing. Add
346
+ `--dry-run` to see what it would touch.
347
+
348
+ It will not rewrite your Vite config; it prints the two lines to paste instead.
349
+
350
+ ```bash
351
+ bunx nqm-ui list forms # every component in a category
352
+ bunx nqm-ui info date-picker # subpath, what it renders, what it imports
353
+ bunx nqm-ui add button badge # print the import lines
354
+ bunx nqm-ui add button --to src/routes/+page.svelte # …or insert them
355
+ ```
356
+
357
+ `add` is a convenience, not an installer: the whole library is already there
358
+ after `bun add`, and a component pulls in whatever it renders internally — `add
359
+ date-picker` mentions that `Calendar` comes with it, but there is nothing extra
360
+ to install.
361
+
362
+ ### Then, in every case
363
+
364
+ **1. Peer requirements** — the consuming project brings its own `svelte` ^5 and
365
+ Tailwind CSS v4. The package itself has no runtime dependencies.
366
+
367
+ **2. SvelteKit from scratch**, start to finish:
368
+
369
+ ```bash
370
+ bunx sv create myapp --template minimal --types ts --install bun
371
+ ```
372
+
373
+ ```bash
374
+ cd myapp && bun add -d tailwindcss @tailwindcss/vite
375
+ ```
376
+
377
+ ```bash
378
+ bun add @nqmcreative/ui
379
+ ```
380
+
381
+ Register the Tailwind plugin **before** `sveltekit()` in `vite.config.ts`:
382
+
383
+ ```ts
384
+ import tailwindcss from '@tailwindcss/vite';
385
+ import { sveltekit } from '@sveltejs/kit/vite';
386
+
387
+ export default defineConfig({
388
+ plugins: [tailwindcss(), sveltekit()]
389
+ });
390
+ ```
391
+
392
+ Import the CSS once, in `src/routes/+layout.svelte`, and mount `Toaster` there
393
+ if you use toasts:
394
+
395
+ ```svelte
396
+ <script lang="ts">
397
+ import '../app.css';
398
+ import { Toaster, setLocale, idID } from '@nqmcreative/ui';
399
+
400
+ setLocale(idID);
401
+
402
+ let { children } = $props();
403
+ </script>
404
+
405
+ {@render children()}
406
+
407
+ <Toaster position="bottom-right" />
408
+ ```
409
+
410
+ Add the font preconnect to `src/app.html`, above `%sveltekit.head%` — it belongs
411
+ in plain HTML, because Svelte parses a bare `crossorigin` attribute as boolean
412
+ `true` and `svelte-check` rejects it:
413
+
414
+ ```html
415
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
416
+ ```
417
+
418
+ **3. Wire up the CSS.** In your app's Tailwind entry CSS:
419
+
420
+ ```css
421
+ @import 'tailwindcss';
422
+ @import '@nqmcreative/ui/theme.css';
423
+ @import '@nqmcreative/ui/fonts.css'; /* optional */
424
+
425
+ /* Tailwind v4 ignores node_modules by default — point it at the package's
426
+ dist so the class names used inside the components are generated */
427
+ @source '../node_modules/@nqmcreative/ui/dist';
428
+ ```
429
+
430
+ Adjust the `@source` path to wherever `node_modules` resolves relative to that
431
+ CSS file. Without it, every component renders unstyled — Tailwind never sees the
432
+ class names that live inside the package.
433
+
434
+ `@source` scans the whole package, so the generated CSS covers every component,
435
+ not only the ones you import — roughly 48 kB, 9 kB gzipped, for the full set.
436
+
437
+ **4. Use the components.** Import each one on its own:
438
+
439
+ ```svelte
440
+ <script>
441
+ import Button from '@nqmcreative/ui/button';
442
+ import Badge from '@nqmcreative/ui/badge';
443
+ import Field from '@nqmcreative/ui/field';
444
+ import Input from '@nqmcreative/ui/input';
445
+
446
+ let email = $state('');
447
+ </script>
448
+
449
+ <Button>Get started</Button>
450
+ <Button variant="soft" tone="accent">Learn more</Button>
451
+ <Badge tone="success" dot>Live</Badge>
452
+
453
+ <Field label="Work email" hint="We only email about releases.">
454
+ <Input bind:value={email} placeholder="you@example.com" />
455
+ </Field>
456
+ ```
457
+
458
+ Every component has its own subpath — the file name in kebab-case, so
459
+ `AvatarGroup` is `@nqmcreative/ui/avatar-group` and `DatePicker` is
460
+ `@nqmcreative/ui/date-picker`. The shared modules are there too:
461
+ `/tones`, `/locale`, `/date`, `/toast`, `/actions/anchor`,
462
+ `/actions/dismissable`.
463
+
464
+ The barrel still works if you prefer it:
465
+
466
+ ```js
467
+ import { Button, Badge } from '@nqmcreative/ui';
468
+ ```
469
+
470
+ Both produce the same bundle — measured on the same app, barrel and subpaths
471
+ came out byte-identical, because the barrel tree-shakes. Subpaths are about
472
+ being explicit about what a file uses, and they cut the module graph the
473
+ bundler walks (177 modules to 123 in that test), not the output.
474
+
475
+ Update with `bun update @nqmcreative/ui`. The `exports` map is generated from
476
+ `src/lib` by `bun run exports`, and `bun run lint` fails if it is stale — a new
477
+ component cannot ship without its subpath.
478
+
479
+ ## Adding more components
480
+
481
+ Drop a new `.svelte` file in `src/lib/components`, export it from
482
+ `src/lib/index.ts`, style it with the existing tokens (`bg-brand`,
483
+ `text-text-secondary`, `font-heading`, `ease-brand-out`, etc.) instead of raw
484
+ hex values, then `bun run build`.
485
+
486
+ If the component is tonal, import the maps from `../tones.js` rather than
487
+ writing colour classes by hand — Tailwind only sees literal class strings, so
488
+ `bg-{tone}` would silently produce nothing:
489
+
490
+ ```svelte
491
+ <script lang="ts">
492
+ import { toneSoft, toneFill, type Tone } from '../tones.js';
493
+
494
+ let { tone = 'brand' }: { tone?: Tone } = $props();
495
+ </script>
496
+
497
+ <div class={toneSoft[tone]}>…</div>
498
+ ```
499
+
500
+ One more rule worth knowing: never put two same-property colour utilities on
501
+ one element (`border-hairline` + `border-brand`), because CSS order — not class
502
+ order — decides the winner. Use the side-specific map (`toneBorderLeft`) or set
503
+ the colour once per variant.
504
+
505
+ ## License
506
+
507
+ MIT — see [LICENSE](LICENSE).