@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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -129
  3. package/package.json +23 -15
  4. package/src/auto-import.js +96 -22
  5. package/src/components/Accordion.n +38 -0
  6. package/src/components/Audio.n +24 -0
  7. package/src/components/Autocomplete.n +59 -0
  8. package/src/components/Avatar.n +27 -0
  9. package/src/components/AvatarGroup.n +25 -0
  10. package/src/components/Badge.n +20 -0
  11. package/src/components/Banner.n +25 -0
  12. package/src/components/Breadcrumbs.n +22 -0
  13. package/src/components/Btn.n +29 -8
  14. package/src/components/ButtonGroup.n +14 -0
  15. package/src/components/Calendar.n +153 -0
  16. package/src/components/Card.n +34 -0
  17. package/src/components/Carousel.n +66 -0
  18. package/src/components/Chip.n +27 -0
  19. package/src/components/Collapsible.n +32 -0
  20. package/src/components/ColorPicker.n +62 -0
  21. package/src/components/ContextMenu.n +48 -0
  22. package/src/components/CopyButton.n +50 -0
  23. package/src/components/DataTable.n +61 -0
  24. package/src/components/DatePicker.n +68 -0
  25. package/src/components/DateRangePicker.n +59 -0
  26. package/src/components/Dialog.n +54 -0
  27. package/src/components/Drawer.n +49 -0
  28. package/src/components/Dropdown.n +41 -0
  29. package/src/components/Field.n +29 -20
  30. package/src/components/Form.n +22 -0
  31. package/src/components/GradientText.n +21 -0
  32. package/src/components/Header.n +23 -0
  33. package/src/components/HeroBanner.n +37 -0
  34. package/src/components/HierarchicalMenu.n +38 -0
  35. package/src/components/Icon.n +16 -0
  36. package/src/components/Image.n +31 -0
  37. package/src/components/MegaMenu.n +36 -0
  38. package/src/components/MultiSelect.n +56 -0
  39. package/src/components/NumberInput.n +49 -0
  40. package/src/components/OtpField.n +67 -0
  41. package/src/components/Pagination.n +73 -0
  42. package/src/components/Popover.n +32 -0
  43. package/src/components/PreviewCard.n +30 -0
  44. package/src/components/Progress.n +26 -0
  45. package/src/components/ProgressCircular.n +52 -0
  46. package/src/components/RichTooltip.n +33 -0
  47. package/src/components/RippleButton.n +56 -0
  48. package/src/components/ScrollArea.n +31 -0
  49. package/src/components/Separator.n +12 -0
  50. package/src/components/Sheet.n +55 -0
  51. package/src/components/ShinyButton.n +36 -0
  52. package/src/components/Skeleton.n +27 -0
  53. package/src/components/Stepper.n +35 -0
  54. package/src/components/Surface.n +10 -0
  55. package/src/components/Tabs.n +28 -0
  56. package/src/components/TagsInput.n +66 -0
  57. package/src/components/ThemeToggler.n +43 -0
  58. package/src/components/TimePicker.n +166 -0
  59. package/src/components/Timeline.n +27 -0
  60. package/src/components/ToastProvider.n +16 -0
  61. package/src/components/ToggleButton.n +29 -0
  62. package/src/components/ToggleButtonGroup.n +34 -0
  63. package/src/components/Toolbar.n +15 -0
  64. package/src/components/Tooltip.n +34 -0
  65. package/src/components/Tree.n +57 -0
  66. package/src/components/Uploader.n +88 -0
  67. package/src/components/Video.n +26 -0
  68. package/src/index.js +76 -0
  69. package/src/lib/toast.svelte.js +42 -0
  70. package/src/motion/AnimatedNumber.n +55 -0
  71. package/src/motion/GradientBackground.n +25 -0
  72. package/src/motion/LiquidButton.n +48 -0
  73. package/src/motion/Reveal.n +37 -0
  74. package/src/motion/Sparkles.n +53 -0
  75. package/src/motion/index.js +18 -0
  76. package/src/styles/atoms.css +1794 -71
  77. package/src/styles/tokens.css +171 -40
  78. package/src/types/Accordion.d.ts +17 -0
  79. package/src/types/AnimatedNumber.d.ts +15 -0
  80. package/src/types/Audio.d.ts +18 -0
  81. package/src/types/Autocomplete.d.ts +17 -0
  82. package/src/types/Avatar.d.ts +14 -0
  83. package/src/types/AvatarGroup.d.ts +15 -0
  84. package/src/types/Badge.d.ts +14 -0
  85. package/src/types/Banner.d.ts +15 -0
  86. package/src/types/Breadcrumbs.d.ts +15 -0
  87. package/src/types/Btn.d.ts +10 -0
  88. package/src/types/ButtonGroup.d.ts +10 -0
  89. package/src/types/Calendar.d.ts +20 -0
  90. package/src/types/Card.d.ts +19 -0
  91. package/src/types/Carousel.d.ts +23 -0
  92. package/src/types/Chip.d.ts +12 -0
  93. package/src/types/Collapsible.d.ts +12 -0
  94. package/src/types/ColorPicker.d.ts +15 -0
  95. package/src/types/ContextMenu.d.ts +19 -0
  96. package/src/types/CopyButton.d.ts +16 -0
  97. package/src/types/DataTable.d.ts +27 -0
  98. package/src/types/DatePicker.d.ts +18 -0
  99. package/src/types/DateRangePicker.d.ts +17 -0
  100. package/src/types/Dialog.d.ts +17 -0
  101. package/src/types/Drawer.d.ts +18 -0
  102. package/src/types/Dropdown.d.ts +23 -0
  103. package/src/types/Field.d.ts +14 -3
  104. package/src/types/Form.d.ts +17 -0
  105. package/src/types/GradientBackground.d.ts +14 -0
  106. package/src/types/GradientText.d.ts +19 -0
  107. package/src/types/Header.d.ts +12 -0
  108. package/src/types/HeroBanner.d.ts +17 -0
  109. package/src/types/HierarchicalMenu.d.ts +20 -0
  110. package/src/types/Icon.d.ts +21 -0
  111. package/src/types/Image.d.ts +15 -0
  112. package/src/types/LiquidButton.d.ts +15 -0
  113. package/src/types/MegaMenu.d.ts +29 -0
  114. package/src/types/MultiSelect.d.ts +13 -0
  115. package/src/types/NumberInput.d.ts +17 -0
  116. package/src/types/OtpField.d.ts +13 -0
  117. package/src/types/Pagination.d.ts +13 -0
  118. package/src/types/Popover.d.ts +18 -0
  119. package/src/types/PreviewCard.d.ts +16 -0
  120. package/src/types/Progress.d.ts +14 -0
  121. package/src/types/ProgressCircular.d.ts +14 -0
  122. package/src/types/Reveal.d.ts +20 -0
  123. package/src/types/RichTooltip.d.ts +16 -0
  124. package/src/types/RippleButton.d.ts +15 -0
  125. package/src/types/ScrollArea.d.ts +11 -0
  126. package/src/types/Separator.d.ts +9 -0
  127. package/src/types/Sheet.d.ts +20 -0
  128. package/src/types/ShinyButton.d.ts +15 -0
  129. package/src/types/Skeleton.d.ts +13 -0
  130. package/src/types/Sparkles.d.ts +13 -0
  131. package/src/types/Stepper.d.ts +19 -0
  132. package/src/types/Surface.d.ts +10 -0
  133. package/src/types/Tabs.d.ts +17 -0
  134. package/src/types/TagsInput.d.ts +17 -0
  135. package/src/types/ThemeToggler.d.ts +17 -0
  136. package/src/types/TimePicker.d.ts +20 -0
  137. package/src/types/Timeline.d.ts +17 -0
  138. package/src/types/ToastProvider.d.ts +15 -0
  139. package/src/types/ToggleButton.d.ts +15 -0
  140. package/src/types/ToggleButtonGroup.d.ts +20 -0
  141. package/src/types/Toolbar.d.ts +12 -0
  142. package/src/types/Tooltip.d.ts +15 -0
  143. package/src/types/Tree.d.ts +20 -0
  144. package/src/types/Uploader.d.ts +15 -0
  145. 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 [Norns](https://github.com/human-synthesis/norns) ecosystem Pug + Civet components on Tailwind v4.
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` `^0.0.7` — peer (`nornsAutoImport` for the registration flow)
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
- ### 1. Wire `presetUI()` into `nornsAutoImport`
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 defineConfig({
40
- plugins: [
41
- nornsCivetPlugin(),
42
- nornsAutoImport({
43
- componentDirs: ['src/lib/components'],
44
- components: ui.components
45
- }),
46
- tailwindcss(),
47
- sveltekit()
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
- Same shape goes in `svelte.config.js`'s `preprocess` array.
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
- ## Component catalog
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
- - `.btn` + variants (`primary`, `secondary`, `ghost`, `danger`, `link`) + sizes (`sm`, `lg`)
90
- - `.input` / `.textarea` / `.select` + `-err` modifier
91
- - `.checkbox` / `.radio` / `.switch` + `-err` modifier
92
- - `.field` / `.field-label` / `.field-required` / `.field-help` / `.field-error`
93
- - `.field-group` / `.field-group-legend`
94
- - `.form`
95
- - `.ui-spinner`
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
- Wrapped `<button>` with class merging, variant/size props, loading state, and snippet-prop API for icons.
62
+ preprocess: [
63
+ ...nornsPreprocess().map(scopeToProject),
64
+ scopeToProject(nornsAutoImport({ /* … */ }))
65
+ ]
66
+ ```
100
67
 
101
- ```pug
102
- Btn(variant="primary") Save
103
- Btn(variant="secondary" size="sm") Cancel
104
- Btn(loading!="{saving}" type="submit") Save
105
- Btn(variant="danger" onclick!="{remove}")
106
- +snippet('leading')
107
- // icon goes here
108
- | Delete
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
- Props (see [`src/types/Btn.d.ts`](src/types/Btn.d.ts)):
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
- ### Phase 2 forms tier
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
- #### `<Field>`
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
- Wraps a control with label + optional help/error text. Provides `id` and `hasError` to descendant controls via Svelte context — child Inputs auto-pick up the right `id` for `<label for=>` and switch to error styling.
96
+ Then use `<Icon>` anywhere:
122
97
 
123
98
  ```pug
124
- Field(label="Email" required error!="{form?.errors?.email}" help="We'll never share it")
125
- Input(name="email" type="email" required)
99
+ Icon(name="lucide:check" size="size-5")
100
+ Btn(icon="lucide:save") Save
126
101
  ```
127
102
 
128
- Props: `label?`, `help?`, `error?`, `required?`, `id?`, `class?`, `children`.
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
- Styled `<form>` (vertical stack via `.form`) and `<fieldset>` (`.field-group` with optional legend).
105
+ ## Usage
133
106
 
134
107
  ```pug
135
- Form(action="?/save")
136
- FieldGroup(legend="Profile")
137
- Field(label="Name")
138
- Input(name="name" required)
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
- #### Form controls
114
+ `<Field name="title">` reads its error from the parent `<Form>`'s context map; no per-page boilerplate.
145
115
 
146
- All accept `class?` (merged via `cn`), pull `id` + `hasError` from a parent `<Field>` automatically, and surface `aria-invalid` + `aria-describedby` for accessibility:
116
+ ## What's in here
147
117
 
148
- | Component | Bind | Common props |
149
- |---|---|---|
150
- | `<Input>` | `bind:value` | `type`, `size`, `name`, `placeholder`, `required`, `disabled`, `readonly`, `autocomplete` |
151
- | `<Textarea>` | `bind:value` | `name`, `placeholder`, `rows` (default 4), `required`, `disabled`, `readonly` |
152
- | `<Select>` | `bind:value` | `name`, `required`, `disabled`. Children: `<option>` elements |
153
- | `<Checkbox>` | `bind:checked` | `name`, `value`, `required`, `disabled` |
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
- Each has a hand-rolled `.d.ts` shim under `src/types/`.
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 live in [`src/styles/tokens.css`](src/styles/tokens.css) as a Tailwind v4 `@theme` block — colors, radii, sizes. Override by re-declaring `@theme { ... }` in your own `app.css` after the library import. Tailwind v4 merges layered theme blocks.
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
- Drop a same-name file in your `src/lib/components/` and `nornsAutoImport`'s first-match-wins resolves to your version silently. No fork needed.
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.2",
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
- "optional": false
40
+ "optional": false
41
+ },
42
+ "@human-synthesis/norns-core": {
43
+ "optional": false
44
+ }
38
45
  },
39
- "@human-synthesis/norns-core": {
40
- "optional": false
41
- }
42
- },
43
- "dependencies": {
44
- "tailwind-merge": "^2.5.0"
45
- },
46
- "engines": {
47
- "node": ">=18"
48
- },
49
- "publishConfig": {
50
- "access": "public"
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
  }
@@ -12,44 +12,118 @@
12
12
  * plugins: [
13
13
  * nornsCivetPlugin(),
14
14
  * nornsAutoImport({
15
- * componentDirs: ['src/lib/components'], // user folder wins
16
- * components: ui.components // norns-ui as fallback
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 name → bare-specifier import path
26
- * @property {Array<{ from: string, imports: string[] }>} helpers name groups for nornsAutoImport
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: '@human-synthesis/norns-ui/components/Btn.n',
33
+ Btn: c('Btn'),
35
34
 
36
35
  // Phase 2 — forms tier
37
- Field: '@human-synthesis/norns-ui/components/Field.n',
38
- FieldGroup: '@human-synthesis/norns-ui/components/FieldGroup.n',
39
- Form: '@human-synthesis/norns-ui/components/Form.n',
40
- Input: '@human-synthesis/norns-ui/components/Input.n',
41
- Textarea: '@human-synthesis/norns-ui/components/Textarea.n',
42
- Select: '@human-synthesis/norns-ui/components/Select.n',
43
- Checkbox: '@human-synthesis/norns-ui/components/Checkbox.n',
44
- Radio: '@human-synthesis/norns-ui/components/Radio.n',
45
- Switch: '@human-synthesis/norns-ui/components/Switch.n'
46
-
47
- // Phase 3+ adds: Card, Dialog, Sheet, Popover, Dropdown, Tooltip,
48
- // Tabs, Accordion, Toast + ToastProvider, Listbox, Combobox,
49
- // Pagination, Avatar, Badge, Spinner, Progress, Skeleton, Icon
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
- // Phase 3+: { from: '@human-synthesis/norns-ui', imports: ['toast'] }
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>