@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 +99 -442
- package/dist/fonts.css +4 -3
- package/dist/theme.css +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,506 +1,163 @@
|
|
|
1
1
|
# @nqmcreative/ui
|
|
2
2
|
|
|
3
|
-
Svelte 5 (runes) + Tailwind CSS v4 component library
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
##
|
|
10
|
+
## Install
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
`
|
|
129
|
-
|
|
130
|
-
`
|
|
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
|
-
|
|
24
|
+
```ts
|
|
25
|
+
plugins: [tailwindcss(), sveltekit()];
|
|
26
|
+
```
|
|
133
27
|
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
33
|
+
```css
|
|
34
|
+
/* src/app.css */
|
|
35
|
+
@import 'tailwindcss';
|
|
36
|
+
@import '@nqmcreative/ui/theme.css';
|
|
37
|
+
@import '@nqmcreative/ui/fonts.css';
|
|
146
38
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
import
|
|
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
|
-
|
|
68
|
+
let email = $state('');
|
|
69
|
+
</script>
|
|
278
70
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
71
|
+
<Field label="Work email">
|
|
72
|
+
<Input bind:value={email} placeholder="you@example.com" />
|
|
73
|
+
</Field>
|
|
282
74
|
|
|
283
|
-
|
|
284
|
-
bunx nqm-ui init
|
|
75
|
+
<Button tone="accent">Get started</Button>
|
|
285
76
|
```
|
|
286
77
|
|
|
287
|
-
|
|
78
|
+
The barrel works too: `import { Button } from '@nqmcreative/ui'`.
|
|
288
79
|
|
|
289
|
-
###
|
|
80
|
+
### Tones
|
|
290
81
|
|
|
291
|
-
|
|
82
|
+
Eight of them — `brand`, `accent`, `violet`, `info`, `success`, `warning`,
|
|
83
|
+
`danger`, `neutral` — and every tonal component takes the same prop:
|
|
292
84
|
|
|
293
|
-
```
|
|
294
|
-
|
|
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
|
-
|
|
298
|
-
`
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
+
Every built-in string — ARIA names, `No data`, the calendar's month names —
|
|
104
|
+
comes from one object:
|
|
322
105
|
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
329
|
-
npm install file:../path/to/nqmcreative-ui
|
|
109
|
+
setLocale(idID);
|
|
330
110
|
```
|
|
331
111
|
|
|
332
|
-
`
|
|
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
|
-
|
|
337
|
-
and the no-flash theme script are the three steps most easily forgotten:
|
|
115
|
+
## Components
|
|
338
116
|
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
`
|
|
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
|
-
|
|
131
|
+
## CLI
|
|
349
132
|
|
|
350
133
|
```bash
|
|
351
|
-
bunx nqm-ui list forms #
|
|
352
|
-
bunx nqm-ui info date-picker # subpath, what it
|
|
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
|
|
137
|
+
bunx nqm-ui add button --to src/routes/+page.svelte
|
|
355
138
|
```
|
|
356
139
|
|
|
357
|
-
|
|
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
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
|
3
|
-
* Work Sans (body), JetBrains Mono (labels and code). Import this
|
|
4
|
-
* project doesn't already self-host or load these fonts another
|
|
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