@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,86 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import { resolveTokenColor } from "../../storybook-utils.js";
5
+ import Switch from "./Switch.svelte";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Forms/Switch",
9
+ component: Switch,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <!-- AC-44, AC-45, AC-46, AC-47: Off state -->
15
+ <Story name="Off (Default)" args={{ label: "Dark mode", checked: false }}
16
+ play={async ({ canvasElement }) => {
17
+ const canvas = within(canvasElement);
18
+ // AC-44: renders a button with role="switch"
19
+ // AC-46: accessible name provided by aria-label={label}
20
+ const sw = canvas.getByRole("switch", { name: "Dark mode" });
21
+ await expect(sw).toBeVisible();
22
+ // AC-45: aria-checked is "false" when off
23
+ await expect(sw.getAttribute("aria-checked")).toBe("false");
24
+ // AC-47: track background matches var(--bg-sunken) when off
25
+ const bgSunken = resolveTokenColor("--bg-sunken");
26
+ await expect(getComputedStyle(sw).backgroundColor).toBe(bgSunken);
27
+ }} />
28
+
29
+ <!-- AC-45, AC-48: On state -->
30
+ <Story name="On" args={{ label: "Dark mode", checked: true }}
31
+ play={async ({ canvasElement }) => {
32
+ const canvas = within(canvasElement);
33
+ const sw = canvas.getByRole("switch", { name: "Dark mode" });
34
+ // AC-45: aria-checked is "true" when on
35
+ await expect(sw.getAttribute("aria-checked")).toBe("true");
36
+ // AC-48: track background matches var(--amber) when on
37
+ const amberColor = resolveTokenColor("--amber");
38
+ await expect(getComputedStyle(sw).backgroundColor).toBe(amberColor);
39
+ }} />
40
+
41
+ <!-- AC-49: disabled off — toBeDisabled and wrap opacity 0.4 -->
42
+ <Story name="Disabled Off" args={{ label: "Feature flag", disabled: true, checked: false }}
43
+ play={async ({ canvasElement }) => {
44
+ const canvas = within(canvasElement);
45
+ const sw = canvas.getByRole("switch", { name: "Feature flag" });
46
+ await expect(sw).toBeDisabled();
47
+ // AC-49: wrap opacity is 0.4
48
+ const wrap = canvasElement.querySelector(".switch-wrap");
49
+ await expect(getComputedStyle(wrap!).opacity).toBe("0.4");
50
+ }} />
51
+
52
+ <!-- AC-49: disabled on — toBeDisabled and aria-checked true -->
53
+ <Story name="Disabled On" args={{ label: "Feature flag", disabled: true, checked: true }}
54
+ play={async ({ canvasElement }) => {
55
+ const canvas = within(canvasElement);
56
+ const sw = canvas.getByRole("switch", { name: "Feature flag" });
57
+ await expect(sw).toBeDisabled();
58
+ await expect(sw.getAttribute("aria-checked")).toBe("true");
59
+ }} />
60
+
61
+ <!-- AC-50: Space key toggles switch from off to on -->
62
+ <Story name="Space to Toggle" args={{ label: "Toggle", checked: false }}
63
+ play={async ({ canvasElement, userEvent }) => {
64
+ const canvas = within(canvasElement);
65
+ const sw = canvas.getByRole("switch", { name: "Toggle" });
66
+ await expect(sw.getAttribute("aria-checked")).toBe("false");
67
+ await userEvent.tab();
68
+ await expect(sw).toHaveFocus();
69
+ await userEvent.keyboard(" ");
70
+ // AC-50: after Space on an off switch, aria-checked becomes "true"
71
+ await expect(sw.getAttribute("aria-checked")).toBe("true");
72
+ }} />
73
+
74
+ <!-- AC-48, AC-52: amber track when on + pill shape dimensions -->
75
+ <Story name="Amber Track When On" args={{ label: "Power", checked: true }}
76
+ play={async ({ canvasElement }) => {
77
+ const canvas = within(canvasElement);
78
+ const sw = canvas.getByRole("switch", { name: "Power" });
79
+ // AC-48: track background matches var(--amber)
80
+ const amberColor = resolveTokenColor("--amber");
81
+ await expect(getComputedStyle(sw).backgroundColor).toBe(amberColor);
82
+ // AC-52: pill shape — borderRadius is 11px, width 40px, height 22px
83
+ await expect(getComputedStyle(sw).borderRadius).toBe("11px");
84
+ await expect(getComputedStyle(sw).width).toBe("40px");
85
+ await expect(getComputedStyle(sw).height).toBe("22px");
86
+ }} />
@@ -0,0 +1,19 @@
1
+ import Switch from "./Switch.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 Switch: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Switch = InstanceType<typeof Switch>;
19
+ export default Switch;
@@ -0,0 +1,113 @@
1
+ <script lang="ts">
2
+ import type { HTMLButtonAttributes } 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 HTMLButtonAttributes {
8
+ /** Accessible label rendered next to the toggle track. */
9
+ label: string
10
+ /** Whether the switch is on. Bindable. @default false */
11
+ checked?: boolean
12
+ [key: string]: unknown
13
+ }
14
+
15
+ let {
16
+ label,
17
+ checked = $bindable(false),
18
+ disabled,
19
+ ...rest
20
+ }: Props = $props()
21
+
22
+ const fieldCtx = getContext<FieldContext | undefined>(FIELD_CONTEXT_KEY)
23
+
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
+ // Explicit boolean for aria-checked (avoids unknown from index signature)
29
+ let ariaChecked: boolean = $derived(!!checked)
30
+ </script>
31
+
32
+ <span class="switch-wrap" class:disabled={disabled}>
33
+ <button
34
+ type="button"
35
+ class="switch"
36
+ class:on={checked}
37
+ role="switch"
38
+ aria-checked={ariaChecked}
39
+ aria-label={label}
40
+ {disabled}
41
+ aria-disabled={disabled}
42
+ aria-invalid={resolvedAriaInvalid}
43
+ aria-describedby={resolvedAriaDescribedby}
44
+ onclick={() => { if (!disabled) checked = !checked }}
45
+ {...rest}
46
+ >
47
+ <span class="switch-knob" aria-hidden="true"></span>
48
+ </button>
49
+ <span class="switch-label">{label}</span>
50
+ </span>
51
+
52
+ <style>
53
+ .switch-wrap {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ font-family: var(--mono);
58
+ font-size: 13px;
59
+ color: var(--ink);
60
+ }
61
+
62
+ .switch-wrap.disabled {
63
+ opacity: 0.4;
64
+ cursor: not-allowed;
65
+ }
66
+
67
+ .switch {
68
+ display: inline-flex;
69
+ align-items: center;
70
+ width: 40px;
71
+ height: 22px;
72
+ border-radius: 11px;
73
+ border: 1px solid var(--rail);
74
+ background: var(--bg-sunken);
75
+ cursor: pointer;
76
+ padding: 0;
77
+ position: relative;
78
+ transition: background var(--transition), border-color var(--transition);
79
+ flex-shrink: 0;
80
+ }
81
+
82
+ .switch:disabled {
83
+ cursor: not-allowed;
84
+ }
85
+
86
+ .switch:focus-visible {
87
+ outline: 2px solid var(--amber);
88
+ outline-offset: 2px;
89
+ }
90
+
91
+ .switch.on {
92
+ background: var(--amber);
93
+ border-color: var(--amber);
94
+ }
95
+
96
+ .switch-knob {
97
+ position: absolute;
98
+ width: 16px;
99
+ height: 16px;
100
+ border-radius: 50%;
101
+ background: var(--bg);
102
+ transform: translateX(2px);
103
+ transition: transform var(--transition);
104
+ }
105
+
106
+ .switch.on .switch-knob {
107
+ transform: translateX(20px);
108
+ }
109
+
110
+ .switch-label {
111
+ color: var(--ink);
112
+ }
113
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { HTMLButtonAttributes } from 'svelte/elements';
2
+ interface Props extends HTMLButtonAttributes {
3
+ /** Accessible label rendered next to the toggle track. */
4
+ label: string;
5
+ /** Whether the switch is on. Bindable. @default false */
6
+ checked?: boolean;
7
+ [key: string]: unknown;
8
+ }
9
+ declare const Switch: import("svelte").Component<Props, {}, "checked">;
10
+ type Switch = ReturnType<typeof Switch>;
11
+ export default Switch;
@@ -0,0 +1,40 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Textarea from "./Textarea.svelte";
5
+ import { resolveTokenColor } from "../../storybook-utils.js";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Forms/Textarea",
9
+ component: Textarea,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="Default" args={{ placeholder: "What changed, what broke, what shipped…" }}
15
+ play={async ({ canvasElement }) => {
16
+ const ta = within(canvasElement).getByRole("textbox");
17
+ await expect(ta).toBeVisible();
18
+ await expect(getComputedStyle(ta).resize).toBe("vertical");
19
+ await expect(getComputedStyle(ta).minHeight).toBe("60px");
20
+ }} />
21
+
22
+ <Story name="With Content" args={{ value: "Conduit arrived. Rail draws 62mA @ +12V." }}
23
+ play={async ({ canvasElement }) => {
24
+ const ta = within(canvasElement).getByRole("textbox");
25
+ await expect(ta).toHaveValue("Conduit arrived. Rail draws 62mA @ +12V.");
26
+ }} />
27
+
28
+ <Story name="Error State" args={{ error: true, value: "Bad data" }}
29
+ play={async ({ canvasElement }) => {
30
+ const ta = within(canvasElement).getByRole("textbox");
31
+ const dangerColor = resolveTokenColor("--danger");
32
+ await expect(getComputedStyle(ta).borderColor).toBe(dangerColor);
33
+ }} />
34
+
35
+ <Story name="Disabled" args={{ disabled: true, value: "Locked content" }}
36
+ play={async ({ canvasElement }) => {
37
+ const ta = within(canvasElement).getByRole("textbox");
38
+ await expect(ta).toBeDisabled();
39
+ await expect(getComputedStyle(ta).opacity).toBe("0.4");
40
+ }} />
@@ -0,0 +1,19 @@
1
+ import Textarea from "./Textarea.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 Textarea: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Textarea = InstanceType<typeof Textarea>;
19
+ export default Textarea;
@@ -0,0 +1,66 @@
1
+ <script lang="ts">
2
+ import type { HTMLTextareaAttributes } 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 HTMLTextareaAttributes {
8
+ /** Show the error (danger-border) state. Auto-set when inside a Field with an error prop. @default false */
9
+ error?: boolean
10
+ [key: string]: unknown
11
+ }
12
+
13
+ let { error = false, ...rest }: Props = $props()
14
+
15
+ const fieldCtx = getContext<FieldContext | undefined>(FIELD_CONTEXT_KEY)
16
+
17
+ let resolvedId = $derived(fieldCtx ? fieldCtx.inputId : (rest.id as string | undefined))
18
+ let resolvedAriaInvalid: boolean | 'true' | 'false' | 'grammar' | 'spelling' | null | undefined = $derived(
19
+ fieldCtx?.hasError ? 'true' : undefined
20
+ )
21
+ let resolvedAriaDescribedby = $derived(
22
+ fieldCtx?.hasHint ? fieldCtx.hintId : (rest['aria-describedby'] as string | undefined)
23
+ )
24
+ let resolvedError = $derived(error || (fieldCtx?.hasError ?? false))
25
+ </script>
26
+
27
+ <textarea
28
+ class="input"
29
+ class:err={resolvedError}
30
+ id={resolvedId}
31
+ aria-invalid={resolvedAriaInvalid}
32
+ aria-describedby={resolvedAriaDescribedby}
33
+ {...rest}
34
+ ></textarea>
35
+
36
+ <style>
37
+ .input {
38
+ font-family: var(--mono);
39
+ font-size: 13px;
40
+ letter-spacing: 0.02em;
41
+ background: var(--bg-sunken);
42
+ color: var(--ink);
43
+ border: 1px solid var(--rule-strong);
44
+ padding: 7px 10px;
45
+ outline: none;
46
+ width: 100%;
47
+ transition: border-color var(--transition);
48
+ border-radius: 0;
49
+ resize: vertical;
50
+ min-height: 60px;
51
+ line-height: 1.5;
52
+ }
53
+
54
+ .input:focus {
55
+ border-color: var(--amber);
56
+ }
57
+
58
+ .input.err {
59
+ border-color: var(--danger);
60
+ }
61
+
62
+ .input:disabled {
63
+ opacity: 0.4;
64
+ cursor: not-allowed;
65
+ }
66
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
2
+ interface Props extends HTMLTextareaAttributes {
3
+ /** Show the error (danger-border) state. Auto-set when inside a Field with an error prop. @default false */
4
+ error?: boolean;
5
+ [key: string]: unknown;
6
+ }
7
+ declare const Textarea: import("svelte").Component<Props, {}, "">;
8
+ type Textarea = ReturnType<typeof Textarea>;
9
+ export default Textarea;
@@ -0,0 +1,7 @@
1
+ export interface FieldContext {
2
+ inputId: string;
3
+ hintId: string;
4
+ hasHint: boolean;
5
+ hasError: boolean;
6
+ }
7
+ export declare const FIELD_CONTEXT_KEY = "field";
@@ -0,0 +1 @@
1
+ export const FIELD_CONTEXT_KEY = 'field';
@@ -0,0 +1,9 @@
1
+ export { default as Input } from './Input.svelte';
2
+ export { default as Textarea } from './Textarea.svelte';
3
+ export { default as Select } from './Select.svelte';
4
+ export { default as InputWrap } from './InputWrap.svelte';
5
+ export { default as Field } from './Field.svelte';
6
+ export { default as Checkbox } from './Checkbox.svelte';
7
+ export { default as Radio } from './Radio.svelte';
8
+ export { default as RadioGroup } from './RadioGroup.svelte';
9
+ export { default as Switch } from './Switch.svelte';
@@ -0,0 +1,9 @@
1
+ export { default as Input } from './Input.svelte';
2
+ export { default as Textarea } from './Textarea.svelte';
3
+ export { default as Select } from './Select.svelte';
4
+ export { default as InputWrap } from './InputWrap.svelte';
5
+ export { default as Field } from './Field.svelte';
6
+ export { default as Checkbox } from './Checkbox.svelte';
7
+ export { default as Radio } from './Radio.svelte';
8
+ export { default as RadioGroup } from './RadioGroup.svelte';
9
+ export { default as Switch } from './Switch.svelte';
@@ -0,0 +1,67 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Container from "./Container.svelte";
5
+ import Stack from "./Stack.svelte";
6
+ import Rule from "./Rule.svelte";
7
+ import Button from "../primitives/Button.svelte";
8
+
9
+ const { Story } = defineMeta({
10
+ title: "Layout/Container",
11
+ component: Container,
12
+ tags: ["autodocs"],
13
+ });
14
+ </script>
15
+
16
+ <Story name="Large" args={{ size: "lg", "aria-label": "test-label" }}
17
+ play={async ({ canvasElement }) => {
18
+ const root = canvasElement.firstElementChild as HTMLElement;
19
+ await expect(root).toBeVisible();
20
+ const style = getComputedStyle(root);
21
+ await expect(style.maxWidth).toBe("1440px");
22
+ await expect(style.paddingBottom).toBe("80px");
23
+ await expect(style.paddingLeft).toBe("32px");
24
+ await expect(style.paddingRight).toBe("32px");
25
+ // margin: 0 auto — both left and right should be equal (auto-resolved)
26
+ await expect(style.marginLeft).toBe(style.marginRight);
27
+ // aria-label forwarded via ...rest
28
+ await expect(root.getAttribute("aria-label")).toBe("test-label");
29
+ }}>
30
+ <p>Large container — max-width 1440px. Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
31
+ <Rule />
32
+ </Story>
33
+
34
+ <Story name="Medium" args={{ size: "md" }}
35
+ play={async ({ canvasElement }) => {
36
+ const root = canvasElement.firstElementChild as HTMLElement;
37
+ const style = getComputedStyle(root);
38
+ await expect(style.maxWidth).toBe("960px");
39
+ await expect(style.paddingBottom).toBe("64px");
40
+ await expect(style.paddingLeft).toBe("32px");
41
+ await expect(style.paddingRight).toBe("32px");
42
+ }}>
43
+ <Stack gap="sm">
44
+ <Button variant="primary">Order Now</Button>
45
+ <Button variant="ghost">View All →</Button>
46
+ </Stack>
47
+ </Story>
48
+
49
+ <Story name="Small" args={{ size: "sm" }}
50
+ play={async ({ canvasElement }) => {
51
+ const root = canvasElement.firstElementChild as HTMLElement;
52
+ const style = getComputedStyle(root);
53
+ await expect(style.maxWidth).toBe("640px");
54
+ await expect(style.paddingBottom).toBe("48px");
55
+ await expect(style.paddingLeft).toBe("32px");
56
+ await expect(style.paddingRight).toBe("32px");
57
+ }}>
58
+ <p>Small container — max-width 640px. A narrow reading column for prose content.</p>
59
+ </Story>
60
+
61
+ <Story name="As Main" args={{ as: "main", size: "md" }}
62
+ play={async ({ canvasElement }) => {
63
+ const root = canvasElement.firstElementChild as HTMLElement;
64
+ await expect(root.tagName).toBe("MAIN");
65
+ }}>
66
+ <p>Rendered as a semantic main element.</p>
67
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Container from "./Container.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 Container: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Container = InstanceType<typeof Container>;
19
+ export default Container;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ type ContainerSize = 'lg' | 'md' | 'sm'
6
+
7
+ interface Props extends HTMLAttributes<HTMLDivElement> {
8
+ /** HTML element to render as. @default 'div' */
9
+ as?: string
10
+ /** Max-width preset: lg = 1440px, md = 960px, sm = 640px. @default 'lg' */
11
+ size?: ContainerSize
12
+ children?: Snippet
13
+ [key: string]: unknown
14
+ }
15
+
16
+ let { as = 'div', size = 'lg', children, ...rest }: Props = $props()
17
+ </script>
18
+
19
+ <svelte:element this={as} class="container-wrap" data-size={size} {...rest}>
20
+ {@render children?.()}
21
+ </svelte:element>
22
+
23
+ <style>
24
+ .container-wrap {
25
+ margin: 0 auto;
26
+ padding-left: 32px;
27
+ padding-right: 32px;
28
+ container-type: inline-size;
29
+ }
30
+
31
+ .container-wrap[data-size="lg"] {
32
+ max-width: 1440px;
33
+ padding-bottom: 80px;
34
+ }
35
+
36
+ .container-wrap[data-size="md"] {
37
+ max-width: 960px;
38
+ padding-bottom: 64px;
39
+ }
40
+
41
+ .container-wrap[data-size="sm"] {
42
+ max-width: 640px;
43
+ padding-bottom: 48px;
44
+ }
45
+
46
+ @media (max-width: 720px) {
47
+ .container-wrap {
48
+ padding-left: 16px;
49
+ padding-right: 16px;
50
+ }
51
+ }
52
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ type ContainerSize = 'lg' | 'md' | 'sm';
4
+ interface Props extends HTMLAttributes<HTMLDivElement> {
5
+ /** HTML element to render as. @default 'div' */
6
+ as?: string;
7
+ /** Max-width preset: lg = 1440px, md = 960px, sm = 640px. @default 'lg' */
8
+ size?: ContainerSize;
9
+ children?: Snippet;
10
+ [key: string]: unknown;
11
+ }
12
+ declare const Container: import("svelte").Component<Props, {}, "">;
13
+ type Container = ReturnType<typeof Container>;
14
+ export default Container;
@@ -0,0 +1,109 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Grid from "./Grid.svelte";
5
+ import TagPill from "../primitives/TagPill.svelte";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Layout/Grid",
9
+ component: Grid,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="Three Column" args={{ cols: 3, gap: "sm" }}
15
+ play={async ({ canvasElement }) => {
16
+ const root = canvasElement.firstElementChild as HTMLElement;
17
+ await expect(root).toBeVisible();
18
+ const style = getComputedStyle(root);
19
+ await expect(style.display).toBe("grid");
20
+ // The implementer sets inline style — check element.style for the template string
21
+ await expect(
22
+ root.style.gridTemplateColumns.includes("3") ||
23
+ root.style.gridTemplateColumns === "repeat(3, 1fr)"
24
+ ).toBe(true);
25
+ // gap="sm" → var(--u2) → 16px
26
+ const gap = style.gap || style.columnGap;
27
+ await expect(gap.includes("16px")).toBe(true);
28
+ }}>
29
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 1</div>
30
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 2</div>
31
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 3</div>
32
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 4</div>
33
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 5</div>
34
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 6</div>
35
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 7</div>
36
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 8</div>
37
+ <div style="background: var(--bg-elev); padding: 16px;">Cell 9</div>
38
+ </Story>
39
+
40
+ <Story name="Two Column" args={{ cols: 2, gap: "lg" }}
41
+ play={async ({ canvasElement }) => {
42
+ const root = canvasElement.firstElementChild as HTMLElement;
43
+ const style = getComputedStyle(root);
44
+ await expect(style.display).toBe("grid");
45
+ await expect(
46
+ root.style.gridTemplateColumns.includes("2") ||
47
+ root.style.gridTemplateColumns === "repeat(2, 1fr)"
48
+ ).toBe(true);
49
+ const gap = style.gap || style.columnGap;
50
+ await expect(gap.includes("32px")).toBe(true);
51
+ }}>
52
+ <div style="background: var(--bg-elev); padding: 16px;">Cell A</div>
53
+ <div style="background: var(--bg-elev); padding: 16px;">Cell B</div>
54
+ <div style="background: var(--bg-elev); padding: 16px;">Cell C</div>
55
+ <div style="background: var(--bg-elev); padding: 16px;">Cell D</div>
56
+ </Story>
57
+
58
+ <Story name="Four Column" args={{ cols: 4, gap: "sm" }}
59
+ play={async ({ canvasElement }) => {
60
+ const root = canvasElement.firstElementChild as HTMLElement;
61
+ const style = getComputedStyle(root);
62
+ await expect(style.display).toBe("grid");
63
+ await expect(
64
+ root.style.gridTemplateColumns.includes("4") ||
65
+ root.style.gridTemplateColumns === "repeat(4, 1fr)"
66
+ ).toBe(true);
67
+ }}>
68
+ <div style="background: var(--bg-elev); padding: 16px;">1</div>
69
+ <div style="background: var(--bg-elev); padding: 16px;">2</div>
70
+ <div style="background: var(--bg-elev); padding: 16px;">3</div>
71
+ <div style="background: var(--bg-elev); padding: 16px;">4</div>
72
+ <div style="background: var(--bg-elev); padding: 16px;">5</div>
73
+ <div style="background: var(--bg-elev); padding: 16px;">6</div>
74
+ <div style="background: var(--bg-elev); padding: 16px;">7</div>
75
+ <div style="background: var(--bg-elev); padding: 16px;">8</div>
76
+ </Story>
77
+
78
+ <Story name="Auto Fill" args={{ cols: "auto", minColWidth: "160px", gap: "sm" }}
79
+ play={async ({ canvasElement }) => {
80
+ const root = canvasElement.firstElementChild as HTMLElement;
81
+ const style = getComputedStyle(root);
82
+ await expect(style.display).toBe("grid");
83
+ const templateCols = root.style.gridTemplateColumns;
84
+ await expect(
85
+ templateCols.includes("auto-fill") || templateCols.includes("minmax")
86
+ ).toBe(true);
87
+ // minColWidth="160px" should be reflected in the template string
88
+ await expect(templateCols.includes("160px")).toBe(true);
89
+ }}>
90
+ <TagPill>Alpha</TagPill>
91
+ <TagPill variant="amber">Beta</TagPill>
92
+ <TagPill variant="cyan">Gamma</TagPill>
93
+ <TagPill>Delta</TagPill>
94
+ <TagPill variant="amber">Epsilon</TagPill>
95
+ <TagPill>Zeta</TagPill>
96
+ </Story>
97
+
98
+ <Story name="Single Column" args={{ cols: 1, gap: "md" }}
99
+ play={async ({ canvasElement }) => {
100
+ const root = canvasElement.firstElementChild as HTMLElement;
101
+ const style = getComputedStyle(root);
102
+ await expect(style.display).toBe("grid");
103
+ const templateCols = root.style.gridTemplateColumns;
104
+ await expect(templateCols).toBe("1fr");
105
+ }}>
106
+ <div style="background: var(--bg-elev); padding: 16px;">Row 1</div>
107
+ <div style="background: var(--bg-elev); padding: 16px;">Row 2</div>
108
+ <div style="background: var(--bg-elev); padding: 16px;">Row 3</div>
109
+ </Story>