@nqmcreative/ui 0.2.3 → 0.4.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 (232) hide show
  1. package/README.md +209 -15
  2. package/cli/index.mjs +328 -112
  3. package/dist/core/color.d.ts +24 -0
  4. package/dist/core/color.js +39 -0
  5. package/dist/core/index.d.ts +8 -1
  6. package/dist/core/index.js +5 -1
  7. package/dist/core/locale.svelte.d.ts +8 -0
  8. package/dist/core/locale.svelte.js +14 -0
  9. package/dist/core/number.d.ts +22 -0
  10. package/dist/core/number.js +33 -0
  11. package/dist/core/pin.d.ts +23 -0
  12. package/dist/core/pin.js +38 -0
  13. package/dist/core/tags.d.ts +37 -0
  14. package/dist/core/tags.js +51 -0
  15. package/dist/core/time.d.ts +30 -0
  16. package/dist/core/time.js +78 -0
  17. package/dist/core/toast.svelte.d.ts +3 -0
  18. package/dist/styles/matte/AccordionItem.svelte +5 -0
  19. package/dist/styles/matte/AccordionItem.svelte.d.ts +2 -0
  20. package/dist/styles/matte/Avatar.svelte +15 -0
  21. package/dist/styles/matte/Avatar.svelte.d.ts +3 -0
  22. package/dist/styles/matte/Badge.svelte +5 -0
  23. package/dist/styles/matte/Badge.svelte.d.ts +2 -0
  24. package/dist/styles/matte/Breadcrumb.svelte +11 -4
  25. package/dist/styles/matte/Breadcrumb.svelte.d.ts +3 -0
  26. package/dist/styles/matte/Button.svelte +58 -7
  27. package/dist/styles/matte/Button.svelte.d.ts +14 -2
  28. package/dist/styles/matte/Card.svelte +6 -1
  29. package/dist/styles/matte/Card.svelte.d.ts +2 -0
  30. package/dist/styles/matte/CheckboxGroup.svelte +6 -0
  31. package/dist/styles/matte/CheckboxGroup.svelte.d.ts +2 -0
  32. package/dist/styles/matte/ColorInput.svelte +110 -0
  33. package/dist/styles/matte/ColorInput.svelte.d.ts +16 -0
  34. package/dist/styles/matte/Combobox.svelte +5 -0
  35. package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
  36. package/dist/styles/matte/CommandPalette.svelte +6 -0
  37. package/dist/styles/matte/CommandPalette.svelte.d.ts +3 -0
  38. package/dist/styles/matte/CurrencyInput.svelte +108 -0
  39. package/dist/styles/matte/CurrencyInput.svelte.d.ts +26 -0
  40. package/dist/styles/matte/Drawer.svelte +5 -0
  41. package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
  42. package/dist/styles/matte/FileInput.svelte +194 -0
  43. package/dist/styles/matte/FileInput.svelte.d.ts +32 -0
  44. package/dist/styles/matte/Footer.svelte +6 -2
  45. package/dist/styles/matte/Footer.svelte.d.ts +2 -0
  46. package/dist/styles/matte/InputAddon.svelte +36 -0
  47. package/dist/styles/matte/InputAddon.svelte.d.ts +13 -0
  48. package/dist/styles/matte/Link.svelte +9 -0
  49. package/dist/styles/matte/Link.svelte.d.ts +4 -0
  50. package/dist/styles/matte/MenuItem.svelte +2 -1
  51. package/dist/styles/matte/Modal.svelte +5 -0
  52. package/dist/styles/matte/Modal.svelte.d.ts +2 -0
  53. package/dist/styles/matte/MultiSelect.svelte +7 -1
  54. package/dist/styles/matte/MultiSelect.svelte.d.ts +3 -0
  55. package/dist/styles/matte/Navbar.svelte +18 -4
  56. package/dist/styles/matte/Navbar.svelte.d.ts +2 -0
  57. package/dist/styles/matte/PinInput.svelte +137 -0
  58. package/dist/styles/matte/PinInput.svelte.d.ts +25 -0
  59. package/dist/styles/matte/PricingCard.svelte +16 -8
  60. package/dist/styles/matte/PricingCard.svelte.d.ts +2 -0
  61. package/dist/styles/matte/RadioGroup.svelte +6 -0
  62. package/dist/styles/matte/RadioGroup.svelte.d.ts +2 -0
  63. package/dist/styles/matte/SearchInput.svelte +87 -0
  64. package/dist/styles/matte/SearchInput.svelte.d.ts +24 -0
  65. package/dist/styles/matte/SegmentedControl.svelte +16 -2
  66. package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
  67. package/dist/styles/matte/Sidebar.svelte +2 -1
  68. package/dist/styles/matte/Stat.svelte +8 -1
  69. package/dist/styles/matte/Stat.svelte.d.ts +3 -0
  70. package/dist/styles/matte/Steps.svelte +7 -0
  71. package/dist/styles/matte/Steps.svelte.d.ts +3 -0
  72. package/dist/styles/matte/Table.svelte +153 -97
  73. package/dist/styles/matte/Table.svelte.d.ts +12 -0
  74. package/dist/styles/matte/Tabs.svelte +5 -0
  75. package/dist/styles/matte/Tabs.svelte.d.ts +3 -0
  76. package/dist/styles/matte/TagsInput.svelte +173 -0
  77. package/dist/styles/matte/TagsInput.svelte.d.ts +29 -0
  78. package/dist/styles/matte/TimeInput.svelte +129 -0
  79. package/dist/styles/matte/TimeInput.svelte.d.ts +21 -0
  80. package/dist/styles/matte/Toaster.svelte +3 -0
  81. package/dist/styles/matte/icon.d.ts +21 -0
  82. package/dist/styles/matte/icon.js +21 -0
  83. package/dist/styles/matte/index.d.ts +8 -2
  84. package/dist/styles/matte/index.js +8 -2
  85. package/dist/styles/paper/AccordionItem.svelte +5 -0
  86. package/dist/styles/paper/AccordionItem.svelte.d.ts +2 -0
  87. package/dist/styles/paper/Avatar.svelte +15 -0
  88. package/dist/styles/paper/Avatar.svelte.d.ts +3 -0
  89. package/dist/styles/paper/Badge.svelte +5 -0
  90. package/dist/styles/paper/Badge.svelte.d.ts +2 -0
  91. package/dist/styles/paper/Breadcrumb.svelte +8 -4
  92. package/dist/styles/paper/Breadcrumb.svelte.d.ts +3 -0
  93. package/dist/styles/paper/Button.svelte +58 -7
  94. package/dist/styles/paper/Button.svelte.d.ts +14 -2
  95. package/dist/styles/paper/Card.svelte +6 -1
  96. package/dist/styles/paper/Card.svelte.d.ts +2 -0
  97. package/dist/styles/paper/CheckboxGroup.svelte +6 -0
  98. package/dist/styles/paper/CheckboxGroup.svelte.d.ts +2 -0
  99. package/dist/styles/paper/ColorInput.svelte +112 -0
  100. package/dist/styles/paper/ColorInput.svelte.d.ts +16 -0
  101. package/dist/styles/paper/Combobox.svelte +5 -0
  102. package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
  103. package/dist/styles/paper/CommandPalette.svelte +6 -0
  104. package/dist/styles/paper/CommandPalette.svelte.d.ts +3 -0
  105. package/dist/styles/paper/CurrencyInput.svelte +108 -0
  106. package/dist/styles/paper/CurrencyInput.svelte.d.ts +26 -0
  107. package/dist/styles/paper/Drawer.svelte +5 -0
  108. package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
  109. package/dist/styles/paper/FileInput.svelte +204 -0
  110. package/dist/styles/paper/FileInput.svelte.d.ts +32 -0
  111. package/dist/styles/paper/Footer.svelte +6 -2
  112. package/dist/styles/paper/Footer.svelte.d.ts +2 -0
  113. package/dist/styles/paper/InputAddon.svelte +36 -0
  114. package/dist/styles/paper/InputAddon.svelte.d.ts +13 -0
  115. package/dist/styles/paper/Link.svelte +9 -0
  116. package/dist/styles/paper/Link.svelte.d.ts +4 -0
  117. package/dist/styles/paper/MenuItem.svelte +2 -1
  118. package/dist/styles/paper/Modal.svelte +5 -0
  119. package/dist/styles/paper/Modal.svelte.d.ts +2 -0
  120. package/dist/styles/paper/MultiSelect.svelte +7 -1
  121. package/dist/styles/paper/MultiSelect.svelte.d.ts +3 -0
  122. package/dist/styles/paper/Navbar.svelte +18 -4
  123. package/dist/styles/paper/Navbar.svelte.d.ts +2 -0
  124. package/dist/styles/paper/PinInput.svelte +137 -0
  125. package/dist/styles/paper/PinInput.svelte.d.ts +25 -0
  126. package/dist/styles/paper/PricingCard.svelte +23 -15
  127. package/dist/styles/paper/PricingCard.svelte.d.ts +2 -0
  128. package/dist/styles/paper/RadioGroup.svelte +6 -0
  129. package/dist/styles/paper/RadioGroup.svelte.d.ts +2 -0
  130. package/dist/styles/paper/SearchInput.svelte +92 -0
  131. package/dist/styles/paper/SearchInput.svelte.d.ts +24 -0
  132. package/dist/styles/paper/SegmentedControl.svelte +16 -2
  133. package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
  134. package/dist/styles/paper/Sidebar.svelte +2 -1
  135. package/dist/styles/paper/Stat.svelte +8 -1
  136. package/dist/styles/paper/Stat.svelte.d.ts +3 -0
  137. package/dist/styles/paper/Steps.svelte +7 -0
  138. package/dist/styles/paper/Steps.svelte.d.ts +3 -0
  139. package/dist/styles/paper/Table.svelte +153 -97
  140. package/dist/styles/paper/Table.svelte.d.ts +12 -0
  141. package/dist/styles/paper/Tabs.svelte +5 -0
  142. package/dist/styles/paper/Tabs.svelte.d.ts +3 -0
  143. package/dist/styles/paper/TagsInput.svelte +178 -0
  144. package/dist/styles/paper/TagsInput.svelte.d.ts +29 -0
  145. package/dist/styles/paper/TimeInput.svelte +155 -0
  146. package/dist/styles/paper/TimeInput.svelte.d.ts +21 -0
  147. package/dist/styles/paper/Toaster.svelte +3 -0
  148. package/dist/styles/paper/icon.d.ts +21 -0
  149. package/dist/styles/paper/icon.js +21 -0
  150. package/dist/styles/paper/index.d.ts +8 -2
  151. package/dist/styles/paper/index.js +8 -2
  152. package/dist/styles/sprout/AccordionItem.svelte +5 -0
  153. package/dist/styles/sprout/AccordionItem.svelte.d.ts +2 -0
  154. package/dist/styles/sprout/Avatar.svelte +15 -0
  155. package/dist/styles/sprout/Avatar.svelte.d.ts +3 -0
  156. package/dist/styles/sprout/Badge.svelte +5 -0
  157. package/dist/styles/sprout/Badge.svelte.d.ts +2 -0
  158. package/dist/styles/sprout/Breadcrumb.svelte +8 -4
  159. package/dist/styles/sprout/Breadcrumb.svelte.d.ts +3 -0
  160. package/dist/styles/sprout/Button.svelte +58 -7
  161. package/dist/styles/sprout/Button.svelte.d.ts +14 -2
  162. package/dist/styles/sprout/Card.svelte +6 -1
  163. package/dist/styles/sprout/Card.svelte.d.ts +2 -0
  164. package/dist/styles/sprout/CheckboxGroup.svelte +6 -0
  165. package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +2 -0
  166. package/dist/styles/sprout/ColorInput.svelte +112 -0
  167. package/dist/styles/sprout/ColorInput.svelte.d.ts +16 -0
  168. package/dist/styles/sprout/Combobox.svelte +5 -0
  169. package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
  170. package/dist/styles/sprout/CommandPalette.svelte +6 -0
  171. package/dist/styles/sprout/CommandPalette.svelte.d.ts +3 -0
  172. package/dist/styles/sprout/CurrencyInput.svelte +108 -0
  173. package/dist/styles/sprout/CurrencyInput.svelte.d.ts +26 -0
  174. package/dist/styles/sprout/Drawer.svelte +5 -0
  175. package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
  176. package/dist/styles/sprout/FileInput.svelte +205 -0
  177. package/dist/styles/sprout/FileInput.svelte.d.ts +32 -0
  178. package/dist/styles/sprout/Footer.svelte +6 -2
  179. package/dist/styles/sprout/Footer.svelte.d.ts +2 -0
  180. package/dist/styles/sprout/InputAddon.svelte +37 -0
  181. package/dist/styles/sprout/InputAddon.svelte.d.ts +13 -0
  182. package/dist/styles/sprout/Link.svelte +9 -0
  183. package/dist/styles/sprout/Link.svelte.d.ts +4 -0
  184. package/dist/styles/sprout/MenuItem.svelte +2 -1
  185. package/dist/styles/sprout/Modal.svelte +5 -0
  186. package/dist/styles/sprout/Modal.svelte.d.ts +2 -0
  187. package/dist/styles/sprout/MultiSelect.svelte +7 -1
  188. package/dist/styles/sprout/MultiSelect.svelte.d.ts +3 -0
  189. package/dist/styles/sprout/Navbar.svelte +18 -4
  190. package/dist/styles/sprout/Navbar.svelte.d.ts +2 -0
  191. package/dist/styles/sprout/PinInput.svelte +138 -0
  192. package/dist/styles/sprout/PinInput.svelte.d.ts +25 -0
  193. package/dist/styles/sprout/PricingCard.svelte +23 -15
  194. package/dist/styles/sprout/PricingCard.svelte.d.ts +2 -0
  195. package/dist/styles/sprout/RadioGroup.svelte +6 -0
  196. package/dist/styles/sprout/RadioGroup.svelte.d.ts +2 -0
  197. package/dist/styles/sprout/SearchInput.svelte +92 -0
  198. package/dist/styles/sprout/SearchInput.svelte.d.ts +24 -0
  199. package/dist/styles/sprout/SegmentedControl.svelte +16 -2
  200. package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
  201. package/dist/styles/sprout/Sidebar.svelte +2 -1
  202. package/dist/styles/sprout/Stat.svelte +8 -1
  203. package/dist/styles/sprout/Stat.svelte.d.ts +3 -0
  204. package/dist/styles/sprout/Steps.svelte +7 -0
  205. package/dist/styles/sprout/Steps.svelte.d.ts +3 -0
  206. package/dist/styles/sprout/Table.svelte +153 -97
  207. package/dist/styles/sprout/Table.svelte.d.ts +12 -0
  208. package/dist/styles/sprout/Tabs.svelte +5 -0
  209. package/dist/styles/sprout/Tabs.svelte.d.ts +3 -0
  210. package/dist/styles/sprout/TagsInput.svelte +179 -0
  211. package/dist/styles/sprout/TagsInput.svelte.d.ts +29 -0
  212. package/dist/styles/sprout/TimeInput.svelte +155 -0
  213. package/dist/styles/sprout/TimeInput.svelte.d.ts +21 -0
  214. package/dist/styles/sprout/Toaster.svelte +3 -0
  215. package/dist/styles/sprout/icon.d.ts +21 -0
  216. package/dist/styles/sprout/icon.js +21 -0
  217. package/dist/styles/sprout/index.d.ts +8 -2
  218. package/dist/styles/sprout/index.js +8 -2
  219. package/package.json +98 -26
  220. package/registry.json +143 -35
  221. package/dist/styles/matte/NumberInput.svelte +0 -96
  222. package/dist/styles/matte/NumberInput.svelte.d.ts +0 -15
  223. package/dist/styles/matte/PasswordInput.svelte +0 -95
  224. package/dist/styles/matte/PasswordInput.svelte.d.ts +0 -12
  225. package/dist/styles/paper/NumberInput.svelte +0 -96
  226. package/dist/styles/paper/NumberInput.svelte.d.ts +0 -15
  227. package/dist/styles/paper/PasswordInput.svelte +0 -100
  228. package/dist/styles/paper/PasswordInput.svelte.d.ts +0 -12
  229. package/dist/styles/sprout/NumberInput.svelte +0 -97
  230. package/dist/styles/sprout/NumberInput.svelte.d.ts +0 -15
  231. package/dist/styles/sprout/PasswordInput.svelte +0 -101
  232. package/dist/styles/sprout/PasswordInput.svelte.d.ts +0 -12
@@ -0,0 +1,21 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import type { Tone } from '../../core/tones.js';
3
+ import { type InputSize } from './Input.svelte';
4
+ interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix' | 'min' | 'max' | 'step'> {
5
+ /** Bindable `HH:MM`, or `''` for empty. */
6
+ value?: string;
7
+ /** Inclusive bounds, `HH:MM`. */
8
+ min?: string;
9
+ max?: string;
10
+ /** Minutes one press of a stepper moves, and what typing snaps to. */
11
+ step?: number;
12
+ size?: InputSize;
13
+ tone?: Tone;
14
+ invalid?: boolean;
15
+ /** Hide the steppers and let the field be typed into only. */
16
+ steppers?: boolean;
17
+ class?: string;
18
+ }
19
+ declare const TimeInput: import("svelte").Component<Props, {}, "value">;
20
+ type TimeInput = ReturnType<typeof TimeInput>;
21
+ export default TimeInput;
@@ -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 { toneBorderSoft, toneSurface, 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
 
@@ -57,6 +58,8 @@
57
58
  <span class="mt-0.5 shrink-0 {toneText[item.tone]}">
58
59
  {#if item.loading}
59
60
  <Spinner size="sm" label="" />
61
+ {:else if item.icon}
62
+ <span class={iconMd}>{@render item.icon()}</span>
60
63
  {:else if item.tone === 'success'}
61
64
  <svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
62
65
  <path d="m3 8.5 3.2 3.2L13 5" stroke="currentColor" stroke-width="1.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 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';
@@ -32,6 +30,14 @@ export type { CheckboxOption } from './CheckboxGroup.svelte';
32
30
  export { default as SegmentedControl } from './SegmentedControl.svelte';
33
31
  export type { SegmentOption, SegmentedSize } from './SegmentedControl.svelte';
34
32
  export { default as InputGroup } from './InputGroup.svelte';
33
+ export { default as InputAddon } from './InputAddon.svelte';
34
+ export { default as SearchInput } from './SearchInput.svelte';
35
+ export { default as PinInput } from './PinInput.svelte';
36
+ export { default as TagsInput } from './TagsInput.svelte';
37
+ export { default as CurrencyInput } from './CurrencyInput.svelte';
38
+ export { default as TimeInput } from './TimeInput.svelte';
39
+ export { default as ColorInput } from './ColorInput.svelte';
40
+ export { default as FileInput } from './FileInput.svelte';
35
41
  export { default as Combobox } from './Combobox.svelte';
36
42
  export type { ComboboxOption } from './Combobox.svelte';
37
43
  export { default as MultiSelect } from './MultiSelect.svelte';
@@ -21,13 +21,19 @@ 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';
29
27
  export { default as SegmentedControl } from './SegmentedControl.svelte';
30
28
  export { default as InputGroup } from './InputGroup.svelte';
29
+ export { default as InputAddon } from './InputAddon.svelte';
30
+ export { default as SearchInput } from './SearchInput.svelte';
31
+ export { default as PinInput } from './PinInput.svelte';
32
+ export { default as TagsInput } from './TagsInput.svelte';
33
+ export { default as CurrencyInput } from './CurrencyInput.svelte';
34
+ export { default as TimeInput } from './TimeInput.svelte';
35
+ export { default as ColorInput } from './ColorInput.svelte';
36
+ export { default as FileInput } from './FileInput.svelte';
31
37
  export { default as Combobox } from './Combobox.svelte';
32
38
  export { default as MultiSelect } from './MultiSelect.svelte';
33
39
  export { default as DatePicker } from './DatePicker.svelte';
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { toneText, type Tone } from '../../core/tones.js';
5
+ import { iconMd } from './icon.js';
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDetailsElement> {
7
8
  title: string;
@@ -9,6 +10,8 @@
9
10
  tone?: Tone;
10
11
  /** Short text on the right of the summary row. */
11
12
  meta?: string;
13
+ /** Leading icon on the summary row. */
14
+ icon?: Snippet;
12
15
  children: Snippet;
13
16
  }
14
17
 
@@ -17,6 +20,7 @@
17
20
  open = $bindable(false),
18
21
  tone = 'brand',
19
22
  meta,
23
+ icon,
20
24
  class: className = '',
21
25
  children,
22
26
  ...rest
@@ -27,6 +31,7 @@
27
31
  <summary
28
32
  class="flex cursor-pointer list-none items-center gap-4 px-4 py-3.5 font-sans text-sm font-medium text-text transition-colors duration-150 ease-brand-out select-none hover:bg-bg-alt focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-current [&::-webkit-details-marker]:hidden"
29
33
  >
34
+ {#if icon}<span class={iconMd}>{@render icon()}</span>{/if}
30
35
  <span class="flex-1">{title}</span>
31
36
  {#if meta}<span class="text-[13px] font-normal text-text-muted">{meta}</span>{/if}
32
37
  <svg
@@ -7,6 +7,8 @@ interface Props extends HTMLAttributes<HTMLDetailsElement> {
7
7
  tone?: Tone;
8
8
  /** Short text on the right of the summary row. */
9
9
  meta?: string;
10
+ /** Leading icon on the summary row. */
11
+ icon?: Snippet;
10
12
  children: Snippet;
11
13
  }
12
14
  declare const AccordionItem: import("svelte").Component<Props, {}, "open">;
@@ -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 { toneSoft, type Tone } from '../../core/tones.js';
4
5
 
@@ -13,6 +14,8 @@
13
14
  tone?: Tone;
14
15
  /** Rounded square instead of the default circle. */
15
16
  squared?: boolean;
17
+ /** Drawn instead of the initials when there is no `src` — a user mark. */
18
+ fallback?: Snippet;
16
19
  }
17
20
 
18
21
  let {
@@ -22,6 +25,7 @@
22
25
  size = 'md',
23
26
  tone = 'brand',
24
27
  squared = false,
28
+ fallback,
25
29
  class: className = '',
26
30
  ...rest
27
31
  }: Props = $props();
@@ -34,6 +38,15 @@
34
38
  xl: 'size-16 text-lg'
35
39
  };
36
40
 
41
+ // The mark tracks the initials: about half the circle, whatever the size.
42
+ const glyphs: Record<AvatarSize, string> = {
43
+ xs: 'grid place-items-center *:size-3',
44
+ sm: 'grid place-items-center *:size-4',
45
+ md: 'grid place-items-center *:size-5',
46
+ lg: 'grid place-items-center *:size-6',
47
+ xl: 'grid place-items-center *:size-8'
48
+ };
49
+
37
50
  const initials = $derived(
38
51
  name
39
52
  .trim()
@@ -54,6 +67,8 @@
54
67
  >
55
68
  {#if src}
56
69
  <img {src} alt={alt ?? name} class="size-full object-cover" />
70
+ {:else if fallback}
71
+ <span class={glyphs[size]}>{@render fallback()}</span>
57
72
  {:else}
58
73
  {initials}
59
74
  {/if}
@@ -1,3 +1,4 @@
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 type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
@@ -10,6 +11,8 @@ interface Props extends HTMLAttributes<HTMLSpanElement> {
10
11
  tone?: Tone;
11
12
  /** Rounded square instead of the default circle. */
12
13
  squared?: boolean;
14
+ /** Drawn instead of the initials when there is no `src` — a user mark. */
15
+ fallback?: Snippet;
13
16
  }
14
17
  declare const Avatar: import("svelte").Component<Props, {}, "">;
15
18
  type Avatar = ReturnType<typeof Avatar>;
@@ -9,6 +9,7 @@
9
9
  toneText,
10
10
  type Tone
11
11
  } from '../../core/tones.js';
12
+ import { iconSm } from './icon.js';
12
13
 
13
14
  export type BadgeVariant = 'soft' | 'solid' | 'outline';
14
15
  export type BadgeSize = 'sm' | 'md';
@@ -19,6 +20,8 @@
19
20
  size?: BadgeSize;
20
21
  /** Small filled circle before the label. */
21
22
  dot?: boolean;
23
+ /** Leading icon, 14px — sits where the dot would. */
24
+ icon?: Snippet;
22
25
  children: Snippet;
23
26
  }
24
27
 
@@ -27,6 +30,7 @@
27
30
  variant = 'soft',
28
31
  size = 'md',
29
32
  dot = false,
33
+ icon,
30
34
  class: className = '',
31
35
  children,
32
36
  ...rest
@@ -54,5 +58,6 @@
54
58
  class="size-1.5 shrink-0 rounded-full {variant === 'solid' ? 'bg-current' : toneFill[tone]}"
55
59
  ></span>
56
60
  {/if}
61
+ {#if icon}<span class={iconSm}>{@render icon()}</span>{/if}
57
62
  {@render children()}
58
63
  </span>
@@ -9,6 +9,8 @@ interface Props extends HTMLAttributes<HTMLSpanElement> {
9
9
  size?: BadgeSize;
10
10
  /** Small filled circle before the label. */
11
11
  dot?: boolean;
12
+ /** Leading icon, 14px — sits where the dot would. */
13
+ icon?: Snippet;
12
14
  children: Snippet;
13
15
  }
14
16
  declare const Badge: import("svelte").Component<Props, {}, "">;
@@ -1,11 +1,15 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import type { HTMLAttributes } from 'svelte/elements';
3
4
  import { toneHoverText, type Tone } from '../../core/tones.js';
4
5
  import { useLocale } from '../../core/locale.svelte.js';
6
+ import { iconMd } from './icon.js';
5
7
 
6
8
  export interface BreadcrumbItem {
7
9
  label: string;
8
10
  href?: string;
11
+ /** Leading icon — a home mark on the first crumb, say. */
12
+ icon?: Snippet;
9
13
  }
10
14
 
11
15
  interface Props extends HTMLAttributes<HTMLElement> {
@@ -29,18 +33,18 @@
29
33
  {#if item.href && i < items.length - 1}
30
34
  <a
31
35
  href={item.href}
32
- class="rounded px-1 py-0.5 text-text-muted transition-colors duration-150 ease-brand-out hover:bg-bg-inset {toneHoverText[
36
+ class="inline-flex items-center gap-1.5 rounded px-1 py-0.5 text-text-muted transition-colors duration-150 ease-brand-out hover:bg-bg-inset {toneHoverText[
33
37
  tone
34
38
  ]}"
35
39
  >
36
- {item.label}
40
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}{item.label}
37
41
  </a>
38
42
  {:else}
39
43
  <span
40
- class="px-1 py-0.5 font-medium text-text"
44
+ class="inline-flex items-center gap-1.5 px-1 py-0.5 font-medium text-text"
41
45
  aria-current={i === items.length - 1 ? 'page' : undefined}
42
46
  >
43
- {item.label}
47
+ {#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}{item.label}
44
48
  </span>
45
49
  {/if}
46
50
  </li>
@@ -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 BreadcrumbItem {
4
5
  label: string;
5
6
  href?: string;
7
+ /** Leading icon — a home mark on the first crumb, say. */
8
+ icon?: Snippet;
6
9
  }
7
10
  interface Props extends HTMLAttributes<HTMLElement> {
8
11
  items: BreadcrumbItem[];
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
  import Spinner from './Spinner.svelte';
5
+ import { iconMd, iconSm } from './icon.js';
5
6
  import {
6
7
  focusRing,
7
8
  toneBorderSoft,
@@ -17,7 +18,7 @@
17
18
  export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
18
19
  export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
19
20
 
20
- interface Props extends HTMLButtonAttributes {
21
+ interface Props extends Omit<HTMLButtonAttributes, 'onclick'> {
21
22
  variant?: ButtonVariant;
22
23
  tone?: Tone;
23
24
  size?: ButtonSize;
@@ -28,7 +29,19 @@
28
29
  loading?: boolean;
29
30
  /** Stretch to the container's full width. */
30
31
  block?: boolean;
31
- children: Snippet;
32
+ /** Leading icon. The spinner takes its place while the button is busy. */
33
+ icon?: Snippet;
34
+ /** Trailing icon. */
35
+ iconEnd?: Snippet;
36
+ /** Square button with no label — pass an `aria-label`. */
37
+ iconOnly?: boolean;
38
+ /**
39
+ * Return a promise — a save, a submit, a fetch — and the button spins and
40
+ * locks itself until it settles, without any state in the parent. It
41
+ * unlocks on a rejection too, so catch failures inside the handler.
42
+ */
43
+ onclick?: (event: MouseEvent) => unknown;
44
+ children?: Snippet;
32
45
  }
33
46
 
34
47
  let {
@@ -38,6 +51,10 @@
38
51
  href,
39
52
  loading = false,
40
53
  block = false,
54
+ icon,
55
+ iconEnd,
56
+ iconOnly = false,
57
+ onclick,
41
58
  disabled = false,
42
59
  type = 'button',
43
60
  class: className = '',
@@ -55,6 +72,13 @@
55
72
  xl: 'h-12 px-6 text-base'
56
73
  };
57
74
 
75
+ const squares: Record<ButtonSize, string> = {
76
+ sm: 'size-8',
77
+ md: 'size-10',
78
+ lg: 'size-11',
79
+ xl: 'size-12'
80
+ };
81
+
58
82
  const variantClass = $derived.by(() => {
59
83
  switch (variant) {
60
84
  case 'primary':
@@ -74,7 +98,24 @@
74
98
  });
75
99
 
76
100
  const isLink = $derived(variant === 'link');
77
- const inert = $derived(disabled || loading);
101
+
102
+ /** Set by an `onclick` that returned a promise, on top of the `loading` prop. */
103
+ let pending = $state(false);
104
+ const busy = $derived(loading || pending);
105
+ const inert = $derived(disabled || busy);
106
+
107
+ const iconClass = $derived(size === 'sm' ? iconSm : iconMd);
108
+
109
+ async function handleClick(event: MouseEvent) {
110
+ const result = onclick?.(event);
111
+ // Anything else is a plain handler: nothing to wait for, nothing to lock.
112
+ if (!(result instanceof Promise)) return;
113
+ pending = true;
114
+ // The handler owns its own errors — catch them there to show a message.
115
+ // All the button needs to know is that the work has settled.
116
+ await result.catch(() => {});
117
+ pending = false;
118
+ }
78
119
  </script>
79
120
 
80
121
  <svelte:element
@@ -83,14 +124,24 @@
83
124
  type={href ? undefined : type}
84
125
  disabled={href ? undefined : inert}
85
126
  aria-disabled={href && inert ? 'true' : undefined}
86
- aria-busy={loading ? 'true' : undefined}
127
+ aria-busy={busy ? 'true' : undefined}
87
128
  role={href ? 'button' : undefined}
88
- class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink ? '' : sizes[size]}
129
+ class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink
130
+ ? ''
131
+ : iconOnly
132
+ ? squares[size]
133
+ : sizes[size]}
89
134
  {block ? 'w-full' : ''} {className}"
90
135
  {...rest}
136
+ onclick={handleClick}
91
137
  >
92
- {#if loading}
138
+ {#if busy}
93
139
  <Spinner size={size === 'sm' ? 'xs' : 'sm'} label="" />
140
+ {:else if icon}
141
+ <span class={iconClass} aria-hidden="true">{@render icon()}</span>
142
+ {/if}
143
+ {@render children?.()}
144
+ {#if iconEnd && !iconOnly}
145
+ <span class={iconClass} aria-hidden="true">{@render iconEnd()}</span>
94
146
  {/if}
95
- {@render children()}
96
147
  </svelte:element>
@@ -3,7 +3,7 @@ import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  import { type Tone } from '../../core/tones.js';
4
4
  export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
5
5
  export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
6
- interface Props extends HTMLButtonAttributes {
6
+ interface Props extends Omit<HTMLButtonAttributes, 'onclick'> {
7
7
  variant?: ButtonVariant;
8
8
  tone?: Tone;
9
9
  size?: ButtonSize;
@@ -14,7 +14,19 @@ interface Props extends HTMLButtonAttributes {
14
14
  loading?: boolean;
15
15
  /** Stretch to the container's full width. */
16
16
  block?: boolean;
17
- children: Snippet;
17
+ /** Leading icon. The spinner takes its place while the button is busy. */
18
+ icon?: Snippet;
19
+ /** Trailing icon. */
20
+ iconEnd?: Snippet;
21
+ /** Square button with no label — pass an `aria-label`. */
22
+ iconOnly?: boolean;
23
+ /**
24
+ * Return a promise — a save, a submit, a fetch — and the button spins and
25
+ * locks itself until it settles, without any state in the parent. It
26
+ * unlocks on a rejection too, so catch failures inside the handler.
27
+ */
28
+ onclick?: (event: MouseEvent) => unknown;
29
+ children?: Snippet;
18
30
  }
19
31
  declare const Button: import("svelte").Component<Props, {}, "">;
20
32
  type Button = ReturnType<typeof Button>;
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { toneBorderLeft, toneHoverBorder, toneSurface, type Tone } from '../../core/tones.js';
5
+ import { iconLg } from './icon.js';
5
6
 
6
7
  export type CardVariant = 'outline' | 'filled' | 'tinted';
7
8
 
@@ -15,6 +16,8 @@
15
16
  padded?: boolean;
16
17
  title?: string;
17
18
  eyebrow?: string;
19
+ /** Leading icon above the title. Ignored when you pass `header`. */
20
+ icon?: Snippet;
18
21
  header?: Snippet;
19
22
  footer?: Snippet;
20
23
  children: Snippet;
@@ -28,6 +31,7 @@
28
31
  padded = true,
29
32
  title,
30
33
  eyebrow,
34
+ icon,
31
35
  header,
32
36
  footer,
33
37
  class: className = '',
@@ -51,11 +55,12 @@
51
55
  {className}"
52
56
  {...rest}
53
57
  >
54
- {#if header || title || eyebrow}
58
+ {#if header || title || eyebrow || icon}
55
59
  <div class="flex flex-col gap-1 {padded ? 'px-5 pt-5' : ''}">
56
60
  {#if header}
57
61
  {@render header()}
58
62
  {:else}
63
+ {#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
59
64
  {#if eyebrow}
60
65
  <span class="font-sans text-xs font-semibold text-text-muted">{eyebrow}</span>
61
66
  {/if}
@@ -12,6 +12,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
12
12
  padded?: boolean;
13
13
  title?: string;
14
14
  eyebrow?: string;
15
+ /** Leading icon above the title. Ignored when you pass `header`. */
16
+ icon?: Snippet;
15
17
  header?: Snippet;
16
18
  footer?: Snippet;
17
19
  children: Snippet;
@@ -3,10 +3,13 @@
3
3
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
4
  import Checkbox from './Checkbox.svelte';
5
5
  import { toneBorder, toneSurface, type Tone } from '../../core/tones.js';
6
+ import { iconLg } from './icon.js';
6
7
 
7
8
  export interface CheckboxOption {
8
9
  value: string;
9
10
  label: string;
11
+ /** Leading icon, 20px. Drawn in `boxed` mode only. */
12
+ icon?: Snippet;
10
13
  description?: string;
11
14
  disabled?: boolean;
12
15
  }
@@ -78,6 +81,9 @@
78
81
  : 'border-hairline bg-bg hover:border-hairline-strong'}
79
82
  {disabled ? 'pointer-events-none opacity-50' : ''}"
80
83
  >
84
+ {#if option.icon}
85
+ <span class={iconLg}>{@render option.icon()}</span>
86
+ {/if}
81
87
  <Checkbox
82
88
  {checked}
83
89
  {disabled}
@@ -4,6 +4,8 @@ import { type Tone } from '../../core/tones.js';
4
4
  export interface CheckboxOption {
5
5
  value: string;
6
6
  label: string;
7
+ /** Leading icon, 20px. Drawn in `boxed` mode only. */
8
+ icon?: Snippet;
7
9
  description?: string;
8
10
  disabled?: boolean;
9
11
  }
@@ -0,0 +1,112 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import { normaliseHex } from '../../core/color.js';
4
+ import { useLocale } from '../../core/locale.svelte.js';
5
+ import type { Tone } from '../../core/tones.js';
6
+ import Input, { type InputSize } from './Input.svelte';
7
+
8
+ interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix' | 'type'> {
9
+ /** Bindable `#rrggbb`. Shorthand and alpha are normalised away on blur. */
10
+ value?: string;
11
+ size?: InputSize;
12
+ tone?: Tone;
13
+ invalid?: boolean;
14
+ /** Fixed choices under the field. Empty hides the row. */
15
+ swatches?: string[];
16
+ class?: string;
17
+ }
18
+
19
+ let {
20
+ value = $bindable('#000000'),
21
+ size = 'md',
22
+ tone = 'brand',
23
+ invalid = false,
24
+ disabled = false,
25
+ swatches = [],
26
+ class: className = '',
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ const t = useLocale();
31
+
32
+ // The text can be mid-typing (`#a2`) while the swatch needs a real colour,
33
+ // so the swatch follows the last value that parsed rather than the field.
34
+ let text = $state('');
35
+ let editing = $state(false);
36
+
37
+ $effect(() => {
38
+ if (!editing) text = value;
39
+ });
40
+
41
+ const resolved = $derived(normaliseHex(text) ?? normaliseHex(value) ?? '#000000');
42
+
43
+ // Same reason as CurrencyInput: at input time the bound `text` is one
44
+ // keystroke behind, so the element is the only current source.
45
+ function onText(event: Event & { currentTarget: HTMLInputElement }) {
46
+ const hex = normaliseHex(event.currentTarget.value);
47
+ if (hex) value = hex;
48
+ }
49
+
50
+ function settle() {
51
+ editing = false;
52
+ value = normaliseHex(text) ?? value;
53
+ text = value;
54
+ }
55
+
56
+ function choose(hex: string) {
57
+ value = normaliseHex(hex) ?? value;
58
+ text = value;
59
+ }
60
+ </script>
61
+
62
+ <div class="flex w-full flex-col gap-2 {className}">
63
+ <Input
64
+ bind:value={text}
65
+ {size}
66
+ {tone}
67
+ {invalid}
68
+ {disabled}
69
+ spellcheck="false"
70
+ autocapitalize="none"
71
+ placeholder="#000000"
72
+ oninput={onText}
73
+ onfocus={() => (editing = true)}
74
+ onblur={settle}
75
+ class="[&_input]:uppercase [&_input]:tabular-nums"
76
+ {...rest}
77
+ >
78
+ {#snippet prefix()}
79
+ <span
80
+ class="relative grid size-5 place-items-center overflow-hidden rounded border border-hairline-strong"
81
+ >
82
+ <span class="absolute inset-0" style="background-color: {resolved}"></span>
83
+ <input
84
+ type="color"
85
+ value={resolved}
86
+ {disabled}
87
+ aria-label={t.current.pickColour}
88
+ oninput={(event) => choose(event.currentTarget.value)}
89
+ class="absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed"
90
+ />
91
+ </span>
92
+ {/snippet}
93
+ </Input>
94
+
95
+ {#if swatches.length}
96
+ <div class="flex flex-wrap gap-1.5">
97
+ {#each swatches as swatch (swatch)}
98
+ <button
99
+ type="button"
100
+ {disabled}
101
+ onclick={() => choose(swatch)}
102
+ title={swatch}
103
+ aria-label={swatch}
104
+ aria-pressed={resolved === normaliseHex(swatch)}
105
+ class="size-6 rounded-md border transition-transform duration-150 hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current disabled:pointer-events-none disabled:opacity-50
106
+ {resolved === normaliseHex(swatch) ? 'border-text' : 'border-hairline-strong'}"
107
+ style="background-color: {swatch}"
108
+ ></button>
109
+ {/each}
110
+ </div>
111
+ {/if}
112
+ </div>