@nqmcreative/ui 0.1.0 → 0.1.1

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,506 +1,163 @@
1
1
  # @nqmcreative/ui
2
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.
3
+ Svelte 5 (runes) + Tailwind CSS v4 component library. Flat surfaces, no
4
+ shadows, no border-radius except pills. 57 components, eight tones, light and
5
+ dark, no runtime dependencies.
6
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.
7
+ The docs site lives in this repo every component with a live demo, plus
8
+ theming, locale and contributing guides. `bun run dev` to read it.
9
9
 
10
- ## What's included
10
+ ## Install
11
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} />
12
+ ```bash
13
+ bun add @nqmcreative/ui
44
14
  ```
45
15
 
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>
16
+ ```bash
17
+ bunx nqm-ui init
126
18
  ```
127
19
 
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()`.
20
+ `init` writes `src/app.css`, patches `src/app.html`, and adds the CSS import to
21
+ your root layout. It prints the one Vite change to paste — the Tailwind plugin,
22
+ before `sveltekit()`:
131
23
 
132
- ### Dark mode
24
+ ```ts
25
+ plugins: [tailwindcss(), sveltekit()];
26
+ ```
133
27
 
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:
28
+ Your project brings its own `svelte` ^5 and Tailwind CSS v4.
137
29
 
138
- | root | result |
139
- | ---------------------- | ------------------------- |
140
- | `<html class="dark">` | dark, explicitly chosen |
141
- | `<html class="light">` | light, explicitly chosen |
142
- | `<html>` | follows the OS preference |
30
+ <details>
31
+ <summary>Wiring it by hand instead</summary>
143
32
 
144
- `ThemeToggle` writes that class and remembers the choice under the
145
- `nqm-theme` key:
33
+ ```css
34
+ /* src/app.css */
35
+ @import 'tailwindcss';
36
+ @import '@nqmcreative/ui/theme.css';
37
+ @import '@nqmcreative/ui/fonts.css';
146
38
 
147
- ```svelte
148
- <ThemeToggle variant="segmented" />
149
- <!-- or a single cycling button -->
150
- <ThemeToggle />
39
+ /* Tailwind v4 skips node_modules. Without this every component renders
40
+ unstyled, with no error anywhere. */
41
+ @source '../node_modules/@nqmcreative/ui/dist';
151
42
  ```
152
43
 
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%`:
44
+ Import that CSS once in `src/routes/+layout.svelte`, and put the font
45
+ preconnect plus the no-flash theme script in `src/app.html`, above
46
+ `%sveltekit.head%`:
156
47
 
157
48
  ```html
49
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
158
50
  <script>
159
51
  const saved = localStorage.getItem('nqm-theme');
160
52
  if (saved === 'dark' || saved === 'light') document.documentElement.classList.add(saved);
161
53
  </script>
162
54
  ```
163
55
 
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.
56
+ </details>
178
57
 
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.
58
+ ## Use
184
59
 
185
- ```js
186
- // one global locale, the common case
187
- import { setLocale, idID } from '@nqmcreative/ui';
188
- setLocale(idID);
189
- ```
60
+ Each component has its own subpath — the file name in kebab-case:
190
61
 
191
62
  ```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.
63
+ <script lang="ts">
64
+ import Button from '@nqmcreative/ui/button';
65
+ import Field from '@nqmcreative/ui/field';
66
+ import Input from '@nqmcreative/ui/input';
276
67
 
277
- ### Option A — npm (recommended)
68
+ let email = $state('');
69
+ </script>
278
70
 
279
- ```bash
280
- bun add @nqmcreative/ui
281
- ```
71
+ <Field label="Work email">
72
+ <Input bind:value={email} placeholder="you@example.com" />
73
+ </Field>
282
74
 
283
- ```bash
284
- bunx nqm-ui init
75
+ <Button tone="accent">Get started</Button>
285
76
  ```
286
77
 
287
- Update with `bun update @nqmcreative/ui`.
78
+ The barrel works too: `import { Button } from '@nqmcreative/ui'`.
288
79
 
289
- ### Option A2 — straight from GitHub
80
+ ### Tones
290
81
 
291
- Tracks `main` rather than a release.
82
+ Eight of them — `brand`, `accent`, `violet`, `info`, `success`, `warning`,
83
+ `danger`, `neutral` — and every tonal component takes the same prop:
292
84
 
293
- ```bash
294
- bun add github:mukhsamr/nqmcreative-ui
85
+ ```svelte
86
+ <Button tone="danger">Delete</Button>
87
+ <Badge tone="warning" dot>Quota</Badge>
88
+ <Alert tone="success" title="Deployed" />
295
89
  ```
296
90
 
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`.
91
+ Each ships four steps: `bg-accent`, `hover:bg-accent-hover`, `bg-accent-light`,
92
+ `border-accent-border`. Redeclare any of them in your own `@theme` block to
93
+ rebrand everything at once.
300
94
 
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
95
+ ### Dark mode
309
96
 
310
- Portable, and needs no repo access hand it to someone or drop it in a CI
311
- cache.
97
+ `theme.css` ships both palettes; nothing in a component says `dark:`.
98
+ `<html class="dark">` or `class="light"` forces one, bare `<html>` follows the
99
+ OS. `ThemeToggle` handles all three.
312
100
 
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
- ```
101
+ ### Locale
320
102
 
321
- ### Option Ca live symlink, for developing both at once
103
+ Every built-in stringARIA names, `No data`, the calendar's month names —
104
+ comes from one object:
322
105
 
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:
106
+ ```js
107
+ import { setLocale, idID } from '@nqmcreative/ui/locale';
327
108
 
328
- ```bash
329
- npm install file:../path/to/nqmcreative-ui
109
+ setLocale(idID);
330
110
  ```
331
111
 
332
- `bun run build` in this repo then shows up without reinstalling.
333
-
334
- ### The CLI
112
+ `enUS` and `idID` ship with the package; pass a partial to change a few keys.
113
+ Under SSR use `<LocaleProvider>` instead, so the locale is scoped to a request.
335
114
 
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:
115
+ ## Components
338
116
 
339
- ```bash
340
- bunx nqm-ui init
341
- ```
117
+ | group | components |
118
+ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
+ | Actions | `Button`, `Link` |
120
+ | Forms | `Input`, `Textarea`, `Select`, `Combobox`, `MultiSelect`, `DatePicker`, `Calendar`, `Checkbox`, `CheckboxGroup`, `Radio`, `RadioGroup`, `SegmentedControl`, `Switch`, `Slider`, `NumberInput`, `PasswordInput`, `InputGroup`, `Dropzone`, `Field`, `Label` |
121
+ | Data display | `Badge`, `Avatar`, `AvatarGroup`, `Card`, `Stat`, `Table`, `Kbd` |
122
+ | Feedback | `Alert`, `Toaster` + `toast`, `Progress`, `Spinner`, `Skeleton`, `EmptyState` |
123
+ | Layout & nav | `Divider`, `Tabs`, `Accordion`, `AccordionItem`, `Breadcrumb`, `Pagination`, `Steps` |
124
+ | App shell | `Navbar`, `Sidebar`, `Footer` |
125
+ | Overlay | `Modal`, `ConfirmDialog`, `Drawer`, `Dropdown` + `MenuItem` + `MenuSeparator`, `ContextMenu`, `CommandPalette`, `Popover`, `Tooltip` |
126
+ | System | `ThemeToggle`, `LocaleProvider` |
342
127
 
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.
128
+ Overlays are built on the native top layer and three in-house actions
129
+ (`portal`, `focusTrap`, `anchored`) rather than a positioning dependency.
347
130
 
348
- It will not rewrite your Vite config; it prints the two lines to paste instead.
131
+ ## CLI
349
132
 
350
133
  ```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
134
+ bunx nqm-ui list forms # components in a category
135
+ bunx nqm-ui info date-picker # subpath, and what it pulls in
353
136
  bunx nqm-ui add button badge # print the import lines
354
- bunx nqm-ui add button --to src/routes/+page.svelte # …or insert them
137
+ bunx nqm-ui add button --to src/routes/+page.svelte
355
138
  ```
356
139
 
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
- ```
140
+ ## Developing
376
141
 
377
142
  ```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';
143
+ bun install
144
+ bun run dev # the docs site at localhost:5173
145
+ bun run check # type-check
146
+ bun run test # vitest
147
+ bun run build # site + package
468
148
  ```
469
149
 
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:
150
+ `src/lib` is the package, `src/site` and `src/routes` are the docs site.
489
151
 
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
- ```
152
+ **`dist/` is committed — rebuild and commit it with your change.** Consumers
153
+ read from `dist/`, and installing from GitHub cannot build it: bun blocks a
154
+ dependency's `prepare` script.
499
155
 
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.
156
+ Adding a component: write it in `src/lib/components`, export it from
157
+ `src/lib/index.ts` under a category comment, add a demo in `src/site/demos`,
158
+ then `bun run build`. `bun run lint` fails if the generated exports map or
159
+ `registry.json` is stale. The full conventions are on the site under
160
+ `/docs/adding-components`.
504
161
 
505
162
  ## License
506
163
 
package/dist/fonts.css CHANGED
@@ -1,7 +1,8 @@
1
1
  /*
2
- * Optional: the exact Google Fonts used by nqmcreative.com — Sora (headings),
3
- * Work Sans (body), JetBrains Mono (labels and code). Import this only if your
4
- * project doesn't already self-host or load these fonts another way.
2
+ * Optional: the three typefaces this system is designed around — Sora
3
+ * (headings), Work Sans (body), JetBrains Mono (labels and code). Import this
4
+ * only if your project doesn't already self-host or load these fonts another
5
+ * way.
5
6
  *
6
7
  * These are plain `@font-face` rules rather than an `@import` of the Google
7
8
  * stylesheet on purpose: `@import` is only valid before every other rule, so a
package/dist/theme.css CHANGED
@@ -1,6 +1,5 @@
1
1
  /*
2
2
  * @nqmcreative/ui design tokens
3
- * Mirrors the visual language of https://nqmcreative.com
4
3
  * Flat, no shadows, no border-radius (except pills). Import this after
5
4
  * `@import "tailwindcss";` in your app's CSS entry point. Ships light and dark
6
5
  * palettes — see the bottom of this file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqmcreative/ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Svelte 5 + Tailwind CSS v4 design system \u2014 57 components, eight tones, light and dark, no runtime dependencies.",
5
5
  "scripts": {
6
6
  "dev": "vite dev",