@human-synthesis/norns-ui 0.0.2 → 0.0.5
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 +77 -129
- package/package.json +23 -15
- package/src/auto-import.js +96 -22
- package/src/components/Accordion.n +38 -0
- package/src/components/Audio.n +24 -0
- package/src/components/Autocomplete.n +59 -0
- package/src/components/Avatar.n +27 -0
- package/src/components/AvatarGroup.n +25 -0
- package/src/components/Badge.n +20 -0
- package/src/components/Banner.n +25 -0
- package/src/components/Breadcrumbs.n +22 -0
- package/src/components/Btn.n +29 -8
- package/src/components/ButtonGroup.n +14 -0
- package/src/components/Calendar.n +153 -0
- package/src/components/Card.n +34 -0
- package/src/components/Carousel.n +66 -0
- package/src/components/Chip.n +27 -0
- package/src/components/Collapsible.n +32 -0
- package/src/components/ColorPicker.n +62 -0
- package/src/components/ContextMenu.n +48 -0
- package/src/components/CopyButton.n +50 -0
- package/src/components/DataTable.n +61 -0
- package/src/components/DatePicker.n +68 -0
- package/src/components/DateRangePicker.n +59 -0
- package/src/components/Dialog.n +54 -0
- package/src/components/Drawer.n +49 -0
- package/src/components/Dropdown.n +41 -0
- package/src/components/Field.n +29 -20
- package/src/components/Form.n +22 -0
- package/src/components/GradientText.n +21 -0
- package/src/components/Header.n +23 -0
- package/src/components/HeroBanner.n +37 -0
- package/src/components/HierarchicalMenu.n +38 -0
- package/src/components/Icon.n +16 -0
- package/src/components/Image.n +31 -0
- package/src/components/MegaMenu.n +36 -0
- package/src/components/MultiSelect.n +56 -0
- package/src/components/NumberInput.n +49 -0
- package/src/components/OtpField.n +67 -0
- package/src/components/Pagination.n +73 -0
- package/src/components/Popover.n +32 -0
- package/src/components/PreviewCard.n +30 -0
- package/src/components/Progress.n +26 -0
- package/src/components/ProgressCircular.n +52 -0
- package/src/components/RichTooltip.n +33 -0
- package/src/components/RippleButton.n +56 -0
- package/src/components/ScrollArea.n +31 -0
- package/src/components/Separator.n +12 -0
- package/src/components/Sheet.n +55 -0
- package/src/components/ShinyButton.n +36 -0
- package/src/components/Skeleton.n +27 -0
- package/src/components/Stepper.n +35 -0
- package/src/components/Surface.n +10 -0
- package/src/components/Tabs.n +28 -0
- package/src/components/TagsInput.n +66 -0
- package/src/components/ThemeToggler.n +43 -0
- package/src/components/TimePicker.n +166 -0
- package/src/components/Timeline.n +27 -0
- package/src/components/ToastProvider.n +16 -0
- package/src/components/ToggleButton.n +29 -0
- package/src/components/ToggleButtonGroup.n +34 -0
- package/src/components/Toolbar.n +15 -0
- package/src/components/Tooltip.n +34 -0
- package/src/components/Tree.n +57 -0
- package/src/components/Uploader.n +88 -0
- package/src/components/Video.n +26 -0
- package/src/index.js +76 -0
- package/src/lib/toast.svelte.js +42 -0
- package/src/motion/AnimatedNumber.n +55 -0
- package/src/motion/GradientBackground.n +25 -0
- package/src/motion/LiquidButton.n +48 -0
- package/src/motion/Reveal.n +37 -0
- package/src/motion/Sparkles.n +53 -0
- package/src/motion/index.js +18 -0
- package/src/styles/atoms.css +1794 -71
- package/src/styles/tokens.css +171 -40
- package/src/types/Accordion.d.ts +17 -0
- package/src/types/AnimatedNumber.d.ts +15 -0
- package/src/types/Audio.d.ts +18 -0
- package/src/types/Autocomplete.d.ts +17 -0
- package/src/types/Avatar.d.ts +14 -0
- package/src/types/AvatarGroup.d.ts +15 -0
- package/src/types/Badge.d.ts +14 -0
- package/src/types/Banner.d.ts +15 -0
- package/src/types/Breadcrumbs.d.ts +15 -0
- package/src/types/Btn.d.ts +10 -0
- package/src/types/ButtonGroup.d.ts +10 -0
- package/src/types/Calendar.d.ts +20 -0
- package/src/types/Card.d.ts +19 -0
- package/src/types/Carousel.d.ts +23 -0
- package/src/types/Chip.d.ts +12 -0
- package/src/types/Collapsible.d.ts +12 -0
- package/src/types/ColorPicker.d.ts +15 -0
- package/src/types/ContextMenu.d.ts +19 -0
- package/src/types/CopyButton.d.ts +16 -0
- package/src/types/DataTable.d.ts +27 -0
- package/src/types/DatePicker.d.ts +18 -0
- package/src/types/DateRangePicker.d.ts +17 -0
- package/src/types/Dialog.d.ts +17 -0
- package/src/types/Drawer.d.ts +18 -0
- package/src/types/Dropdown.d.ts +23 -0
- package/src/types/Field.d.ts +14 -3
- package/src/types/Form.d.ts +17 -0
- package/src/types/GradientBackground.d.ts +14 -0
- package/src/types/GradientText.d.ts +19 -0
- package/src/types/Header.d.ts +12 -0
- package/src/types/HeroBanner.d.ts +17 -0
- package/src/types/HierarchicalMenu.d.ts +20 -0
- package/src/types/Icon.d.ts +21 -0
- package/src/types/Image.d.ts +15 -0
- package/src/types/LiquidButton.d.ts +15 -0
- package/src/types/MegaMenu.d.ts +29 -0
- package/src/types/MultiSelect.d.ts +13 -0
- package/src/types/NumberInput.d.ts +17 -0
- package/src/types/OtpField.d.ts +13 -0
- package/src/types/Pagination.d.ts +13 -0
- package/src/types/Popover.d.ts +18 -0
- package/src/types/PreviewCard.d.ts +16 -0
- package/src/types/Progress.d.ts +14 -0
- package/src/types/ProgressCircular.d.ts +14 -0
- package/src/types/Reveal.d.ts +20 -0
- package/src/types/RichTooltip.d.ts +16 -0
- package/src/types/RippleButton.d.ts +15 -0
- package/src/types/ScrollArea.d.ts +11 -0
- package/src/types/Separator.d.ts +9 -0
- package/src/types/Sheet.d.ts +20 -0
- package/src/types/ShinyButton.d.ts +15 -0
- package/src/types/Skeleton.d.ts +13 -0
- package/src/types/Sparkles.d.ts +13 -0
- package/src/types/Stepper.d.ts +19 -0
- package/src/types/Surface.d.ts +10 -0
- package/src/types/Tabs.d.ts +17 -0
- package/src/types/TagsInput.d.ts +17 -0
- package/src/types/ThemeToggler.d.ts +17 -0
- package/src/types/TimePicker.d.ts +20 -0
- package/src/types/Timeline.d.ts +17 -0
- package/src/types/ToastProvider.d.ts +15 -0
- package/src/types/ToggleButton.d.ts +15 -0
- package/src/types/ToggleButtonGroup.d.ts +20 -0
- package/src/types/Toolbar.d.ts +12 -0
- package/src/types/Tooltip.d.ts +15 -0
- package/src/types/Tree.d.ts +20 -0
- package/src/types/Uploader.d.ts +15 -0
- package/src/types/Video.d.ts +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Teodoroiu / Human Synthesis
|
|
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
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# `@human-synthesis/norns-ui`
|
|
2
2
|
|
|
3
|
-
UI library for the
|
|
4
|
-
|
|
5
|
-
**Status: Phase 2 (forms tier).** Currently ships `Btn`, `Form`, `Field`, `FieldGroup`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `Switch`. Bits-UI-backed behavior tier (Dialog, Popover, Tabs, Toast, …) lands in Phase 3.
|
|
3
|
+
**UI library for the Norns ecosystem.** Pug + Civet components on Tailwind v4, with Bits UI behind the behavior tier and Iconify behind the icon system.
|
|
6
4
|
|
|
7
5
|
## Stack
|
|
8
6
|
|
|
@@ -10,8 +8,10 @@ UI library for the [Norns](https://github.com/human-synthesis/norns) ecosystem
|
|
|
10
8
|
- [Pug](https://pugjs.org) — templates (in `.n` files)
|
|
11
9
|
- [Civet](https://civet.dev) — `<script>` language
|
|
12
10
|
- [Tailwind CSS v4](https://tailwindcss.com) — styling, **hard peer dep**
|
|
11
|
+
- [Bits UI](https://bits-ui.com) — headless behavior (Dialog, Popover, Tabs, Accordion, …)
|
|
12
|
+
- [Iconify](https://iconify.design) — icons; ships with [Lucide](https://lucide.dev) preset
|
|
13
13
|
- [tailwind-merge](https://github.com/dcastil/tailwind-merge) — class deduplication
|
|
14
|
-
- `@human-synthesis/norns`
|
|
14
|
+
- [`@human-synthesis/norns`](https://github.com/human-synthesis/norns) — peer (`nornsAutoImport`)
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
@@ -21,171 +21,119 @@ bun add -D @human-synthesis/norns-ui
|
|
|
21
21
|
|
|
22
22
|
## Setup
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
So `<Btn>`, `<Field>`, `<Input>`, etc. resolve in markup without explicit imports.
|
|
24
|
+
`vite.config.js`:
|
|
27
25
|
|
|
28
26
|
```js
|
|
29
|
-
// vite.config.js
|
|
30
|
-
import { defineConfig } from 'vite';
|
|
31
|
-
import { sveltekit } from '@sveltejs/kit/vite';
|
|
32
|
-
import tailwindcss from '@tailwindcss/vite';
|
|
33
27
|
import { nornsAutoImport } from '@human-synthesis/norns/auto-import';
|
|
34
|
-
import { nornsCivetPlugin } from '@human-synthesis/norns/vite';
|
|
35
28
|
import { presetUI } from '@human-synthesis/norns-ui/auto-import';
|
|
36
29
|
|
|
37
30
|
const ui = presetUI();
|
|
38
31
|
|
|
39
|
-
export default
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
32
|
+
export default {
|
|
33
|
+
plugins: [
|
|
34
|
+
nornsCivetPlugin(),
|
|
35
|
+
nornsAutoImport({
|
|
36
|
+
componentDirs: ['src/lib/components'],
|
|
37
|
+
components: ui.components
|
|
38
|
+
}),
|
|
39
|
+
tailwindcss(),
|
|
40
|
+
sveltekit()
|
|
41
|
+
]
|
|
42
|
+
};
|
|
50
43
|
```
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`componentDirs` resolves first — your `src/lib/components/Btn.n` silently shadows the library's `Btn` whenever you want to override.
|
|
55
|
-
|
|
56
|
-
### 2. Import the styles
|
|
45
|
+
`app.css`:
|
|
57
46
|
|
|
58
47
|
```css
|
|
59
|
-
/* app.css */
|
|
60
48
|
@import 'tailwindcss';
|
|
61
49
|
@import '@human-synthesis/norns-ui/styles';
|
|
62
|
-
|
|
63
|
-
/* override tokens here if you want a different brand color */
|
|
64
|
-
@theme {
|
|
65
|
-
--color-primary-500: oklch(60% 0.2 200);
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 3. Use components in Pug
|
|
70
|
-
|
|
71
|
-
```pug
|
|
72
|
-
section.space-y-4
|
|
73
|
-
h1.text-3xl New note
|
|
74
|
-
|
|
75
|
-
Form(method="POST" action="?/create")
|
|
76
|
-
Field(label="Title" required error!="{form?.errors?.title}")
|
|
77
|
-
Input(name="title" required)
|
|
78
|
-
Field(label="Body" help="Optional — supports plain text only")
|
|
79
|
-
Textarea(name="body" rows="4")
|
|
80
|
-
Btn(type="submit" variant="primary") Create note
|
|
81
50
|
```
|
|
82
51
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
### Phase 1 — atoms
|
|
86
|
-
|
|
87
|
-
CSS classes via `@layer components`. Use directly via Pug class shorthand:
|
|
52
|
+
`svelte.config.js` — wrap our preprocessors so they no-op on `node_modules`. Without this, svelte-preprocess corrupts Bits UI's `<script lang="ts">` source:
|
|
88
53
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### Phase 1 — Btn
|
|
54
|
+
```js
|
|
55
|
+
const scopeToProject = (p) => ({
|
|
56
|
+
name: p.name,
|
|
57
|
+
markup: p.markup ? (a) => (a.filename?.includes('/node_modules/') ? null : p.markup(a)) : undefined,
|
|
58
|
+
script: p.script ? (a) => (a.filename?.includes('/node_modules/') ? null : p.script(a)) : undefined,
|
|
59
|
+
style: p.style ? (a) => (a.filename?.includes('/node_modules/') ? null : p.style(a)) : undefined
|
|
60
|
+
});
|
|
98
61
|
|
|
99
|
-
|
|
62
|
+
preprocess: [
|
|
63
|
+
...nornsPreprocess().map(scopeToProject),
|
|
64
|
+
scopeToProject(nornsAutoImport({ /* … */ }))
|
|
65
|
+
]
|
|
66
|
+
```
|
|
100
67
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
68
|
+
## Dark mode
|
|
69
|
+
|
|
70
|
+
Toggle via `<html data-theme="dark">` — every component swaps. Persist the choice with a small inline script in `app.html` to avoid FOUC:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<script>
|
|
74
|
+
(function () {
|
|
75
|
+
try {
|
|
76
|
+
var t = localStorage.getItem('theme');
|
|
77
|
+
if (t === 'dark' || (t === null && matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
78
|
+
document.documentElement.dataset.theme = 'dark';
|
|
79
|
+
}
|
|
80
|
+
} catch (_) {}
|
|
81
|
+
})();
|
|
82
|
+
</script>
|
|
109
83
|
```
|
|
110
84
|
|
|
111
|
-
|
|
112
|
-
- `variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link'` (default `'primary'`)
|
|
113
|
-
- `size?: 'sm' | 'md' | 'lg'` (default `'md'`)
|
|
114
|
-
- `loading?: boolean` — replaces leading icon with `.ui-spinner`, sets `disabled` and `aria-busy`
|
|
115
|
-
- `disabled?: boolean`, `type?`, `onclick?`, `class?`, `children`, `leading`, `trailing`
|
|
85
|
+
## Icons
|
|
116
86
|
|
|
117
|
-
|
|
87
|
+
The library ships [`@iconify/svelte`](https://iconify.design/docs/icon-components/svelte/) with the [Lucide](https://lucide.dev) collection bundled. Register the collection once in your app entry so icons render in SSR and the client:
|
|
118
88
|
|
|
119
|
-
|
|
89
|
+
```js
|
|
90
|
+
// src/routes/+layout.svelte (or wherever fits)
|
|
91
|
+
import { addCollection } from '@iconify/svelte';
|
|
92
|
+
import lucide from '@iconify-json/lucide/icons.json';
|
|
93
|
+
addCollection(lucide);
|
|
94
|
+
```
|
|
120
95
|
|
|
121
|
-
|
|
96
|
+
Then use `<Icon>` anywhere:
|
|
122
97
|
|
|
123
98
|
```pug
|
|
124
|
-
|
|
125
|
-
|
|
99
|
+
Icon(name="lucide:check" size="size-5")
|
|
100
|
+
Btn(icon="lucide:save") Save
|
|
126
101
|
```
|
|
127
102
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
#### `<Form>`, `<FieldGroup>`
|
|
103
|
+
To mix in another icon set, install its JSON package (e.g. `@iconify-json/heroicons`) and `addCollection(...)` it the same way.
|
|
131
104
|
|
|
132
|
-
|
|
105
|
+
## Usage
|
|
133
106
|
|
|
134
107
|
```pug
|
|
135
|
-
Form(action="?/save")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Field(label="Bio")
|
|
140
|
-
Textarea(name="bio" rows="3")
|
|
141
|
-
Btn(type="submit") Save
|
|
108
|
+
Form(action="?/save" form!="{form}")
|
|
109
|
+
Field(label="Title" name="title" required)
|
|
110
|
+
Input(name="title" placeholder="title…" required)
|
|
111
|
+
Btn(type="submit" variant="primary" icon="lucide:save") Save
|
|
142
112
|
```
|
|
143
113
|
|
|
144
|
-
|
|
114
|
+
`<Field name="title">` reads its error from the parent `<Form>`'s context map; no per-page boilerplate.
|
|
145
115
|
|
|
146
|
-
|
|
116
|
+
## What's in here
|
|
147
117
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
| `<Radio>` | `bind:group` | `name`, `value`, `required`, `disabled` |
|
|
155
|
-
| `<Switch>` | `bind:checked` | `name`, `value`, `required`, `disabled`. Renders as a styled toggle (`role="switch"`) |
|
|
118
|
+
- **Atoms** (CSS-only `@layer components`): `.btn`, `.input`, `.field`, `.form`, `.checkbox`, `.radio`, `.switch`, `.card`, `.banner`, `.badge`, `.chip`, `.avatar`, `.skeleton`, `.progress`, `.norns-header`, `.hero`, `.stepper`, `.breadcrumbs`, `.pagination`, `.accordion`, `.carousel`, plus variants/sizes.
|
|
119
|
+
- **Forms**: `Btn`, `Form`, `Field`, `FieldGroup`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `Switch`.
|
|
120
|
+
- **Behavior** (Bits UI): `Dialog`, `Sheet`, `Popover`, `Dropdown`, `Tooltip`, `Tabs`, `Accordion`, `Drawer`.
|
|
121
|
+
- **Display**: `Card`, `Surface`, `Banner`, `Badge`, `Chip`, `Avatar`, `Skeleton`, `Progress`, `ProgressCircular`, `Icon`.
|
|
122
|
+
- **Composite**: `Header`, `HeroBanner`, `Stepper`, `Breadcrumbs`, `Pagination`, `Carousel`.
|
|
123
|
+
- **Toast**: `ToastProvider` + `toast()` / `notify()` / `dismiss()` from `@human-synthesis/norns-ui/toast`.
|
|
156
124
|
|
|
157
|
-
|
|
125
|
+
Hand-rolled `.d.ts` shims live under `src/types/`. Override any component by dropping `src/lib/components/<Name>.n` in your project — `nornsAutoImport`'s first-match-wins shadows the library silently.
|
|
158
126
|
|
|
159
127
|
## Theming
|
|
160
128
|
|
|
161
|
-
Tokens
|
|
162
|
-
|
|
163
|
-
Dark mode: toggle via `<html data-theme="dark">`. The library's tokens.css ships dark-theme overrides; Tailwind's existing dark-aware utilities continue to work.
|
|
164
|
-
|
|
165
|
-
## Class merging
|
|
166
|
-
|
|
167
|
-
Every component takes a `class` prop merged via `tailwind-merge` (the `cn` helper):
|
|
168
|
-
|
|
169
|
-
```svelte
|
|
170
|
-
<Btn variant="primary" class="w-full" />
|
|
171
|
-
<Input class="font-mono" />
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
`cn` is exported from `@human-synthesis/norns-ui/cn` if you want to swap in plain `clsx` for smaller bundle:
|
|
175
|
-
|
|
176
|
-
```js
|
|
177
|
-
import { cn } from '@human-synthesis/norns-ui/cn';
|
|
178
|
-
cn('btn', isActive && 'btn-active', extra);
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Override a component
|
|
129
|
+
Tokens are in [`src/styles/tokens.css`](src/styles/tokens.css) as a Tailwind v4 `@theme` block. The library exposes:
|
|
182
130
|
|
|
183
|
-
|
|
131
|
+
- **Color scales** (50–950) for `primary`, `success`, `warning`, `danger`, `info` — all in `oklch` for perceptual uniformity. Use as `bg-primary-500`, `text-danger-700`, etc.
|
|
132
|
+
- **Role tokens** that atoms reference (and that you should reach for in your own components): `--color-fg`, `--color-fg-muted`, `--color-fg-subtle`, `--color-bg`, `--color-bg-muted`, `--color-bg-subtle`, `--color-bg-elevated`, `--color-border`, `--color-border-strong`, `--color-ring`. These swap automatically on dark mode.
|
|
133
|
+
- **Feedback tokens**: `--color-{success,warning,danger,info}-{bg,fg,border}` for tinted surfaces.
|
|
134
|
+
- **Typography tokens**: `--font-sans`, `--font-mono`.
|
|
184
135
|
|
|
185
|
-
|
|
186
|
-
src/lib/components/Btn.n ← your override wins
|
|
187
|
-
node_modules/@human-synthesis/norns-ui/src/components/Btn.n ← fallback
|
|
188
|
-
```
|
|
136
|
+
Override by re-declaring `@theme { … }` in your `app.css` after the library import.
|
|
189
137
|
|
|
190
138
|
## License
|
|
191
139
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@human-synthesis/norns-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "UI library for the Norns ecosystem — Pug + Civet components on Tailwind v4.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Daniel Teodoroiu (https://humansynthesis.ai)",
|
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
".": "./src/index.js",
|
|
21
21
|
"./auto-import": "./src/auto-import.js",
|
|
22
22
|
"./cn": "./src/lib/cn.js",
|
|
23
|
+
"./toast": "./src/lib/toast.svelte.js",
|
|
23
24
|
"./styles": "./src/styles/index.css",
|
|
24
25
|
"./styles/tokens": "./src/styles/tokens.css",
|
|
25
26
|
"./styles/atoms": "./src/styles/atoms.css",
|
|
26
27
|
"./components/*": "./src/components/*",
|
|
28
|
+
"./motion": "./src/motion/index.js",
|
|
29
|
+
"./motion/*": "./src/motion/*",
|
|
27
30
|
"./package.json": "./package.json"
|
|
28
31
|
},
|
|
29
32
|
"peerDependencies": {
|
|
@@ -34,19 +37,24 @@
|
|
|
34
37
|
},
|
|
35
38
|
"peerDependenciesMeta": {
|
|
36
39
|
"@human-synthesis/norns": {
|
|
37
|
-
|
|
40
|
+
"optional": false
|
|
41
|
+
},
|
|
42
|
+
"@human-synthesis/norns-core": {
|
|
43
|
+
"optional": false
|
|
44
|
+
}
|
|
38
45
|
},
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@fontsource-variable/outfit": "^5.2.8",
|
|
48
|
+
"@iconify-json/lucide": "^1.2.105",
|
|
49
|
+
"@iconify/svelte": "^5.2.1",
|
|
50
|
+
"bits-ui": "^2.18.1",
|
|
51
|
+
"motion": "^12.38.0",
|
|
52
|
+
"tailwind-merge": "^2.5.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
}
|
|
52
60
|
}
|
package/src/auto-import.js
CHANGED
|
@@ -12,44 +12,118 @@
|
|
|
12
12
|
* plugins: [
|
|
13
13
|
* nornsCivetPlugin(),
|
|
14
14
|
* nornsAutoImport({
|
|
15
|
-
* componentDirs: ['src/lib/components'],
|
|
16
|
-
* components: ui.components
|
|
15
|
+
* componentDirs: ['src/lib/components'],
|
|
16
|
+
* components: ui.components,
|
|
17
|
+
* helpers: [...DEFAULT_HELPERS, ...ui.helpers]
|
|
17
18
|
* }),
|
|
18
19
|
* sveltekit()
|
|
19
20
|
* ]
|
|
20
21
|
*
|
|
21
|
-
* Components are imported via bare specifiers so the consumer's
|
|
22
|
-
* `node_modules/@human-synthesis/norns-ui/...` is the resolution target.
|
|
23
|
-
*
|
|
24
22
|
* @typedef {Object} UIPreset
|
|
25
|
-
* @property {Record<string, string>} components
|
|
26
|
-
* @property {Array<{ from: string, imports: string[] }>} helpers
|
|
23
|
+
* @property {Record<string, string>} components
|
|
24
|
+
* @property {Array<{ from: string, imports: string[] }>} helpers
|
|
27
25
|
*
|
|
28
26
|
* @returns {UIPreset}
|
|
29
27
|
*/
|
|
30
28
|
export function presetUI() {
|
|
29
|
+
const c = (name) => `@human-synthesis/norns-ui/components/${name}.n`;
|
|
31
30
|
return {
|
|
32
31
|
components: {
|
|
33
32
|
// Phase 1
|
|
34
|
-
Btn: '
|
|
33
|
+
Btn: c('Btn'),
|
|
35
34
|
|
|
36
35
|
// Phase 2 — forms tier
|
|
37
|
-
Field: '
|
|
38
|
-
FieldGroup: '
|
|
39
|
-
Form: '
|
|
40
|
-
Input: '
|
|
41
|
-
Textarea: '
|
|
42
|
-
Select: '
|
|
43
|
-
Checkbox: '
|
|
44
|
-
Radio: '
|
|
45
|
-
Switch: '
|
|
46
|
-
|
|
47
|
-
// Phase 3
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
Field: c('Field'),
|
|
37
|
+
FieldGroup: c('FieldGroup'),
|
|
38
|
+
Form: c('Form'),
|
|
39
|
+
Input: c('Input'),
|
|
40
|
+
Textarea: c('Textarea'),
|
|
41
|
+
Select: c('Select'),
|
|
42
|
+
Checkbox: c('Checkbox'),
|
|
43
|
+
Radio: c('Radio'),
|
|
44
|
+
Switch: c('Switch'),
|
|
45
|
+
|
|
46
|
+
// Phase 3 — behavior tier (Bits UI)
|
|
47
|
+
Dialog: c('Dialog'),
|
|
48
|
+
Sheet: c('Sheet'),
|
|
49
|
+
Popover: c('Popover'),
|
|
50
|
+
Dropdown: c('Dropdown'),
|
|
51
|
+
Tooltip: c('Tooltip'),
|
|
52
|
+
Tabs: c('Tabs'),
|
|
53
|
+
ToastProvider: c('ToastProvider'),
|
|
54
|
+
|
|
55
|
+
// 0.0.4 — display layer
|
|
56
|
+
Icon: c('Icon'),
|
|
57
|
+
Card: c('Card'),
|
|
58
|
+
Surface: c('Surface'),
|
|
59
|
+
Banner: c('Banner'),
|
|
60
|
+
Badge: c('Badge'),
|
|
61
|
+
Chip: c('Chip'),
|
|
62
|
+
Avatar: c('Avatar'),
|
|
63
|
+
Skeleton: c('Skeleton'),
|
|
64
|
+
Progress: c('Progress'),
|
|
65
|
+
ProgressCircular: c('ProgressCircular'),
|
|
66
|
+
|
|
67
|
+
// 0.0.4 — composite layer
|
|
68
|
+
Header: c('Header'),
|
|
69
|
+
Drawer: c('Drawer'),
|
|
70
|
+
HeroBanner: c('HeroBanner'),
|
|
71
|
+
Stepper: c('Stepper'),
|
|
72
|
+
Breadcrumbs: c('Breadcrumbs'),
|
|
73
|
+
Pagination: c('Pagination'),
|
|
74
|
+
Accordion: c('Accordion'),
|
|
75
|
+
Carousel: c('Carousel'),
|
|
76
|
+
Tree: c('Tree'),
|
|
77
|
+
|
|
78
|
+
// 0.0.4 — wave A: small/standalone
|
|
79
|
+
Separator: c('Separator'),
|
|
80
|
+
Toolbar: c('Toolbar'),
|
|
81
|
+
ButtonGroup: c('ButtonGroup'),
|
|
82
|
+
ToggleButton: c('ToggleButton'),
|
|
83
|
+
ToggleButtonGroup: c('ToggleButtonGroup'),
|
|
84
|
+
Collapsible: c('Collapsible'),
|
|
85
|
+
NumberInput: c('NumberInput'),
|
|
86
|
+
OtpField: c('OtpField'),
|
|
87
|
+
TagsInput: c('TagsInput'),
|
|
88
|
+
Image: c('Image'),
|
|
89
|
+
Audio: c('Audio'),
|
|
90
|
+
Video: c('Video'),
|
|
91
|
+
Timeline: c('Timeline'),
|
|
92
|
+
|
|
93
|
+
// 0.0.4 — wave B: medium / Bits-UI-backed
|
|
94
|
+
Autocomplete: c('Autocomplete'),
|
|
95
|
+
MultiSelect: c('MultiSelect'),
|
|
96
|
+
ContextMenu: c('ContextMenu'),
|
|
97
|
+
RichTooltip: c('RichTooltip'),
|
|
98
|
+
PreviewCard: c('PreviewCard'),
|
|
99
|
+
ScrollArea: c('ScrollArea'),
|
|
100
|
+
ColorPicker: c('ColorPicker'),
|
|
101
|
+
Uploader: c('Uploader'),
|
|
102
|
+
HierarchicalMenu: c('HierarchicalMenu'),
|
|
103
|
+
MegaMenu: c('MegaMenu'),
|
|
104
|
+
|
|
105
|
+
// 0.0.4 — wave C: pickers + table
|
|
106
|
+
DatePicker: c('DatePicker'),
|
|
107
|
+
DateRangePicker: c('DateRangePicker'),
|
|
108
|
+
TimePicker: c('TimePicker'),
|
|
109
|
+
Calendar: c('Calendar'),
|
|
110
|
+
DataTable: c('DataTable'),
|
|
111
|
+
|
|
112
|
+
// 0.0.5 — CSS-only motion components
|
|
113
|
+
AvatarGroup: c('AvatarGroup'),
|
|
114
|
+
GradientText: c('GradientText'),
|
|
115
|
+
CopyButton: c('CopyButton'),
|
|
116
|
+
ThemeToggler: c('ThemeToggler'),
|
|
117
|
+
ShinyButton: c('ShinyButton'),
|
|
118
|
+
RippleButton: c('RippleButton')
|
|
119
|
+
// Motion-driven components live in `@human-synthesis/norns-ui/motion`
|
|
120
|
+
// and are NOT registered here (opt-in import only).
|
|
50
121
|
},
|
|
51
122
|
helpers: [
|
|
52
|
-
|
|
123
|
+
{
|
|
124
|
+
from: '@human-synthesis/norns-ui/toast',
|
|
125
|
+
imports: ['toast', 'notify', 'dismiss']
|
|
126
|
+
}
|
|
53
127
|
]
|
|
54
128
|
};
|
|
55
129
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
AccordionRoot(
|
|
2
|
+
type!="{multiple ? 'multiple' : 'single'}"
|
|
3
|
+
bind:value
|
|
4
|
+
class!="{classes}"
|
|
5
|
+
)
|
|
6
|
+
+each('items as item')
|
|
7
|
+
AccordionItem.accordion-item(value!="{item.value}")
|
|
8
|
+
AccordionHeader
|
|
9
|
+
AccordionTrigger.accordion-trigger
|
|
10
|
+
span
|
|
11
|
+
| {item.title}
|
|
12
|
+
.accordion-chevron
|
|
13
|
+
Icon(name="lucide:chevron-down" size="size-4")
|
|
14
|
+
AccordionContent.accordion-content
|
|
15
|
+
| {@render item.content?.()}
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { Accordion } from 'bits-ui'
|
|
19
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
20
|
+
import Icon from './Icon.n'
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
Root: AccordionRoot
|
|
24
|
+
Item: AccordionItem
|
|
25
|
+
Header: AccordionHeader
|
|
26
|
+
Trigger: AccordionTrigger
|
|
27
|
+
Content: AccordionContent
|
|
28
|
+
} := Accordion
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
items = []
|
|
32
|
+
multiple = false
|
|
33
|
+
value = $bindable(undefined)
|
|
34
|
+
class: extra = ''
|
|
35
|
+
} .= $props()
|
|
36
|
+
|
|
37
|
+
classes := cn 'accordion', extra
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
audio(src!="{src}" controls!="{controls}" autoplay!="{autoplay}" loop!="{looping}" muted!="{muted}" preload!="{preload}" class!="{classes}")
|
|
2
|
+
+each('sources as source')
|
|
3
|
+
source(src!="{source.src}" type!="{source.type}")
|
|
4
|
+
| {fallback}
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
8
|
+
|
|
9
|
+
// Note: HTML attribute `loop` becomes `looping` here because `loop` is a
|
|
10
|
+
// Civet reserved word.
|
|
11
|
+
{
|
|
12
|
+
src
|
|
13
|
+
sources = []
|
|
14
|
+
controls = true
|
|
15
|
+
autoplay = false
|
|
16
|
+
loop: looping = false
|
|
17
|
+
muted = false
|
|
18
|
+
preload = 'metadata'
|
|
19
|
+
fallback = 'Your browser does not support the audio element.'
|
|
20
|
+
class: extra = ''
|
|
21
|
+
} := $props()
|
|
22
|
+
|
|
23
|
+
classes := cn 'audio-player w-full', extra
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
ComboboxRoot(bind:value bind:open type="single")
|
|
2
|
+
.combobox-input-wrap
|
|
3
|
+
ComboboxInput.input.combobox-input(
|
|
4
|
+
placeholder!="{placeholder}"
|
|
5
|
+
disabled!="{disabled}"
|
|
6
|
+
oninput!="{(e) => term = e.currentTarget.value}"
|
|
7
|
+
id!="{resolvedId}"
|
|
8
|
+
aria-invalid!="{hasError ? 'true' : undefined}"
|
|
9
|
+
)
|
|
10
|
+
ComboboxTrigger.combobox-trigger(aria-label="Toggle options")
|
|
11
|
+
Icon(name="lucide:chevrons-up-down" size="size-4")
|
|
12
|
+
ComboboxPortal
|
|
13
|
+
ComboboxContent.dropdown-content
|
|
14
|
+
ComboboxViewport
|
|
15
|
+
+each('filtered as item')
|
|
16
|
+
ComboboxItem.dropdown-item(value!="{item.value}" label!="{item.label}")
|
|
17
|
+
Icon(name="lucide:check" size="size-3.5" class="opacity-0 data-[selected]:opacity-100")
|
|
18
|
+
span {item.label}
|
|
19
|
+
+if('filtered.length === 0')
|
|
20
|
+
.dropdown-item(class="text-fg-muted")
|
|
21
|
+
| No matches
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
import { Combobox } from 'bits-ui'
|
|
25
|
+
import Icon from './Icon.n'
|
|
26
|
+
import { getContext } from 'svelte'
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
Root: ComboboxRoot
|
|
30
|
+
Input: ComboboxInput
|
|
31
|
+
Trigger: ComboboxTrigger
|
|
32
|
+
Portal: ComboboxPortal
|
|
33
|
+
Content: ComboboxContent
|
|
34
|
+
Viewport: ComboboxViewport
|
|
35
|
+
Item: ComboboxItem
|
|
36
|
+
} := Combobox
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
items = []
|
|
40
|
+
value = $bindable(undefined)
|
|
41
|
+
open = $bindable(false)
|
|
42
|
+
placeholder = 'Search…'
|
|
43
|
+
disabled = false
|
|
44
|
+
name
|
|
45
|
+
id: providedId
|
|
46
|
+
error: explicitError = false
|
|
47
|
+
} .= $props()
|
|
48
|
+
|
|
49
|
+
field := getContext 'norns-ui:field'
|
|
50
|
+
resolvedId := providedId ?? field?.id
|
|
51
|
+
hasError := explicitError || field?.hasError || false
|
|
52
|
+
|
|
53
|
+
term .= $state ''
|
|
54
|
+
|
|
55
|
+
filtered := $derived.by =>
|
|
56
|
+
return items unless term
|
|
57
|
+
t := term.toLowerCase()
|
|
58
|
+
items.filter (it) => it.label.toLowerCase().includes(t)
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
span(class!="{classes}")
|
|
2
|
+
+if('src')
|
|
3
|
+
img(src!="{src}" alt!="{name ?? ''}" loading="lazy")
|
|
4
|
+
+if('!src && name')
|
|
5
|
+
| {initials}
|
|
6
|
+
+if('!src && !name')
|
|
7
|
+
Icon(name="lucide:user" size="size-1/2" class="opacity-50")
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
11
|
+
import Icon from './Icon.n'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
src
|
|
15
|
+
name
|
|
16
|
+
size = 'md'
|
|
17
|
+
class: extra = ''
|
|
18
|
+
...rest
|
|
19
|
+
} := $props()
|
|
20
|
+
|
|
21
|
+
initials := $derived.by =>
|
|
22
|
+
return '' unless name
|
|
23
|
+
parts := name.trim().split /\s+/
|
|
24
|
+
`${parts[0][0] ?? ''}${parts[1]?.[0] ?? ''}`.toUpperCase()
|
|
25
|
+
|
|
26
|
+
classes := cn 'avatar', `avatar-${size}`, extra
|
|
27
|
+
</script>
|