@nqmcreative/ui 0.2.3 → 0.3.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.
Files changed (170) hide show
  1. package/README.md +15 -15
  2. package/cli/index.mjs +81 -42
  3. package/dist/core/toast.svelte.d.ts +3 -0
  4. package/dist/styles/matte/AccordionItem.svelte +5 -0
  5. package/dist/styles/matte/AccordionItem.svelte.d.ts +2 -0
  6. package/dist/styles/matte/Avatar.svelte +15 -0
  7. package/dist/styles/matte/Avatar.svelte.d.ts +3 -0
  8. package/dist/styles/matte/Badge.svelte +5 -0
  9. package/dist/styles/matte/Badge.svelte.d.ts +2 -0
  10. package/dist/styles/matte/Breadcrumb.svelte +11 -4
  11. package/dist/styles/matte/Breadcrumb.svelte.d.ts +3 -0
  12. package/dist/styles/matte/Button.svelte +58 -7
  13. package/dist/styles/matte/Button.svelte.d.ts +14 -2
  14. package/dist/styles/matte/Card.svelte +6 -1
  15. package/dist/styles/matte/Card.svelte.d.ts +2 -0
  16. package/dist/styles/matte/CheckboxGroup.svelte +6 -0
  17. package/dist/styles/matte/CheckboxGroup.svelte.d.ts +2 -0
  18. package/dist/styles/matte/Combobox.svelte +5 -0
  19. package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
  20. package/dist/styles/matte/CommandPalette.svelte +6 -0
  21. package/dist/styles/matte/CommandPalette.svelte.d.ts +3 -0
  22. package/dist/styles/matte/Drawer.svelte +5 -0
  23. package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
  24. package/dist/styles/matte/Footer.svelte +6 -2
  25. package/dist/styles/matte/Footer.svelte.d.ts +2 -0
  26. package/dist/styles/matte/Link.svelte +9 -0
  27. package/dist/styles/matte/Link.svelte.d.ts +4 -0
  28. package/dist/styles/matte/MenuItem.svelte +2 -1
  29. package/dist/styles/matte/Modal.svelte +5 -0
  30. package/dist/styles/matte/Modal.svelte.d.ts +2 -0
  31. package/dist/styles/matte/MultiSelect.svelte +7 -1
  32. package/dist/styles/matte/MultiSelect.svelte.d.ts +3 -0
  33. package/dist/styles/matte/Navbar.svelte +18 -4
  34. package/dist/styles/matte/Navbar.svelte.d.ts +2 -0
  35. package/dist/styles/matte/PricingCard.svelte +16 -8
  36. package/dist/styles/matte/PricingCard.svelte.d.ts +2 -0
  37. package/dist/styles/matte/RadioGroup.svelte +6 -0
  38. package/dist/styles/matte/RadioGroup.svelte.d.ts +2 -0
  39. package/dist/styles/matte/SegmentedControl.svelte +16 -2
  40. package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
  41. package/dist/styles/matte/Sidebar.svelte +2 -1
  42. package/dist/styles/matte/Stat.svelte +8 -1
  43. package/dist/styles/matte/Stat.svelte.d.ts +3 -0
  44. package/dist/styles/matte/Steps.svelte +7 -0
  45. package/dist/styles/matte/Steps.svelte.d.ts +3 -0
  46. package/dist/styles/matte/Table.svelte +153 -97
  47. package/dist/styles/matte/Table.svelte.d.ts +12 -0
  48. package/dist/styles/matte/Tabs.svelte +5 -0
  49. package/dist/styles/matte/Tabs.svelte.d.ts +3 -0
  50. package/dist/styles/matte/Toaster.svelte +3 -0
  51. package/dist/styles/matte/icon.d.ts +21 -0
  52. package/dist/styles/matte/icon.js +21 -0
  53. package/dist/styles/matte/index.d.ts +0 -2
  54. package/dist/styles/matte/index.js +0 -2
  55. package/dist/styles/paper/AccordionItem.svelte +5 -0
  56. package/dist/styles/paper/AccordionItem.svelte.d.ts +2 -0
  57. package/dist/styles/paper/Avatar.svelte +15 -0
  58. package/dist/styles/paper/Avatar.svelte.d.ts +3 -0
  59. package/dist/styles/paper/Badge.svelte +5 -0
  60. package/dist/styles/paper/Badge.svelte.d.ts +2 -0
  61. package/dist/styles/paper/Breadcrumb.svelte +8 -4
  62. package/dist/styles/paper/Breadcrumb.svelte.d.ts +3 -0
  63. package/dist/styles/paper/Button.svelte +58 -7
  64. package/dist/styles/paper/Button.svelte.d.ts +14 -2
  65. package/dist/styles/paper/Card.svelte +6 -1
  66. package/dist/styles/paper/Card.svelte.d.ts +2 -0
  67. package/dist/styles/paper/CheckboxGroup.svelte +6 -0
  68. package/dist/styles/paper/CheckboxGroup.svelte.d.ts +2 -0
  69. package/dist/styles/paper/Combobox.svelte +5 -0
  70. package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
  71. package/dist/styles/paper/CommandPalette.svelte +6 -0
  72. package/dist/styles/paper/CommandPalette.svelte.d.ts +3 -0
  73. package/dist/styles/paper/Drawer.svelte +5 -0
  74. package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
  75. package/dist/styles/paper/Footer.svelte +6 -2
  76. package/dist/styles/paper/Footer.svelte.d.ts +2 -0
  77. package/dist/styles/paper/Link.svelte +9 -0
  78. package/dist/styles/paper/Link.svelte.d.ts +4 -0
  79. package/dist/styles/paper/MenuItem.svelte +2 -1
  80. package/dist/styles/paper/Modal.svelte +5 -0
  81. package/dist/styles/paper/Modal.svelte.d.ts +2 -0
  82. package/dist/styles/paper/MultiSelect.svelte +7 -1
  83. package/dist/styles/paper/MultiSelect.svelte.d.ts +3 -0
  84. package/dist/styles/paper/Navbar.svelte +18 -4
  85. package/dist/styles/paper/Navbar.svelte.d.ts +2 -0
  86. package/dist/styles/paper/PricingCard.svelte +23 -15
  87. package/dist/styles/paper/PricingCard.svelte.d.ts +2 -0
  88. package/dist/styles/paper/RadioGroup.svelte +6 -0
  89. package/dist/styles/paper/RadioGroup.svelte.d.ts +2 -0
  90. package/dist/styles/paper/SegmentedControl.svelte +16 -2
  91. package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
  92. package/dist/styles/paper/Sidebar.svelte +2 -1
  93. package/dist/styles/paper/Stat.svelte +8 -1
  94. package/dist/styles/paper/Stat.svelte.d.ts +3 -0
  95. package/dist/styles/paper/Steps.svelte +7 -0
  96. package/dist/styles/paper/Steps.svelte.d.ts +3 -0
  97. package/dist/styles/paper/Table.svelte +153 -97
  98. package/dist/styles/paper/Table.svelte.d.ts +12 -0
  99. package/dist/styles/paper/Tabs.svelte +5 -0
  100. package/dist/styles/paper/Tabs.svelte.d.ts +3 -0
  101. package/dist/styles/paper/Toaster.svelte +3 -0
  102. package/dist/styles/paper/icon.d.ts +21 -0
  103. package/dist/styles/paper/icon.js +21 -0
  104. package/dist/styles/paper/index.d.ts +0 -2
  105. package/dist/styles/paper/index.js +0 -2
  106. package/dist/styles/sprout/AccordionItem.svelte +5 -0
  107. package/dist/styles/sprout/AccordionItem.svelte.d.ts +2 -0
  108. package/dist/styles/sprout/Avatar.svelte +15 -0
  109. package/dist/styles/sprout/Avatar.svelte.d.ts +3 -0
  110. package/dist/styles/sprout/Badge.svelte +5 -0
  111. package/dist/styles/sprout/Badge.svelte.d.ts +2 -0
  112. package/dist/styles/sprout/Breadcrumb.svelte +8 -4
  113. package/dist/styles/sprout/Breadcrumb.svelte.d.ts +3 -0
  114. package/dist/styles/sprout/Button.svelte +58 -7
  115. package/dist/styles/sprout/Button.svelte.d.ts +14 -2
  116. package/dist/styles/sprout/Card.svelte +6 -1
  117. package/dist/styles/sprout/Card.svelte.d.ts +2 -0
  118. package/dist/styles/sprout/CheckboxGroup.svelte +6 -0
  119. package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +2 -0
  120. package/dist/styles/sprout/Combobox.svelte +5 -0
  121. package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
  122. package/dist/styles/sprout/CommandPalette.svelte +6 -0
  123. package/dist/styles/sprout/CommandPalette.svelte.d.ts +3 -0
  124. package/dist/styles/sprout/Drawer.svelte +5 -0
  125. package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
  126. package/dist/styles/sprout/Footer.svelte +6 -2
  127. package/dist/styles/sprout/Footer.svelte.d.ts +2 -0
  128. package/dist/styles/sprout/Link.svelte +9 -0
  129. package/dist/styles/sprout/Link.svelte.d.ts +4 -0
  130. package/dist/styles/sprout/MenuItem.svelte +2 -1
  131. package/dist/styles/sprout/Modal.svelte +5 -0
  132. package/dist/styles/sprout/Modal.svelte.d.ts +2 -0
  133. package/dist/styles/sprout/MultiSelect.svelte +7 -1
  134. package/dist/styles/sprout/MultiSelect.svelte.d.ts +3 -0
  135. package/dist/styles/sprout/Navbar.svelte +18 -4
  136. package/dist/styles/sprout/Navbar.svelte.d.ts +2 -0
  137. package/dist/styles/sprout/PricingCard.svelte +23 -15
  138. package/dist/styles/sprout/PricingCard.svelte.d.ts +2 -0
  139. package/dist/styles/sprout/RadioGroup.svelte +6 -0
  140. package/dist/styles/sprout/RadioGroup.svelte.d.ts +2 -0
  141. package/dist/styles/sprout/SegmentedControl.svelte +16 -2
  142. package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
  143. package/dist/styles/sprout/Sidebar.svelte +2 -1
  144. package/dist/styles/sprout/Stat.svelte +8 -1
  145. package/dist/styles/sprout/Stat.svelte.d.ts +3 -0
  146. package/dist/styles/sprout/Steps.svelte +7 -0
  147. package/dist/styles/sprout/Steps.svelte.d.ts +3 -0
  148. package/dist/styles/sprout/Table.svelte +153 -97
  149. package/dist/styles/sprout/Table.svelte.d.ts +12 -0
  150. package/dist/styles/sprout/Tabs.svelte +5 -0
  151. package/dist/styles/sprout/Tabs.svelte.d.ts +3 -0
  152. package/dist/styles/sprout/Toaster.svelte +3 -0
  153. package/dist/styles/sprout/icon.d.ts +21 -0
  154. package/dist/styles/sprout/icon.js +21 -0
  155. package/dist/styles/sprout/index.d.ts +0 -2
  156. package/dist/styles/sprout/index.js +0 -2
  157. package/package.json +2 -26
  158. package/registry.json +1 -35
  159. package/dist/styles/matte/NumberInput.svelte +0 -96
  160. package/dist/styles/matte/NumberInput.svelte.d.ts +0 -15
  161. package/dist/styles/matte/PasswordInput.svelte +0 -95
  162. package/dist/styles/matte/PasswordInput.svelte.d.ts +0 -12
  163. package/dist/styles/paper/NumberInput.svelte +0 -96
  164. package/dist/styles/paper/NumberInput.svelte.d.ts +0 -15
  165. package/dist/styles/paper/PasswordInput.svelte +0 -100
  166. package/dist/styles/paper/PasswordInput.svelte.d.ts +0 -12
  167. package/dist/styles/sprout/NumberInput.svelte +0 -97
  168. package/dist/styles/sprout/NumberInput.svelte.d.ts +0 -15
  169. package/dist/styles/sprout/PasswordInput.svelte +0 -101
  170. package/dist/styles/sprout/PasswordInput.svelte.d.ts +0 -12
@@ -33,6 +33,16 @@
33
33
  /** Header stays put while the body scrolls — pair with a `max-h-*` class. */
34
34
  stickyHeader?: boolean;
35
35
 
36
+ /** Bar above the table — a title, a search field, a filter row. */
37
+ header?: Snippet;
38
+ /** Bar below the table — pagination, a total, a summary line. */
39
+ footer?: Snippet;
40
+ /**
41
+ * Shown in place of `header` while rows are ticked, after the count and the
42
+ * clear button. Passing it is what turns the bulk bar on.
43
+ */
44
+ bulkActions?: Snippet<[{ selected: string[]; clear: () => void }]>;
45
+
36
46
  /** Bindable. Set it to sort; leave `sortable` columns to drive it. */
37
47
  sort?: TableSort | null;
38
48
  /** Sort the rows here rather than refetching them server-side. Default true. */
@@ -58,6 +68,9 @@
58
68
  hoverable = true,
59
69
  compact = false,
60
70
  stickyHeader = false,
71
+ header,
72
+ footer,
73
+ bulkActions,
61
74
  sort = $bindable(null),
62
75
  sortLocally = true,
63
76
  onsort,
@@ -107,112 +120,155 @@
107
120
  selected = toggleKey(selected, keyOf(row));
108
121
  onselect?.(selected);
109
122
  }
123
+
124
+ function clearSelection() {
125
+ selected = [];
126
+ onselect?.(selected);
127
+ }
128
+
129
+ /** The bulk bar takes the header's place, so only one of the two is up. */
130
+ const bulkBar = $derived(selectable && bulkActions !== undefined && selected.length > 0);
110
131
  </script>
111
132
 
112
133
  <div
113
- class="w-full overflow-auto rounded-[20px] border border-hairline bg-bg {edge} {className}"
134
+ class="flex w-full flex-col overflow-hidden rounded-[20px] border border-hairline bg-bg {edge} {className}"
114
135
  {...rest}
115
136
  >
116
- <table class="w-full border-collapse font-sans text-sm">
117
- {#if caption}
118
- <caption
119
- class="border-b border-hairline px-4 py-3 text-left font-sans text-[13px] text-text-muted"
137
+ {#if bulkBar}
138
+ <div
139
+ class="flex flex-wrap items-center gap-3 border-b border-hairline bg-brand-light px-4 py-3"
140
+ >
141
+ <span class="font-sans text-[13px] font-semibold text-text">
142
+ {selected.length}
143
+ {t.current.selected}
144
+ </span>
145
+ <button
146
+ type="button"
147
+ onclick={clearSelection}
148
+ class="rounded-full font-sans text-[13px] font-medium text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
120
149
  >
121
- {caption}
122
- </caption>
123
- {/if}
124
- <thead class={stickyHeader ? 'sticky top-0 z-10' : ''}>
125
- <tr class="border-b border-hairline bg-bg-alt">
126
- {#if selectable}
127
- <th scope="col" class="w-10 {pad}">
128
- <Checkbox
129
- checked={allSelected}
130
- indeterminate={someSelected}
131
- {tone}
132
- aria-label={t.current.selectAllRows}
133
- onchange={toggleAll}
134
- />
135
- </th>
136
- {/if}
137
- {#each columns as column (column.key)}
138
- <th
139
- scope="col"
140
- aria-sort={sort?.key === column.key
141
- ? sort.direction === 'asc'
142
- ? 'ascending'
143
- : 'descending'
144
- : undefined}
145
- class="text-[13px] font-semibold text-text-muted {pad} {aligns[
146
- column.align ?? 'left'
147
- ]} {column.class ?? ''}"
148
- >
149
- {#if column.sortable}
150
- <button
151
- type="button"
152
- onclick={() => toggleSort(column)}
153
- class="inline-flex items-center gap-1.5 rounded transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current
150
+ {t.current.clear}
151
+ </button>
152
+ <div class="ml-auto flex flex-wrap items-center gap-2">
153
+ {@render bulkActions?.({ selected, clear: clearSelection })}
154
+ </div>
155
+ </div>
156
+ {:else if header}
157
+ <div
158
+ class="flex flex-wrap items-center gap-3 border-b border-hairline px-4 py-3 font-sans text-[13px] text-text-secondary"
159
+ >
160
+ {@render header()}
161
+ </div>
162
+ {/if}
163
+ <div class="min-h-0 w-full flex-1 overflow-auto">
164
+ <table class="w-full border-collapse font-sans text-sm">
165
+ {#if caption}
166
+ <caption
167
+ class="border-b border-hairline px-4 py-3 text-left font-sans text-[13px] text-text-muted"
168
+ >
169
+ {caption}
170
+ </caption>
171
+ {/if}
172
+ <thead class={stickyHeader ? 'sticky top-0 z-10' : ''}>
173
+ <tr class="border-b border-hairline bg-bg-alt">
174
+ {#if selectable}
175
+ <th scope="col" class="w-10 {pad}">
176
+ <Checkbox
177
+ checked={allSelected}
178
+ indeterminate={someSelected}
179
+ {tone}
180
+ aria-label={t.current.selectAllRows}
181
+ onchange={toggleAll}
182
+ />
183
+ </th>
184
+ {/if}
185
+ {#each columns as column (column.key)}
186
+ <th
187
+ scope="col"
188
+ aria-sort={sort?.key === column.key
189
+ ? sort.direction === 'asc'
190
+ ? 'ascending'
191
+ : 'descending'
192
+ : undefined}
193
+ class="text-[13px] font-semibold text-text-muted {pad} {aligns[
194
+ column.align ?? 'left'
195
+ ]} {column.class ?? ''}"
196
+ >
197
+ {#if column.sortable}
198
+ <button
199
+ type="button"
200
+ onclick={() => toggleSort(column)}
201
+ class="inline-flex items-center gap-1.5 rounded transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current
154
202
  {sort?.key === column.key ? 'text-text' : ''}"
155
- >
203
+ >
204
+ {column.label}
205
+ <span class="text-[9px] leading-none" aria-hidden="true">
206
+ {#if sort?.key === column.key}
207
+ {sort.direction === 'asc' ? '▲' : '▼'}
208
+ {:else}
209
+
210
+ {/if}
211
+ </span>
212
+ </button>
213
+ {:else}
156
214
  {column.label}
157
- <span class="text-[9px] leading-none" aria-hidden="true">
158
- {#if sort?.key === column.key}
159
- {sort.direction === 'asc' ? '▲' : '▼'}
160
- {:else}
161
-
162
- {/if}
163
- </span>
164
- </button>
165
- {:else}
166
- {column.label}
167
- {/if}
168
- </th>
169
- {/each}
170
- </tr>
171
- </thead>
172
- <tbody>
173
- {#if sorted.length === 0}
174
- <tr>
175
- <td
176
- colspan={columns.length + (selectable ? 1 : 0)}
177
- class="px-4 py-10 text-center text-sm text-text-muted"
178
- >
179
- {#if empty}{@render empty()}{:else}{t.current.noData}{/if}
180
- </td>
215
+ {/if}
216
+ </th>
217
+ {/each}
181
218
  </tr>
182
- {:else}
183
- {#each sorted as row, i (selectable ? keyOf(row) : i)}
184
- {@const isSelected = selectable && selected.includes(keyOf(row))}
185
- <tr
186
- aria-selected={selectable ? isSelected : undefined}
187
- class="border-b border-hairline last:border-b-0
219
+ </thead>
220
+ <tbody>
221
+ {#if sorted.length === 0}
222
+ <tr>
223
+ <td
224
+ colspan={columns.length + (selectable ? 1 : 0)}
225
+ class="px-4 py-10 text-center text-sm text-text-muted"
226
+ >
227
+ {#if empty}{@render empty()}{:else}{t.current.noData}{/if}
228
+ </td>
229
+ </tr>
230
+ {:else}
231
+ {#each sorted as row, i (selectable ? keyOf(row) : i)}
232
+ {@const isSelected = selectable && selected.includes(keyOf(row))}
233
+ <tr
234
+ aria-selected={selectable ? isSelected : undefined}
235
+ class="border-b border-hairline last:border-b-0
188
236
  {isSelected ? 'bg-brand-light' : striped && i % 2 === 1 ? 'bg-bg-alt' : ''}
189
237
  {hoverable ? 'transition-colors duration-150 hover:bg-bg-alt' : ''}"
190
- >
191
- {#if selectable}
192
- <td class={pad}>
193
- <Checkbox
194
- checked={isSelected}
195
- {tone}
196
- aria-label={t.current.selectRow}
197
- onchange={() => toggleRow(row)}
198
- />
199
- </td>
200
- {/if}
201
- {#each columns as column (column.key)}
202
- <td
203
- class="text-text-secondary {pad} {aligns[column.align ?? 'left']} {column.class ??
204
- ''}"
205
- >
206
- {#if cell}
207
- {@render cell({ row, column, value: row[column.key] })}
208
- {:else}
209
- {row[column.key] ?? ''}
210
- {/if}
211
- </td>
212
- {/each}
213
- </tr>
214
- {/each}
215
- {/if}
216
- </tbody>
217
- </table>
238
+ >
239
+ {#if selectable}
240
+ <td class={pad}>
241
+ <Checkbox
242
+ checked={isSelected}
243
+ {tone}
244
+ aria-label={t.current.selectRow}
245
+ onchange={() => toggleRow(row)}
246
+ />
247
+ </td>
248
+ {/if}
249
+ {#each columns as column (column.key)}
250
+ <td
251
+ class="text-text-secondary {pad} {aligns[column.align ?? 'left']} {column.class ??
252
+ ''}"
253
+ >
254
+ {#if cell}
255
+ {@render cell({ row, column, value: row[column.key] })}
256
+ {:else}
257
+ {row[column.key] ?? ''}
258
+ {/if}
259
+ </td>
260
+ {/each}
261
+ </tr>
262
+ {/each}
263
+ {/if}
264
+ </tbody>
265
+ </table>
266
+ </div>
267
+ {#if footer}
268
+ <div
269
+ class="flex flex-wrap items-center gap-3 border-t border-hairline px-4 py-3 font-sans text-[13px] text-text-secondary"
270
+ >
271
+ {@render footer()}
272
+ </div>
273
+ {/if}
218
274
  </div>
@@ -29,6 +29,18 @@ declare function $$render<T extends Record<string, unknown>>(): {
29
29
  compact?: boolean;
30
30
  /** Header stays put while the body scrolls — pair with a `max-h-*` class. */
31
31
  stickyHeader?: boolean;
32
+ /** Bar above the table — a title, a search field, a filter row. */
33
+ header?: Snippet;
34
+ /** Bar below the table — pagination, a total, a summary line. */
35
+ footer?: Snippet;
36
+ /**
37
+ * Shown in place of `header` while rows are ticked, after the count and the
38
+ * clear button. Passing it is what turns the bulk bar on.
39
+ */
40
+ bulkActions?: Snippet<[{
41
+ selected: string[];
42
+ clear: () => void;
43
+ }]>;
32
44
  /** Bindable. Set it to sort; leave `sortable` columns to drive it. */
33
45
  sort?: TableSort | null;
34
46
  /** Sort the rows here rather than refetching them server-side. Default true. */
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import type { HTMLAttributes } from 'svelte/elements';
3
4
  import { edge } from './lift.js';
4
5
  import {
@@ -9,10 +10,13 @@
9
10
  toneText,
10
11
  type Tone
11
12
  } from '../../core/tones.js';
13
+ import { iconMd } from './icon.js';
12
14
 
13
15
  export interface TabItem {
14
16
  value: string;
15
17
  label: string;
18
+ /** Leading icon, 16px. */
19
+ icon?: Snippet;
16
20
  disabled?: boolean;
17
21
  /** Small count/label rendered after the tab title. */
18
22
  badge?: string | number;
@@ -90,6 +94,7 @@
90
94
  onclick={() => select(item)}
91
95
  class={tabClass(value === item.value)}
92
96
  >
97
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
93
98
  {item.label}
94
99
  {#if item.badge !== undefined}
95
100
  <span
@@ -1,8 +1,11 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { HTMLAttributes } from 'svelte/elements';
2
3
  import { type Tone } from '../../core/tones.js';
3
4
  export interface TabItem {
4
5
  value: string;
5
6
  label: string;
7
+ /** Leading icon, 16px. */
8
+ icon?: Snippet;
6
9
  disabled?: boolean;
7
10
  /** Small count/label rendered after the tab title. */
8
11
  badge?: string | number;
@@ -4,6 +4,7 @@
4
4
  import { portal } from '../../core/actions/dismissable.js';
5
5
  import { toast } from '../../core/toast.svelte.js';
6
6
  import { toneText } from '../../core/tones.js';
7
+ import { iconMd } from './icon.js';
7
8
  import Spinner from './Spinner.svelte';
8
9
  import { useLocale } from '../../core/locale.svelte.js';
9
10
  import { soft } from './lift.js';
@@ -62,6 +63,8 @@
62
63
  <span class="mt-0.5 shrink-0 {toneText[item.tone]}">
63
64
  {#if item.loading}
64
65
  <Spinner size="sm" label="" />
66
+ {:else if item.icon}
67
+ <span class={iconMd}>{@render item.icon()}</span>
65
68
  {:else if item.tone === 'success'}
66
69
  <svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
67
70
  <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5" />
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The box a consumer-supplied icon renders into.
3
+ *
4
+ * Every icon in the library is a `Snippet` the app writes — the package ships
5
+ * no icon set and no runtime dependency — so the component has to size the
6
+ * glyph rather than trust it. The `*:` variant hits the snippet's own element,
7
+ * which is what keeps a bare `<svg viewBox="0 0 16 16">` from laying out at its
8
+ * intrinsic 300×150.
9
+ *
10
+ * Kept per style rather than in `core/`: an app only `@source`s the style
11
+ * folder it installed, so a class string living outside it would never be
12
+ * generated by Tailwind.
13
+ */
14
+ /** 14px — menu rows, chips, anything sitting on one line of small text. */
15
+ export declare const iconSm = "grid size-3.5 shrink-0 place-items-center *:size-3.5";
16
+ /** 16px — the default: nav items, tabs, buttons, list rows. */
17
+ export declare const iconMd = "grid size-4 shrink-0 place-items-center *:size-4";
18
+ /** 20px — headers, stats, anything sitting beside a heading. */
19
+ export declare const iconLg = "grid size-5 shrink-0 place-items-center *:size-5";
20
+ /** 24px — dialog and empty-state marks. */
21
+ export declare const iconXl = "grid size-6 shrink-0 place-items-center *:size-6";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The box a consumer-supplied icon renders into.
3
+ *
4
+ * Every icon in the library is a `Snippet` the app writes — the package ships
5
+ * no icon set and no runtime dependency — so the component has to size the
6
+ * glyph rather than trust it. The `*:` variant hits the snippet's own element,
7
+ * which is what keeps a bare `<svg viewBox="0 0 16 16">` from laying out at its
8
+ * intrinsic 300×150.
9
+ *
10
+ * Kept per style rather than in `core/`: an app only `@source`s the style
11
+ * folder it installed, so a class string living outside it would never be
12
+ * generated by Tailwind.
13
+ */
14
+ /** 14px — menu rows, chips, anything sitting on one line of small text. */
15
+ export const iconSm = 'grid size-3.5 shrink-0 place-items-center *:size-3.5';
16
+ /** 16px — the default: nav items, tabs, buttons, list rows. */
17
+ export const iconMd = 'grid size-4 shrink-0 place-items-center *:size-4';
18
+ /** 20px — headers, stats, anything sitting beside a heading. */
19
+ export const iconLg = 'grid size-5 shrink-0 place-items-center *:size-5';
20
+ /** 24px — dialog and empty-state marks. */
21
+ export const iconXl = 'grid size-6 shrink-0 place-items-center *:size-6';
@@ -22,8 +22,6 @@ export { default as Switch } from './Switch.svelte';
22
22
  export type { SwitchSize } from './Switch.svelte';
23
23
  export { default as Field } from './Field.svelte';
24
24
  export { default as Label } from './Label.svelte';
25
- export { default as PasswordInput } from './PasswordInput.svelte';
26
- export { default as NumberInput } from './NumberInput.svelte';
27
25
  export { default as Slider } from './Slider.svelte';
28
26
  export { default as RadioGroup } from './RadioGroup.svelte';
29
27
  export type { RadioOption } from './RadioGroup.svelte';
@@ -21,8 +21,6 @@ export { default as Radio } from './Radio.svelte';
21
21
  export { default as Switch } from './Switch.svelte';
22
22
  export { default as Field } from './Field.svelte';
23
23
  export { default as Label } from './Label.svelte';
24
- export { default as PasswordInput } from './PasswordInput.svelte';
25
- export { default as NumberInput } from './NumberInput.svelte';
26
24
  export { default as Slider } from './Slider.svelte';
27
25
  export { default as RadioGroup } from './RadioGroup.svelte';
28
26
  export { default as CheckboxGroup } from './CheckboxGroup.svelte';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nqmcreative/ui",
3
- "version": "0.2.3",
4
- "description": "Svelte 5 + Tailwind CSS v4 component library — three styles (matte, paper, sprout), 65 components each, one shared behaviour core. No runtime dependencies.",
3
+ "version": "0.3.0",
4
+ "description": "Svelte 5 + Tailwind CSS v4 component library — three styles (matte, paper, sprout), 63 components each, one shared behaviour core. No runtime dependencies.",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
7
7
  "build": "vite build && svelte-kit sync && svelte-package && node scripts/strip-dist-tests.mjs && node scripts/generate-index.mjs && node scripts/generate-exports.mjs && node scripts/generate-registry.mjs && node scripts/generate-scoped-css.mjs && node scripts/generate-demos.mjs && publint",
@@ -135,14 +135,6 @@
135
135
  "types": "./dist/styles/matte/Label.svelte.d.ts",
136
136
  "svelte": "./dist/styles/matte/Label.svelte"
137
137
  },
138
- "./matte/password-input": {
139
- "types": "./dist/styles/matte/PasswordInput.svelte.d.ts",
140
- "svelte": "./dist/styles/matte/PasswordInput.svelte"
141
- },
142
- "./matte/number-input": {
143
- "types": "./dist/styles/matte/NumberInput.svelte.d.ts",
144
- "svelte": "./dist/styles/matte/NumberInput.svelte"
145
- },
146
138
  "./matte/slider": {
147
139
  "types": "./dist/styles/matte/Slider.svelte.d.ts",
148
140
  "svelte": "./dist/styles/matte/Slider.svelte"
@@ -401,14 +393,6 @@
401
393
  "types": "./dist/styles/paper/Label.svelte.d.ts",
402
394
  "svelte": "./dist/styles/paper/Label.svelte"
403
395
  },
404
- "./paper/password-input": {
405
- "types": "./dist/styles/paper/PasswordInput.svelte.d.ts",
406
- "svelte": "./dist/styles/paper/PasswordInput.svelte"
407
- },
408
- "./paper/number-input": {
409
- "types": "./dist/styles/paper/NumberInput.svelte.d.ts",
410
- "svelte": "./dist/styles/paper/NumberInput.svelte"
411
- },
412
396
  "./paper/slider": {
413
397
  "types": "./dist/styles/paper/Slider.svelte.d.ts",
414
398
  "svelte": "./dist/styles/paper/Slider.svelte"
@@ -667,14 +651,6 @@
667
651
  "types": "./dist/styles/sprout/Label.svelte.d.ts",
668
652
  "svelte": "./dist/styles/sprout/Label.svelte"
669
653
  },
670
- "./sprout/password-input": {
671
- "types": "./dist/styles/sprout/PasswordInput.svelte.d.ts",
672
- "svelte": "./dist/styles/sprout/PasswordInput.svelte"
673
- },
674
- "./sprout/number-input": {
675
- "types": "./dist/styles/sprout/NumberInput.svelte.d.ts",
676
- "svelte": "./dist/styles/sprout/NumberInput.svelte"
677
- },
678
654
  "./sprout/slider": {
679
655
  "types": "./dist/styles/sprout/Slider.svelte.d.ts",
680
656
  "svelte": "./dist/styles/sprout/Slider.svelte"
package/registry.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nqmcreative/ui",
3
3
  "homepage": "https://github.com/mukhsamr/nqmcreative-ui",
4
- "count": 65,
4
+ "count": 63,
5
5
  "demoStyle": "matte",
6
6
  "styles": [
7
7
  {
@@ -190,40 +190,6 @@
190
190
  "uses": [],
191
191
  "modules": []
192
192
  },
193
- {
194
- "name": "PasswordInput",
195
- "slug": "password-input",
196
- "category": "forms",
197
- "description": "Password field with a visibility toggle and a strength meter.",
198
- "subpaths": {
199
- "matte": "@nqmcreative/ui/matte/password-input",
200
- "paper": "@nqmcreative/ui/paper/password-input",
201
- "sprout": "@nqmcreative/ui/sprout/password-input"
202
- },
203
- "uses": [],
204
- "modules": [
205
- "locale",
206
- "password",
207
- "tones"
208
- ]
209
- },
210
- {
211
- "name": "NumberInput",
212
- "slug": "number-input",
213
- "category": "forms",
214
- "description": "Number field with steppers, clamping and decimal handling.",
215
- "subpaths": {
216
- "matte": "@nqmcreative/ui/matte/number-input",
217
- "paper": "@nqmcreative/ui/paper/number-input",
218
- "sprout": "@nqmcreative/ui/sprout/number-input"
219
- },
220
- "uses": [],
221
- "modules": [
222
- "locale",
223
- "number",
224
- "tones"
225
- ]
226
- },
227
193
  {
228
194
  "name": "Slider",
229
195
  "slug": "slider",
@@ -1,96 +0,0 @@
1
- <script lang="ts">
2
- import type { HTMLInputAttributes } from 'svelte/elements';
3
- import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
4
- import { useLocale } from '../../core/locale.svelte.js';
5
- import { clampToStep } from '../../core/number.js';
6
-
7
- interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
8
- value?: number;
9
- min?: number;
10
- max?: number;
11
- step?: number;
12
- tone?: Tone;
13
- invalid?: boolean;
14
- /** Short unit rendered inside the field, e.g. `'px'` or `'%'`. */
15
- unit?: string;
16
- }
17
-
18
- let {
19
- value = $bindable(0),
20
- min = -Infinity,
21
- max = Infinity,
22
- step = 1,
23
- tone = 'brand',
24
- invalid = false,
25
- unit,
26
- disabled = false,
27
- class: className = '',
28
- ...rest
29
- }: Props = $props();
30
-
31
- const t = useLocale();
32
-
33
- const clamp = (next: number) => clampToStep(next, min, max, step);
34
-
35
- function nudge(direction: 1 | -1) {
36
- value = clamp(value + direction * step);
37
- }
38
-
39
- function onKeydown(event: KeyboardEvent) {
40
- if (event.key === 'ArrowUp') {
41
- event.preventDefault();
42
- nudge(1);
43
- } else if (event.key === 'ArrowDown') {
44
- event.preventDefault();
45
- nudge(-1);
46
- }
47
- }
48
-
49
- const stepper =
50
- 'flex size-8 shrink-0 items-center justify-center text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current';
51
- </script>
52
-
53
- <span
54
- class="inline-flex w-full items-center border bg-bg pr-1 pl-3.5 transition-colors duration-150 ease-brand-out
55
- {invalid ? 'border-danger' : `border-hairline ${toneFocusWithinBorder[tone]}`}
56
- {disabled ? 'pointer-events-none opacity-50' : ''} {className}"
57
- >
58
- <input
59
- type="number"
60
- {min}
61
- {max}
62
- {step}
63
- {disabled}
64
- {value}
65
- oninput={(event) => (value = clamp(event.currentTarget.valueAsNumber))}
66
- onkeydown={onKeydown}
67
- aria-invalid={invalid ? 'true' : undefined}
68
- class="h-10 w-full min-w-0 [appearance:textfield] bg-transparent font-sans text-[15px] text-text tabular-nums placeholder:text-text-muted focus:outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
69
- {...rest}
70
- />
71
- {#if unit}
72
- <span class="shrink-0 px-2 font-mono text-xs text-text-muted">{unit}</span>
73
- {/if}
74
- <button
75
- type="button"
76
- class={stepper}
77
- onclick={() => nudge(-1)}
78
- disabled={disabled || value <= min}
79
- aria-label={t.current.decrease}
80
- >
81
- <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
82
- <path d="M2.5 7h9" stroke="currentColor" stroke-width="1.5" />
83
- </svg>
84
- </button>
85
- <button
86
- type="button"
87
- class={stepper}
88
- onclick={() => nudge(1)}
89
- disabled={disabled || value >= max}
90
- aria-label={t.current.increase}
91
- >
92
- <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
93
- <path d="M7 2.5v9M2.5 7h9" stroke="currentColor" stroke-width="1.5" />
94
- </svg>
95
- </button>
96
- </span>
@@ -1,15 +0,0 @@
1
- import type { HTMLInputAttributes } from 'svelte/elements';
2
- import { type Tone } from '../../core/tones.js';
3
- interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
4
- value?: number;
5
- min?: number;
6
- max?: number;
7
- step?: number;
8
- tone?: Tone;
9
- invalid?: boolean;
10
- /** Short unit rendered inside the field, e.g. `'px'` or `'%'`. */
11
- unit?: string;
12
- }
13
- declare const NumberInput: import("svelte").Component<Props, {}, "value">;
14
- type NumberInput = ReturnType<typeof NumberInput>;
15
- export default NumberInput;