@dxlbnl/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +94 -0
  2. package/dist/components/cards/Card.stories.svelte +82 -0
  3. package/dist/components/cards/Card.stories.svelte.d.ts +19 -0
  4. package/dist/components/cards/Card.svelte +28 -0
  5. package/dist/components/cards/Card.svelte.d.ts +12 -0
  6. package/dist/components/cards/NoteCard.stories.svelte +94 -0
  7. package/dist/components/cards/NoteCard.stories.svelte.d.ts +19 -0
  8. package/dist/components/cards/NoteCard.svelte +89 -0
  9. package/dist/components/cards/NoteCard.svelte.d.ts +18 -0
  10. package/dist/components/cards/ProductCard.stories.svelte +98 -0
  11. package/dist/components/cards/ProductCard.stories.svelte.d.ts +19 -0
  12. package/dist/components/cards/ProductCard.svelte +150 -0
  13. package/dist/components/cards/ProductCard.svelte.d.ts +22 -0
  14. package/dist/components/cards/ProjectCard.stories.svelte +88 -0
  15. package/dist/components/cards/ProjectCard.stories.svelte.d.ts +19 -0
  16. package/dist/components/cards/ProjectCard.svelte +109 -0
  17. package/dist/components/cards/ProjectCard.svelte.d.ts +20 -0
  18. package/dist/components/cards/index.d.ts +4 -0
  19. package/dist/components/cards/index.js +4 -0
  20. package/dist/components/data/Accordion.stories.svelte +316 -0
  21. package/dist/components/data/Accordion.stories.svelte.d.ts +19 -0
  22. package/dist/components/data/Accordion.svelte +23 -0
  23. package/dist/components/data/Accordion.svelte.d.ts +9 -0
  24. package/dist/components/data/AccordionItem.svelte +112 -0
  25. package/dist/components/data/AccordionItem.svelte.d.ts +11 -0
  26. package/dist/components/data/Table.composition.stories.svelte +67 -0
  27. package/dist/components/data/Table.composition.stories.svelte.d.ts +19 -0
  28. package/dist/components/data/Table.stories.svelte +137 -0
  29. package/dist/components/data/Table.stories.svelte.d.ts +19 -0
  30. package/dist/components/data/Table.svelte +83 -0
  31. package/dist/components/data/Table.svelte.d.ts +14 -0
  32. package/dist/components/data/Tabs.stories.svelte +386 -0
  33. package/dist/components/data/Tabs.stories.svelte.d.ts +19 -0
  34. package/dist/components/data/Tabs.svelte +142 -0
  35. package/dist/components/data/Tabs.svelte.d.ts +19 -0
  36. package/dist/components/data/index.d.ts +4 -0
  37. package/dist/components/data/index.js +4 -0
  38. package/dist/components/feedback/Modal.stories.svelte +192 -0
  39. package/dist/components/feedback/Modal.stories.svelte.d.ts +4 -0
  40. package/dist/components/feedback/Modal.svelte +185 -0
  41. package/dist/components/feedback/Modal.svelte.d.ts +19 -0
  42. package/dist/components/feedback/Toast.stories.svelte +203 -0
  43. package/dist/components/feedback/Toast.stories.svelte.d.ts +19 -0
  44. package/dist/components/feedback/Toast.svelte +109 -0
  45. package/dist/components/feedback/Toast.svelte.d.ts +15 -0
  46. package/dist/components/feedback/ToastRegion.stories.svelte +193 -0
  47. package/dist/components/feedback/ToastRegion.stories.svelte.d.ts +19 -0
  48. package/dist/components/feedback/ToastRegion.svelte +102 -0
  49. package/dist/components/feedback/ToastRegion.svelte.d.ts +9 -0
  50. package/dist/components/feedback/index.d.ts +3 -0
  51. package/dist/components/feedback/index.js +3 -0
  52. package/dist/components/forms/Checkbox.stories.svelte +103 -0
  53. package/dist/components/forms/Checkbox.stories.svelte.d.ts +19 -0
  54. package/dist/components/forms/Checkbox.svelte +150 -0
  55. package/dist/components/forms/Checkbox.svelte.d.ts +11 -0
  56. package/dist/components/forms/Field.stories.svelte +113 -0
  57. package/dist/components/forms/Field.stories.svelte.d.ts +19 -0
  58. package/dist/components/forms/Field.svelte +77 -0
  59. package/dist/components/forms/Field.svelte.d.ts +17 -0
  60. package/dist/components/forms/Input.stories.svelte +58 -0
  61. package/dist/components/forms/Input.stories.svelte.d.ts +19 -0
  62. package/dist/components/forms/Input.svelte +64 -0
  63. package/dist/components/forms/Input.svelte.d.ts +9 -0
  64. package/dist/components/forms/InputWrap.composition.stories.svelte +32 -0
  65. package/dist/components/forms/InputWrap.composition.stories.svelte.d.ts +19 -0
  66. package/dist/components/forms/InputWrap.stories.svelte +53 -0
  67. package/dist/components/forms/InputWrap.stories.svelte.d.ts +19 -0
  68. package/dist/components/forms/InputWrap.svelte +128 -0
  69. package/dist/components/forms/InputWrap.svelte.d.ts +21 -0
  70. package/dist/components/forms/Radio.stories.svelte +70 -0
  71. package/dist/components/forms/Radio.stories.svelte.d.ts +19 -0
  72. package/dist/components/forms/Radio.svelte +109 -0
  73. package/dist/components/forms/Radio.svelte.d.ts +9 -0
  74. package/dist/components/forms/RadioGroup.stories.svelte +115 -0
  75. package/dist/components/forms/RadioGroup.stories.svelte.d.ts +19 -0
  76. package/dist/components/forms/RadioGroup.svelte +116 -0
  77. package/dist/components/forms/RadioGroup.svelte.d.ts +24 -0
  78. package/dist/components/forms/Select.stories.svelte +168 -0
  79. package/dist/components/forms/Select.stories.svelte.d.ts +19 -0
  80. package/dist/components/forms/Select.svelte +262 -0
  81. package/dist/components/forms/Select.svelte.d.ts +23 -0
  82. package/dist/components/forms/Switch.stories.svelte +86 -0
  83. package/dist/components/forms/Switch.stories.svelte.d.ts +19 -0
  84. package/dist/components/forms/Switch.svelte +113 -0
  85. package/dist/components/forms/Switch.svelte.d.ts +11 -0
  86. package/dist/components/forms/Textarea.stories.svelte +40 -0
  87. package/dist/components/forms/Textarea.stories.svelte.d.ts +19 -0
  88. package/dist/components/forms/Textarea.svelte +66 -0
  89. package/dist/components/forms/Textarea.svelte.d.ts +9 -0
  90. package/dist/components/forms/field-context.d.ts +7 -0
  91. package/dist/components/forms/field-context.js +1 -0
  92. package/dist/components/forms/index.d.ts +9 -0
  93. package/dist/components/forms/index.js +9 -0
  94. package/dist/components/layout/Container.stories.svelte +67 -0
  95. package/dist/components/layout/Container.stories.svelte.d.ts +19 -0
  96. package/dist/components/layout/Container.svelte +52 -0
  97. package/dist/components/layout/Container.svelte.d.ts +14 -0
  98. package/dist/components/layout/Grid.stories.svelte +109 -0
  99. package/dist/components/layout/Grid.stories.svelte.d.ts +19 -0
  100. package/dist/components/layout/Grid.svelte +54 -0
  101. package/dist/components/layout/Grid.svelte.d.ts +19 -0
  102. package/dist/components/layout/Inline.stories.svelte +136 -0
  103. package/dist/components/layout/Inline.stories.svelte.d.ts +19 -0
  104. package/dist/components/layout/Inline.svelte +46 -0
  105. package/dist/components/layout/Inline.svelte.d.ts +19 -0
  106. package/dist/components/layout/Prose.stories.svelte +423 -0
  107. package/dist/components/layout/Prose.stories.svelte.d.ts +19 -0
  108. package/dist/components/layout/Prose.svelte +176 -0
  109. package/dist/components/layout/Prose.svelte.d.ts +12 -0
  110. package/dist/components/layout/Rule.stories.svelte +80 -0
  111. package/dist/components/layout/Rule.stories.svelte.d.ts +19 -0
  112. package/dist/components/layout/Rule.svelte +33 -0
  113. package/dist/components/layout/Rule.svelte.d.ts +9 -0
  114. package/dist/components/layout/Spread.stories.svelte +118 -0
  115. package/dist/components/layout/Spread.stories.svelte.d.ts +19 -0
  116. package/dist/components/layout/Spread.svelte +38 -0
  117. package/dist/components/layout/Spread.svelte.d.ts +16 -0
  118. package/dist/components/layout/Stack.stories.svelte +90 -0
  119. package/dist/components/layout/Stack.stories.svelte.d.ts +19 -0
  120. package/dist/components/layout/Stack.svelte +37 -0
  121. package/dist/components/layout/Stack.svelte.d.ts +16 -0
  122. package/dist/components/layout/index.d.ts +7 -0
  123. package/dist/components/layout/index.js +7 -0
  124. package/dist/components/navigation/Breadcrumb.stories.svelte +122 -0
  125. package/dist/components/navigation/Breadcrumb.stories.svelte.d.ts +19 -0
  126. package/dist/components/navigation/Breadcrumb.svelte +70 -0
  127. package/dist/components/navigation/Breadcrumb.svelte.d.ts +13 -0
  128. package/dist/components/navigation/Nav.stories.svelte +323 -0
  129. package/dist/components/navigation/Nav.stories.svelte.d.ts +19 -0
  130. package/dist/components/navigation/Nav.svelte +257 -0
  131. package/dist/components/navigation/Nav.svelte.d.ts +21 -0
  132. package/dist/components/navigation/index.d.ts +2 -0
  133. package/dist/components/navigation/index.js +2 -0
  134. package/dist/components/patterns/ActivityRow.stories.svelte +45 -0
  135. package/dist/components/patterns/ActivityRow.stories.svelte.d.ts +19 -0
  136. package/dist/components/patterns/ActivityRow.svelte +69 -0
  137. package/dist/components/patterns/ActivityRow.svelte.d.ts +16 -0
  138. package/dist/components/patterns/Alert.stories.svelte +63 -0
  139. package/dist/components/patterns/Alert.stories.svelte.d.ts +19 -0
  140. package/dist/components/patterns/Alert.svelte +91 -0
  141. package/dist/components/patterns/Alert.svelte.d.ts +16 -0
  142. package/dist/components/patterns/CtaBlock.stories.svelte +62 -0
  143. package/dist/components/patterns/CtaBlock.stories.svelte.d.ts +19 -0
  144. package/dist/components/patterns/CtaBlock.svelte +80 -0
  145. package/dist/components/patterns/CtaBlock.svelte.d.ts +16 -0
  146. package/dist/components/patterns/KvList.stories.svelte +48 -0
  147. package/dist/components/patterns/KvList.stories.svelte.d.ts +19 -0
  148. package/dist/components/patterns/KvList.svelte +65 -0
  149. package/dist/components/patterns/KvList.svelte.d.ts +15 -0
  150. package/dist/components/patterns/PageHero.stories.svelte +62 -0
  151. package/dist/components/patterns/PageHero.stories.svelte.d.ts +19 -0
  152. package/dist/components/patterns/PageHero.svelte +62 -0
  153. package/dist/components/patterns/PageHero.svelte.d.ts +14 -0
  154. package/dist/components/patterns/ProgressBar.stories.svelte +83 -0
  155. package/dist/components/patterns/ProgressBar.stories.svelte.d.ts +19 -0
  156. package/dist/components/patterns/ProgressBar.svelte +71 -0
  157. package/dist/components/patterns/ProgressBar.svelte.d.ts +13 -0
  158. package/dist/components/patterns/SectionFoot.stories.svelte +37 -0
  159. package/dist/components/patterns/SectionFoot.stories.svelte.d.ts +19 -0
  160. package/dist/components/patterns/SectionFoot.svelte +70 -0
  161. package/dist/components/patterns/SectionFoot.svelte.d.ts +15 -0
  162. package/dist/components/patterns/SectionHead.stories.svelte +67 -0
  163. package/dist/components/patterns/SectionHead.stories.svelte.d.ts +19 -0
  164. package/dist/components/patterns/SectionHead.svelte +54 -0
  165. package/dist/components/patterns/SectionHead.svelte.d.ts +14 -0
  166. package/dist/components/patterns/StatCard.stories.svelte +59 -0
  167. package/dist/components/patterns/StatCard.stories.svelte.d.ts +19 -0
  168. package/dist/components/patterns/StatCard.svelte +57 -0
  169. package/dist/components/patterns/StatCard.svelte.d.ts +15 -0
  170. package/dist/components/patterns/index.d.ts +9 -0
  171. package/dist/components/patterns/index.js +9 -0
  172. package/dist/components/primitives/Button.stories.svelte +132 -0
  173. package/dist/components/primitives/Button.stories.svelte.d.ts +19 -0
  174. package/dist/components/primitives/Button.svelte +142 -0
  175. package/dist/components/primitives/Button.svelte.d.ts +16 -0
  176. package/dist/components/primitives/Heading.stories.svelte +137 -0
  177. package/dist/components/primitives/Heading.stories.svelte.d.ts +19 -0
  178. package/dist/components/primitives/Heading.svelte +107 -0
  179. package/dist/components/primitives/Heading.svelte.d.ts +23 -0
  180. package/dist/components/primitives/Led.stories.svelte +63 -0
  181. package/dist/components/primitives/Led.stories.svelte.d.ts +19 -0
  182. package/dist/components/primitives/Led.svelte +65 -0
  183. package/dist/components/primitives/Led.svelte.d.ts +11 -0
  184. package/dist/components/primitives/TagPill.stories.svelte +90 -0
  185. package/dist/components/primitives/TagPill.stories.svelte.d.ts +19 -0
  186. package/dist/components/primitives/TagPill.svelte +44 -0
  187. package/dist/components/primitives/TagPill.svelte.d.ts +9 -0
  188. package/dist/components/primitives/Text.stories.svelte +252 -0
  189. package/dist/components/primitives/Text.stories.svelte.d.ts +19 -0
  190. package/dist/components/primitives/Text.svelte +101 -0
  191. package/dist/components/primitives/Text.svelte.d.ts +25 -0
  192. package/dist/components/primitives/index.d.ts +5 -0
  193. package/dist/components/primitives/index.js +5 -0
  194. package/dist/index.d.ts +10 -0
  195. package/dist/index.js +10 -0
  196. package/dist/stores/toast.d.ts +19 -0
  197. package/dist/stores/toast.js +22 -0
  198. package/dist/storybook-utils.d.ts +11 -0
  199. package/dist/storybook-utils.js +29 -0
  200. package/dist/tokens/ColorSwatch.svelte +73 -0
  201. package/dist/tokens/ColorSwatch.svelte.d.ts +10 -0
  202. package/dist/tokens/layout.css +144 -0
  203. package/dist/tokens/patterns.css +281 -0
  204. package/dist/tokens/tokens.css +96 -0
  205. package/dist/tokens/tokens.stories.svelte +107 -0
  206. package/dist/tokens/tokens.stories.svelte.d.ts +18 -0
  207. package/dist/tokens/typography.css +159 -0
  208. package/package.json +62 -0
@@ -0,0 +1,32 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import InputWrap from "./InputWrap.svelte";
5
+ import Input from "./Input.svelte";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Forms/InputWrap/Composition",
9
+ component: InputWrap,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ {#snippet mailIcon()}
15
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" aria-hidden="true">
16
+ <path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM98.71,128,40,181.81V74.19Zm11.84,10.85,12,11.05a8,8,0,0,0,10.82,0l12-11.05,58.22,53.15H52.27ZM157.29,128,216,74.19V181.81Z"/>
17
+ </svg>
18
+ {/snippet}
19
+
20
+ <Story name="Icon Prefix"
21
+ play={async ({ canvasElement }) => {
22
+ const canvas = within(canvasElement);
23
+ await expect(canvas.getByRole("textbox")).toBeVisible();
24
+ // icon-pre span is aria-hidden so the input is still accessible
25
+ const iconPre = canvasElement.querySelector(".icon-pre");
26
+ await expect(iconPre).not.toBeNull();
27
+ await expect(iconPre!.getAttribute("aria-hidden")).toBe("true");
28
+ }}>
29
+ <InputWrap iconPre={mailIcon}>
30
+ <Input type="email" placeholder="you@domain.com" />
31
+ </InputWrap>
32
+ </Story>
@@ -0,0 +1,19 @@
1
+ import InputWrap from "./InputWrap.svelte";
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const InputWrap: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type InputWrap = InstanceType<typeof InputWrap>;
19
+ export default InputWrap;
@@ -0,0 +1,53 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import InputWrap from "./InputWrap.svelte";
5
+ import Input from "./Input.svelte";
6
+ import { resolveTokenColor } from "../../storybook-utils.js";
7
+
8
+ const { Story } = defineMeta({
9
+ title: "Forms/InputWrap",
10
+ component: InputWrap,
11
+ tags: ["autodocs"],
12
+ });
13
+ </script>
14
+
15
+ <!-- addonPre: slot holds the Input child; InputWrap wraps it with the addon -->
16
+ <Story name="Addon Prefix" args={{ addonPre: "+12V" }}
17
+ play={async ({ canvasElement }) => {
18
+ const canvas = within(canvasElement);
19
+ const addonEl = canvas.getByText("+12V");
20
+ await expect(addonEl).toBeVisible();
21
+ const bgElev = resolveTokenColor("--bg-elev");
22
+ await expect(getComputedStyle(addonEl).backgroundColor).toBe(bgElev);
23
+ await expect(canvas.getByRole("spinbutton")).toBeVisible();
24
+ }}>
25
+ <Input type="number" value="75" />
26
+ </Story>
27
+
28
+ <Story name="Addon Suffix" args={{ addonSuf: "mA" }}
29
+ play={async ({ canvasElement }) => {
30
+ const canvas = within(canvasElement);
31
+ const addonEl = canvas.getByText("mA");
32
+ await expect(addonEl).toBeVisible();
33
+ }}>
34
+ <Input type="number" value="62" />
35
+ </Story>
36
+
37
+ <Story name="Clearable — No Value" args={{ clearable: true, value: "" }}
38
+ play={async ({ canvasElement }) => {
39
+ const canvas = within(canvasElement);
40
+ const clearBtn = canvas.getByRole("button", { name: "Clear" });
41
+ await expect(clearBtn).not.toBeVisible();
42
+ }}>
43
+ <Input type="text" />
44
+ </Story>
45
+
46
+ <Story name="Clearable — With Value" args={{ clearable: true, value: "DISTRANS-AR1" }}
47
+ play={async ({ canvasElement }) => {
48
+ const canvas = within(canvasElement);
49
+ const clearBtn = canvas.getByRole("button", { name: "Clear" });
50
+ await expect(clearBtn).toBeVisible();
51
+ }}>
52
+ <Input type="text" value="DISTRANS-AR1" />
53
+ </Story>
@@ -0,0 +1,19 @@
1
+ import InputWrap from "./InputWrap.svelte";
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const InputWrap: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type InputWrap = InstanceType<typeof InputWrap>;
19
+ export default InputWrap;
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+ import Button from '../primitives/Button.svelte'
5
+
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ /** Snippet rendered as an icon before the input (aria-hidden). */
8
+ iconPre?: Snippet
9
+ /** Text prefix rendered before the input in a pill. */
10
+ addonPre?: string
11
+ /** Text suffix rendered after the input in a pill. */
12
+ addonSuf?: string
13
+ /** Show a clear (×) button when the input has a value. @default false */
14
+ clearable?: boolean
15
+ /** Current input value — used to show/hide the clear button. @default '' */
16
+ value?: string
17
+ /** Called when the clear button is clicked. */
18
+ onclear?: () => void
19
+ children: Snippet
20
+ [key: string]: unknown
21
+ }
22
+
23
+ let {
24
+ iconPre,
25
+ addonPre,
26
+ addonSuf,
27
+ clearable = false,
28
+ value = '',
29
+ onclear,
30
+ children,
31
+ ...rest
32
+ }: Props = $props()
33
+
34
+ let showClear = $derived(clearable && (value?.length ?? 0) > 0)
35
+ </script>
36
+
37
+ <div class="input-wrap" {...rest}>
38
+ {#if iconPre}
39
+ <span class="icon-pre" aria-hidden="true">
40
+ {@render iconPre()}
41
+ </span>
42
+ {/if}
43
+ {#if addonPre}
44
+ <span class="addon addon-pre">{addonPre}</span>
45
+ {/if}
46
+ {@render children()}
47
+ {#if addonSuf}
48
+ <span class="addon addon-suf">{addonSuf}</span>
49
+ {/if}
50
+ {#if clearable}
51
+ <Button
52
+ variant="ghost"
53
+ type="button"
54
+ class={showClear ? 'wrap-clear visible' : 'wrap-clear'}
55
+ aria-label="Clear"
56
+ tabindex={showClear ? 0 : -1}
57
+ onclick={onclear}
58
+ >×</Button>
59
+ {/if}
60
+ </div>
61
+
62
+ <style>
63
+ .input-wrap {
64
+ position: relative;
65
+ display: flex;
66
+ align-items: stretch;
67
+ }
68
+
69
+ .icon-pre {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ width: 34px;
74
+ flex-shrink: 0;
75
+ background: var(--bg-elev);
76
+ border: 1px solid var(--rule-strong);
77
+ border-right: none;
78
+ color: var(--ink-faint);
79
+ }
80
+
81
+ .addon {
82
+ display: flex;
83
+ align-items: center;
84
+ padding: 0 10px;
85
+ background: var(--bg-elev);
86
+ border: 1px solid var(--rule-strong);
87
+ font-family: var(--mono);
88
+ font-size: 13px;
89
+ color: var(--ink-faint);
90
+ white-space: nowrap;
91
+ letter-spacing: 0.04em;
92
+ }
93
+
94
+ .addon-pre {
95
+ border-right: none;
96
+ }
97
+
98
+ .addon-suf {
99
+ border-left: none;
100
+ }
101
+
102
+ :global(.wrap-clear) {
103
+ position: absolute;
104
+ right: 8px;
105
+ top: 50%;
106
+ transform: translateY(-50%);
107
+ font-family: var(--mono);
108
+ font-size: 14px;
109
+ line-height: 1;
110
+ color: var(--ink-faint);
111
+ cursor: pointer;
112
+ background: none;
113
+ border: none;
114
+ padding: 2px 4px;
115
+ transition: color var(--transition);
116
+ opacity: 0;
117
+ pointer-events: none;
118
+ }
119
+
120
+ :global(.wrap-clear.visible) {
121
+ opacity: 1;
122
+ pointer-events: auto;
123
+ }
124
+
125
+ :global(.wrap-clear:hover) {
126
+ color: var(--ink);
127
+ }
128
+ </style>
@@ -0,0 +1,21 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ /** Snippet rendered as an icon before the input (aria-hidden). */
5
+ iconPre?: Snippet;
6
+ /** Text prefix rendered before the input in a pill. */
7
+ addonPre?: string;
8
+ /** Text suffix rendered after the input in a pill. */
9
+ addonSuf?: string;
10
+ /** Show a clear (×) button when the input has a value. @default false */
11
+ clearable?: boolean;
12
+ /** Current input value — used to show/hide the clear button. @default '' */
13
+ value?: string;
14
+ /** Called when the clear button is clicked. */
15
+ onclear?: () => void;
16
+ children: Snippet;
17
+ [key: string]: unknown;
18
+ }
19
+ declare const InputWrap: import("svelte").Component<Props, {}, "">;
20
+ type InputWrap = ReturnType<typeof InputWrap>;
21
+ export default InputWrap;
@@ -0,0 +1,70 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import { resolveTokenColor, resolveTokenFgColor } from "../../storybook-utils.js";
5
+ import Radio from "./Radio.svelte";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Forms/Radio",
9
+ component: Radio,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <!-- AC-25: unchecked by default -->
15
+ <Story name="Default (Unchecked)"
16
+ args={{ label: "Option A", name: "demo", value: "a", checked: false }}
17
+ play={async ({ canvasElement }) => {
18
+ const canvas = within(canvasElement);
19
+ const input = canvas.getByRole("radio");
20
+ await expect(input).toBeVisible();
21
+ await expect(input).not.toBeChecked();
22
+ // AC-28: name and value attributes forwarded via ...rest
23
+ await expect(input.getAttribute("name")).toBe("demo");
24
+ await expect(input.getAttribute("value")).toBe("a");
25
+ // indicator border colour matches var(--rule-strong)
26
+ const indicator = canvasElement.querySelector(".radio-indicator");
27
+ const ruleStrongColor = resolveTokenFgColor("--rule-strong");
28
+ await expect(getComputedStyle(indicator!).borderColor).toBe(ruleStrongColor);
29
+ }} />
30
+
31
+ <!-- AC-26: checked state -->
32
+ <Story name="Checked"
33
+ args={{ label: "Option A", name: "demo", value: "a", checked: true }}
34
+ play={async ({ canvasElement }) => {
35
+ const canvas = within(canvasElement);
36
+ const input = canvas.getByRole("radio");
37
+ await expect(input).toBeChecked();
38
+ // AC-28: attributes still forwarded in checked state
39
+ await expect(input.getAttribute("name")).toBe("demo");
40
+ await expect(input.getAttribute("value")).toBe("a");
41
+ // indicator ::before pseudo-element colour resolves (amber dot present)
42
+ const indicator = canvasElement.querySelector(".radio-indicator");
43
+ const amberColor = resolveTokenColor("--amber");
44
+ await expect(getComputedStyle(indicator!, "::before").backgroundColor).toBe(amberColor);
45
+ }} />
46
+
47
+ <!-- AC-27: disabled — toBeDisabled and wrap opacity 0.4 -->
48
+ <Story name="Disabled"
49
+ args={{ label: "Locked", name: "demo", value: "x", disabled: true }}
50
+ play={async ({ canvasElement }) => {
51
+ const canvas = within(canvasElement);
52
+ const input = canvas.getByRole("radio");
53
+ await expect(input).toBeDisabled();
54
+ // AC-27: wrap opacity 0.4
55
+ const wrap = canvasElement.querySelector(".radio-wrap");
56
+ await expect(getComputedStyle(wrap!).opacity).toBe("0.4");
57
+ }} />
58
+
59
+ <!-- AC-29: focus styles — keyboard focus reaches the radio input -->
60
+ <Story name="Focus Styles"
61
+ args={{ label: "Focus me", name: "demo", value: "f" }}
62
+ play={async ({ canvasElement, userEvent }) => {
63
+ const canvas = within(canvasElement);
64
+ const input = canvas.getByRole("radio");
65
+ await userEvent.tab();
66
+ await expect(input).toHaveFocus();
67
+ // indicator receives the delegated focus outline — it exists in DOM
68
+ const indicator = canvasElement.querySelector(".radio-indicator");
69
+ await expect(indicator).toBeVisible();
70
+ }} />
@@ -0,0 +1,19 @@
1
+ import Radio from "./Radio.svelte";
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const Radio: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Radio = InstanceType<typeof Radio>;
19
+ export default Radio;
@@ -0,0 +1,109 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements'
3
+ import { getContext } from 'svelte'
4
+ import type { FieldContext } from './field-context.js'
5
+ import { FIELD_CONTEXT_KEY } from './field-context.js'
6
+
7
+ interface Props extends HTMLInputAttributes {
8
+ /** Visible label text rendered next to the radio button. */
9
+ label: string
10
+ [key: string]: unknown
11
+ }
12
+
13
+ let {
14
+ label,
15
+ checked = false,
16
+ disabled,
17
+ tabindex,
18
+ ...rest
19
+ }: Props = $props()
20
+
21
+ const fieldCtx = getContext<FieldContext | undefined>(FIELD_CONTEXT_KEY)
22
+
23
+ let resolvedId = $derived(fieldCtx ? fieldCtx.inputId : (rest.id as string | undefined))
24
+ let resolvedAriaInvalid: boolean | 'true' | 'false' | 'grammar' | 'spelling' | null | undefined = $derived(
25
+ fieldCtx?.hasError ? 'true' : undefined
26
+ )
27
+ let resolvedAriaDescribedby = $derived(fieldCtx?.hasHint ? fieldCtx.hintId : undefined)
28
+ </script>
29
+
30
+ <label class="radio-wrap" class:disabled={disabled}>
31
+ <input
32
+ type="radio"
33
+ class="radio-input"
34
+ {checked}
35
+ {disabled}
36
+ {tabindex}
37
+ id={resolvedId}
38
+ aria-invalid={resolvedAriaInvalid}
39
+ aria-describedby={resolvedAriaDescribedby}
40
+ {...rest}
41
+ />
42
+ <span class="radio-indicator" aria-hidden="true"></span>
43
+ <span class="radio-label">{label}</span>
44
+ </label>
45
+
46
+ <style>
47
+ .radio-wrap {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ gap: 8px;
51
+ cursor: pointer;
52
+ user-select: none;
53
+ font-family: var(--mono);
54
+ font-size: 13px;
55
+ color: var(--ink);
56
+ position: relative;
57
+ }
58
+
59
+ .radio-wrap.disabled {
60
+ opacity: 0.4;
61
+ cursor: not-allowed;
62
+ }
63
+
64
+ .radio-input {
65
+ position: absolute;
66
+ width: 1px;
67
+ height: 1px;
68
+ margin: -1px;
69
+ padding: 0;
70
+ border: 0;
71
+ overflow: hidden;
72
+ clip: rect(0, 0, 0, 0);
73
+ white-space: nowrap;
74
+ }
75
+
76
+ .radio-indicator {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ width: 16px;
81
+ height: 16px;
82
+ flex-shrink: 0;
83
+ border: 1px solid var(--rule-strong);
84
+ border-radius: 50%;
85
+ background: transparent;
86
+ transition: border-color var(--transition);
87
+ position: relative;
88
+ }
89
+
90
+ /* Checked state — amber inner dot via ::before */
91
+ .radio-input:checked + .radio-indicator::before {
92
+ content: '';
93
+ display: block;
94
+ width: 8px;
95
+ height: 8px;
96
+ border-radius: 50%;
97
+ background: var(--amber);
98
+ }
99
+
100
+ /* Focus-visible delegated to indicator */
101
+ .radio-input:focus-visible + .radio-indicator {
102
+ outline: 2px solid var(--amber);
103
+ outline-offset: 2px;
104
+ }
105
+
106
+ .radio-label {
107
+ color: var(--ink);
108
+ }
109
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ interface Props extends HTMLInputAttributes {
3
+ /** Visible label text rendered next to the radio button. */
4
+ label: string;
5
+ [key: string]: unknown;
6
+ }
7
+ declare const Radio: import("svelte").Component<Props, {}, "">;
8
+ type Radio = ReturnType<typeof Radio>;
9
+ export default Radio;
@@ -0,0 +1,115 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import RadioGroup from "./RadioGroup.svelte";
5
+
6
+ const { Story } = defineMeta({
7
+ title: "Forms/RadioGroup",
8
+ component: RadioGroup,
9
+ tags: ["autodocs"],
10
+ });
11
+
12
+ const baseOptions = [
13
+ { value: "osc", label: "Oscillator" },
14
+ { value: "env", label: "Envelope" },
15
+ { value: "util", label: "Utility" },
16
+ ];
17
+
18
+ const partiallyDisabledOptions = [
19
+ { value: "osc", label: "Oscillator" },
20
+ { value: "env", label: "Envelope" },
21
+ { value: "util", label: "Utility", disabled: true },
22
+ ];
23
+ </script>
24
+
25
+ <!-- AC-31, AC-32, AC-33, AC-36: fieldset, legend, radios, no selection -->
26
+ <Story name="Default — No Selection"
27
+ args={{ legend: "Module type", name: "mod", options: baseOptions }}
28
+ play={async ({ canvasElement }) => {
29
+ const canvas = within(canvasElement);
30
+ // AC-32: getByRole('group') returns the fieldset with the legend text as accessible name
31
+ const group = canvas.getByRole("group", { name: "Module type" });
32
+ await expect(group).toBeVisible();
33
+ // AC-31: fieldset contains a legend with the legend prop text
34
+ const legend = canvasElement.querySelector("legend");
35
+ await expect(legend).not.toBeNull();
36
+ await expect(legend!.textContent!.trim()).toBe("Module type");
37
+ // AC-33: one radio per option
38
+ const radios = canvas.getAllByRole("radio");
39
+ await expect(radios.length).toBe(3);
40
+ // AC-36: no radio is checked when value is unset
41
+ for (const radio of radios) {
42
+ await expect(radio).not.toBeChecked();
43
+ }
44
+ }} />
45
+
46
+ <!-- AC-34, AC-35: name attribute shared, correct radio checked -->
47
+ <Story name="With Selection"
48
+ args={{ legend: "Module type", name: "mod", options: baseOptions, value: "env" }}
49
+ play={async ({ canvasElement }) => {
50
+ const canvas = within(canvasElement);
51
+ // AC-35: the radio matching value is checked
52
+ const envRadio = canvas.getByRole("radio", { name: "Envelope" });
53
+ await expect(envRadio).toBeChecked();
54
+ // AC-35: all other radios are unchecked
55
+ const oscRadio = canvas.getByRole("radio", { name: "Oscillator" });
56
+ const utilRadio = canvas.getByRole("radio", { name: "Utility" });
57
+ await expect(oscRadio).not.toBeChecked();
58
+ await expect(utilRadio).not.toBeChecked();
59
+ // AC-34: all radios share the same name attribute
60
+ const radios = canvas.getAllByRole("radio");
61
+ for (const radio of radios) {
62
+ await expect(radio.getAttribute("name")).toBe("mod");
63
+ }
64
+ // AC-42: selected radio has tabindex="0", others have tabindex="-1"
65
+ await expect(envRadio.getAttribute("tabindex")).toBe("0");
66
+ await expect(oscRadio.getAttribute("tabindex")).toBe("-1");
67
+ await expect(utilRadio.getAttribute("tabindex")).toBe("-1");
68
+ }} />
69
+
70
+ <!-- AC-37: disabled group — all radios disabled -->
71
+ <Story name="Disabled Group"
72
+ args={{ legend: "Module type", name: "mod", options: baseOptions, disabled: true }}
73
+ play={async ({ canvasElement }) => {
74
+ const canvas = within(canvasElement);
75
+ const radios = canvas.getAllByRole("radio");
76
+ await expect(radios.length).toBe(3);
77
+ // AC-37: all radios are disabled when group disabled=true
78
+ for (const radio of radios) {
79
+ await expect(radio).toBeDisabled();
80
+ }
81
+ }} />
82
+
83
+ <!-- AC-38: individual option disabled, others enabled -->
84
+ <Story name="Partially Disabled"
85
+ args={{ legend: "Module type", name: "mod", options: partiallyDisabledOptions }}
86
+ play={async ({ canvasElement }) => {
87
+ const canvas = within(canvasElement);
88
+ const oscRadio = canvas.getByRole("radio", { name: "Oscillator" });
89
+ const envRadio = canvas.getByRole("radio", { name: "Envelope" });
90
+ const utilRadio = canvas.getByRole("radio", { name: "Utility" });
91
+ // AC-38: only the individually-disabled option is disabled
92
+ await expect(utilRadio).toBeDisabled();
93
+ await expect(oscRadio).not.toBeDisabled();
94
+ await expect(envRadio).not.toBeDisabled();
95
+ }} />
96
+
97
+ <!-- AC-39, AC-42: ArrowDown moves focus to next radio; roving tabindex -->
98
+ <Story name="Keyboard Navigation"
99
+ args={{ legend: "Module type", name: "mod", options: baseOptions, value: "osc" }}
100
+ play={async ({ canvasElement, userEvent }) => {
101
+ const canvas = within(canvasElement);
102
+ const oscRadio = canvas.getByRole("radio", { name: "Oscillator" });
103
+ const envRadio = canvas.getByRole("radio", { name: "Envelope" });
104
+ // AC-42: selected radio ("osc") has tabindex="0"
105
+ await expect(oscRadio.getAttribute("tabindex")).toBe("0");
106
+ // Tab into the group — the selected (tabindex=0) radio receives focus
107
+ await userEvent.tab();
108
+ await expect(oscRadio).toHaveFocus();
109
+ // AC-39: ArrowDown moves DOM focus to the next enabled radio
110
+ await userEvent.keyboard("{ArrowDown}");
111
+ await expect(document.activeElement).toBe(envRadio);
112
+ // AC-40: ArrowUp from second radio returns focus to first
113
+ await userEvent.keyboard("{ArrowUp}");
114
+ await expect(document.activeElement).toBe(oscRadio);
115
+ }} />
@@ -0,0 +1,19 @@
1
+ import RadioGroup from "./RadioGroup.svelte";
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ declare const RadioGroup: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type RadioGroup = InstanceType<typeof RadioGroup>;
19
+ export default RadioGroup;