@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.
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/cli/index.mjs +410 -0
- package/dist/actions/anchor.d.ts +29 -0
- package/dist/actions/anchor.js +122 -0
- package/dist/actions/dismissable.d.ts +44 -0
- package/dist/actions/dismissable.js +147 -0
- package/dist/components/Accordion.svelte +14 -0
- package/dist/components/Accordion.svelte.d.ts +8 -0
- package/dist/components/AccordionItem.svelte +46 -0
- package/dist/components/AccordionItem.svelte.d.ts +14 -0
- package/dist/components/Alert.svelte +88 -0
- package/dist/components/Alert.svelte.d.ts +18 -0
- package/dist/components/Avatar.svelte +58 -0
- package/dist/components/Avatar.svelte.d.ts +16 -0
- package/dist/components/AvatarGroup.svelte +17 -0
- package/dist/components/AvatarGroup.svelte.d.ts +8 -0
- package/dist/components/Badge.svelte +53 -0
- package/dist/components/Badge.svelte.d.ts +18 -0
- package/dist/components/Breadcrumb.svelte +46 -0
- package/dist/components/Breadcrumb.svelte.d.ts +14 -0
- package/dist/components/Button.svelte +94 -0
- package/dist/components/Button.svelte.d.ts +21 -0
- package/dist/components/Calendar.svelte +206 -0
- package/dist/components/Calendar.svelte.d.ts +22 -0
- package/dist/components/Card.svelte +76 -0
- package/dist/components/Card.svelte.d.ts +21 -0
- package/dist/components/Checkbox.svelte +69 -0
- package/dist/components/Checkbox.svelte.d.ts +15 -0
- package/dist/components/CheckboxGroup.svelte +109 -0
- package/dist/components/CheckboxGroup.svelte.d.ts +30 -0
- package/dist/components/Combobox.svelte +268 -0
- package/dist/components/Combobox.svelte.d.ts +29 -0
- package/dist/components/CommandPalette.svelte +226 -0
- package/dist/components/CommandPalette.svelte.d.ts +32 -0
- package/dist/components/ConfirmDialog.svelte +97 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +22 -0
- package/dist/components/ContextMenu.svelte +90 -0
- package/dist/components/ContextMenu.svelte.d.ts +15 -0
- package/dist/components/DatePicker.svelte +186 -0
- package/dist/components/DatePicker.svelte.d.ts +23 -0
- package/dist/components/Divider.svelte +30 -0
- package/dist/components/Divider.svelte.d.ts +10 -0
- package/dist/components/Drawer.svelte +161 -0
- package/dist/components/Drawer.svelte.d.ts +20 -0
- package/dist/components/Dropdown.svelte +112 -0
- package/dist/components/Dropdown.svelte.d.ts +23 -0
- package/dist/components/Dropzone.svelte +226 -0
- package/dist/components/Dropzone.svelte.d.ts +32 -0
- package/dist/components/EmptyState.svelte +57 -0
- package/dist/components/EmptyState.svelte.d.ts +18 -0
- package/dist/components/Field.svelte +42 -0
- package/dist/components/Field.svelte.d.ts +16 -0
- package/dist/components/Footer.svelte +93 -0
- package/dist/components/Footer.svelte.d.ts +25 -0
- package/dist/components/Input.svelte +60 -0
- package/dist/components/Input.svelte.d.ts +18 -0
- package/dist/components/InputGroup.svelte +19 -0
- package/dist/components/InputGroup.svelte.d.ts +10 -0
- package/dist/components/Kbd.svelte +17 -0
- package/dist/components/Kbd.svelte.d.ts +8 -0
- package/dist/components/Label.svelte +16 -0
- package/dist/components/Label.svelte.d.ts +9 -0
- package/dist/components/Link.svelte +44 -0
- package/dist/components/Link.svelte.d.ts +16 -0
- package/dist/components/LocaleProvider.svelte +24 -0
- package/dist/components/LocaleProvider.svelte.d.ts +10 -0
- package/dist/components/MenuItem.svelte +58 -0
- package/dist/components/MenuItem.svelte.d.ts +18 -0
- package/dist/components/MenuSeparator.svelte +22 -0
- package/dist/components/MenuSeparator.svelte.d.ts +8 -0
- package/dist/components/Modal.svelte +114 -0
- package/dist/components/Modal.svelte.d.ts +21 -0
- package/dist/components/MultiSelect.svelte +292 -0
- package/dist/components/MultiSelect.svelte.d.ts +31 -0
- package/dist/components/Navbar.svelte +190 -0
- package/dist/components/Navbar.svelte.d.ts +33 -0
- package/dist/components/NumberInput.svelte +100 -0
- package/dist/components/NumberInput.svelte.d.ts +15 -0
- package/dist/components/Pagination.svelte +90 -0
- package/dist/components/Pagination.svelte.d.ts +15 -0
- package/dist/components/PasswordInput.svelte +104 -0
- package/dist/components/PasswordInput.svelte.d.ts +12 -0
- package/dist/components/Popover.svelte +114 -0
- package/dist/components/Popover.svelte.d.ts +19 -0
- package/dist/components/Progress.svelte +56 -0
- package/dist/components/Progress.svelte.d.ts +17 -0
- package/dist/components/Radio.svelte +55 -0
- package/dist/components/Radio.svelte.d.ts +15 -0
- package/dist/components/RadioGroup.svelte +93 -0
- package/dist/components/RadioGroup.svelte.d.ts +27 -0
- package/dist/components/SegmentedControl.svelte +88 -0
- package/dist/components/SegmentedControl.svelte.d.ts +25 -0
- package/dist/components/Select.svelte +61 -0
- package/dist/components/Select.svelte.d.ts +20 -0
- package/dist/components/Sidebar.svelte +206 -0
- package/dist/components/Sidebar.svelte.d.ts +36 -0
- package/dist/components/Skeleton.svelte +46 -0
- package/dist/components/Skeleton.svelte.d.ts +13 -0
- package/dist/components/Slider.svelte +79 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/Spinner.svelte +34 -0
- package/dist/components/Spinner.svelte.d.ts +13 -0
- package/dist/components/Stat.svelte +49 -0
- package/dist/components/Stat.svelte.d.ts +16 -0
- package/dist/components/Steps.svelte +136 -0
- package/dist/components/Steps.svelte.d.ts +24 -0
- package/dist/components/Switch.svelte +64 -0
- package/dist/components/Switch.svelte.d.ts +14 -0
- package/dist/components/Table.svelte +236 -0
- package/dist/components/Table.svelte.d.ts +70 -0
- package/dist/components/Tabs.svelte +91 -0
- package/dist/components/Tabs.svelte.d.ts +23 -0
- package/dist/components/Textarea.svelte +54 -0
- package/dist/components/Textarea.svelte.d.ts +12 -0
- package/dist/components/ThemeToggle.svelte +133 -0
- package/dist/components/ThemeToggle.svelte.d.ts +20 -0
- package/dist/components/Toaster.svelte +110 -0
- package/dist/components/Toaster.svelte.d.ts +8 -0
- package/dist/components/Tooltip.svelte +33 -0
- package/dist/components/Tooltip.svelte.d.ts +12 -0
- package/dist/date.d.ts +35 -0
- package/dist/date.js +108 -0
- package/dist/fonts.css +193 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +77 -0
- package/dist/locale.svelte.d.ts +93 -0
- package/dist/locale.svelte.js +153 -0
- package/dist/theme.css +210 -0
- package/dist/toast.svelte.d.ts +47 -0
- package/dist/toast.svelte.js +60 -0
- package/dist/tones.d.ts +48 -0
- package/dist/tones.js +209 -0
- package/package.json +349 -0
- 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).
|