@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,80 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Rule from "./Rule.svelte";
5
+ import Stack from "./Stack.svelte";
6
+ import { resolveTokenFgColor } from "../../storybook-utils.js";
7
+
8
+ const { Story } = defineMeta({
9
+ title: "Layout/Rule",
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="All Variants"
15
+ play={async ({ canvasElement }) => {
16
+ const hrs = canvasElement.querySelectorAll("hr");
17
+ await expect(hrs.length).toBe(3);
18
+
19
+ const [solid, dashed, strong] = Array.from(hrs) as HTMLElement[];
20
+
21
+ // Solid variant
22
+ const solidStyle = getComputedStyle(solid);
23
+ await expect(solidStyle.borderTopStyle).toBe("solid");
24
+ await expect(solidStyle.borderTopWidth).toBe("1px");
25
+ const ruleColor = resolveTokenFgColor("--rule");
26
+ await expect(solidStyle.borderTopColor).toBe(ruleColor);
27
+
28
+ // Dashed variant
29
+ const dashedStyle = getComputedStyle(dashed);
30
+ await expect(dashedStyle.borderTopStyle).toBe("dashed");
31
+ await expect(dashedStyle.borderTopWidth).toBe("1px");
32
+ await expect(dashedStyle.borderTopColor).toBe(ruleColor);
33
+
34
+ // Strong variant — same style but different color from solid
35
+ const strongStyle = getComputedStyle(strong);
36
+ await expect(strongStyle.borderTopStyle).toBe("solid");
37
+ const ruleStrongColor = resolveTokenFgColor("--rule-strong");
38
+ await expect(strongStyle.borderTopColor).toBe(ruleStrongColor);
39
+ await expect(strongStyle.borderTopColor).not.toBe(ruleColor);
40
+ }}>
41
+ <Stack gap="md">
42
+ <span>Solid (default)</span>
43
+ <Rule variant="solid" />
44
+ <span>Dashed</span>
45
+ <Rule variant="dashed" />
46
+ <span>Strong</span>
47
+ <Rule variant="strong" />
48
+ </Stack>
49
+ </Story>
50
+
51
+ <Story name="In Context"
52
+ play={async ({ canvasElement }) => {
53
+ const hr = within(canvasElement).getAllByRole("separator")[0];
54
+ await expect(hr).toBeVisible();
55
+ const style = getComputedStyle(hr);
56
+ await expect(style.marginTop).toBe("0px");
57
+ await expect(style.marginBottom).toBe("0px");
58
+ await expect(style.marginLeft).toBe("0px");
59
+ await expect(style.marginRight).toBe("0px");
60
+ // aria-hidden forwarded via ...rest
61
+ const hiddenHr = canvasElement.querySelector("[data-testid='rule-hidden']") as HTMLElement;
62
+ await expect(hiddenHr.getAttribute("aria-hidden")).toBe("true");
63
+ }}>
64
+ <Stack gap="md">
65
+ <h3>Section Heading</h3>
66
+ <Rule />
67
+ <p>Body text below the rule. The rule carries no margin of its own — spacing comes from the Stack gap.</p>
68
+ <Rule aria-hidden="true" data-testid="rule-hidden" />
69
+ </Stack>
70
+ </Story>
71
+
72
+ <Story name="With Margin Override"
73
+ play={async ({ canvasElement }) => {
74
+ const hr = canvasElement.querySelector("hr")!;
75
+ const style = getComputedStyle(hr);
76
+ await expect(style.marginTop).toBe("24px");
77
+ await expect(style.marginBottom).toBe("24px");
78
+ }}>
79
+ <Rule style="margin-top: 24px; margin-bottom: 24px" />
80
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Rule from "./Rule.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 Rule: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Rule = InstanceType<typeof Rule>;
19
+ export default Rule;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+
4
+ type RuleVariant = 'solid' | 'dashed' | 'strong'
5
+
6
+ interface Props extends HTMLAttributes<HTMLHRElement> {
7
+ variant?: RuleVariant
8
+ [key: string]: unknown
9
+ }
10
+
11
+ let { variant = 'solid', ...rest }: Props = $props()
12
+ </script>
13
+
14
+ <hr class="rule" data-variant={variant} {...rest} />
15
+
16
+ <style>
17
+ .rule {
18
+ border: none;
19
+ margin: 0;
20
+ }
21
+
22
+ .rule[data-variant="solid"] {
23
+ border-top: 1px solid var(--rule);
24
+ }
25
+
26
+ .rule[data-variant="dashed"] {
27
+ border-top: 1px dashed var(--rule);
28
+ }
29
+
30
+ .rule[data-variant="strong"] {
31
+ border-top: 1px solid var(--rule-strong);
32
+ }
33
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type RuleVariant = 'solid' | 'dashed' | 'strong';
3
+ interface Props extends HTMLAttributes<HTMLHRElement> {
4
+ variant?: RuleVariant;
5
+ [key: string]: unknown;
6
+ }
7
+ declare const Rule: import("svelte").Component<Props, {}, "">;
8
+ type Rule = ReturnType<typeof Rule>;
9
+ export default Rule;
@@ -0,0 +1,118 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Spread from "./Spread.svelte";
5
+ import Button from "../primitives/Button.svelte";
6
+ import Led from "../primitives/Led.svelte";
7
+ import TagPill from "../primitives/TagPill.svelte";
8
+
9
+ const { Story } = defineMeta({
10
+ title: "Layout/Spread",
11
+ component: Spread,
12
+ tags: ["autodocs"],
13
+ });
14
+ </script>
15
+
16
+ <Story name="Section Header" args={{ "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.display).toBe("flex");
22
+ await expect(style.justifyContent).toBe("space-between");
23
+ // aria-label forwarded via ...rest
24
+ await expect(root.getAttribute("aria-label")).toBe("test-label");
25
+ }}>
26
+ <span class="mono-label">System Status</span>
27
+ <Button variant="ghost">View All →</Button>
28
+ </Story>
29
+
30
+ <Story name="Led Status Bar"
31
+ play={async ({ canvasElement }) => {
32
+ const root = canvasElement.firstElementChild as HTMLElement;
33
+ await expect(root).toBeVisible();
34
+ const style = getComputedStyle(root);
35
+ await expect(style.justifyContent).toBe("space-between");
36
+ // After B25: bare <Spread> with no gap prop defaults to none → 0px
37
+ const gap = style.gap || style.columnGap;
38
+ await expect(gap === "0px" || gap === "0").toBe(true);
39
+ }}>
40
+ <span style="display: flex; align-items: center; gap: 8px;">
41
+ <Led color="ok" aria-label="Status: ok" />
42
+ <span>All systems nominal</span>
43
+ </span>
44
+ <TagPill variant="amber">Live</TagPill>
45
+ </Story>
46
+
47
+ <Story name="With Gap"
48
+ play={async ({ canvasElement }) => {
49
+ const root = canvasElement.firstElementChild!;
50
+ await expect(getComputedStyle(root).display).toBe("flex");
51
+ await expect(getComputedStyle(root).justifyContent).toBe("space-between");
52
+ // After B25: bare <Spread> with no gap prop defaults to none → 0px
53
+ const gap = getComputedStyle(root).gap || getComputedStyle(root).columnGap;
54
+ await expect(gap === "0px" || gap === "0").toBe(true);
55
+ }}>
56
+ <span class="mono-label">Left Item</span>
57
+ <span class="mono-label">Center Item</span>
58
+ <span class="mono-label">Right Item</span>
59
+ </Story>
60
+
61
+ <Story name="Vertical Stack" args={{ as: "section", "aria-label": "stack-region" }}
62
+ play={async ({ canvasElement }) => {
63
+ const root = canvasElement.firstElementChild!;
64
+ await expect(root.tagName).toBe("SECTION");
65
+ await expect(getComputedStyle(root).display).toBe("flex");
66
+ await expect(root.getAttribute("aria-label")).toBe("stack-region");
67
+ }}>
68
+ <span>Item A</span>
69
+ <span>Item B</span>
70
+ </Story>
71
+
72
+ <!-- AC-2 / AC-3: default gap (no prop) and explicit gap="none" both resolve to 0px -->
73
+ <Story name="Gap None" args={{ gap: "none" }}
74
+ play={async ({ canvasElement }) => {
75
+ const root = canvasElement.firstElementChild as HTMLElement;
76
+ await expect(root).toBeVisible();
77
+ const gap = getComputedStyle(root).gap || getComputedStyle(root).columnGap;
78
+ await expect(gap === "0px" || gap === "0").toBe(true);
79
+ }}>
80
+ <span>Left</span>
81
+ <span>Right</span>
82
+ </Story>
83
+
84
+ <!-- AC-5: gap="sm" → var(--u2) → 16px -->
85
+ <Story name="Gap Sm" args={{ gap: "sm" }}
86
+ play={async ({ canvasElement }) => {
87
+ const root = canvasElement.firstElementChild as HTMLElement;
88
+ await expect(root).toBeVisible();
89
+ const gap = getComputedStyle(root).gap || getComputedStyle(root).columnGap;
90
+ await expect(gap.includes("16px")).toBe(true);
91
+ }}>
92
+ <span>Left</span>
93
+ <span>Right</span>
94
+ </Story>
95
+
96
+ <!-- AC-6: gap="md" → var(--u3) → 24px -->
97
+ <Story name="Gap Md" args={{ gap: "md" }}
98
+ play={async ({ canvasElement }) => {
99
+ const root = canvasElement.firstElementChild as HTMLElement;
100
+ await expect(root).toBeVisible();
101
+ const gap = getComputedStyle(root).gap || getComputedStyle(root).columnGap;
102
+ await expect(gap.includes("24px")).toBe(true);
103
+ }}>
104
+ <span>Left</span>
105
+ <span>Right</span>
106
+ </Story>
107
+
108
+ <!-- AC-7: gap="lg" → var(--u4) → 32px -->
109
+ <Story name="Gap Lg" args={{ gap: "lg" }}
110
+ play={async ({ canvasElement }) => {
111
+ const root = canvasElement.firstElementChild as HTMLElement;
112
+ await expect(root).toBeVisible();
113
+ const gap = getComputedStyle(root).gap || getComputedStyle(root).columnGap;
114
+ await expect(gap.includes("32px")).toBe(true);
115
+ }}>
116
+ <span>Left</span>
117
+ <span>Right</span>
118
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Spread from "./Spread.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 Spread: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Spread = InstanceType<typeof Spread>;
19
+ export default Spread;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { ClassValue } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ type GapSize = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
6
+
7
+ interface Props {
8
+ /** HTML element to render as. @default 'div' */
9
+ as?: string
10
+ /** Space between children. @default 'none' */
11
+ gap?: GapSize
12
+ children?: Snippet
13
+ class?: ClassValue | null
14
+ style?: string | null
15
+ [key: string]: unknown
16
+ }
17
+
18
+ let { as = 'div', gap = 'none', children, class: klass = '', ...rest }: Props = $props()
19
+ </script>
20
+
21
+ <svelte:element this={as} class={['spread', klass]} data-gap={gap} {...rest}>
22
+ {@render children?.()}
23
+ </svelte:element>
24
+
25
+ <style>
26
+ .spread {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ }
31
+
32
+ .spread[data-gap="none"] { gap: 0; }
33
+ .spread[data-gap="xs"] { gap: var(--u); }
34
+ .spread[data-gap="sm"] { gap: var(--u2); }
35
+ .spread[data-gap="md"] { gap: var(--u3); }
36
+ .spread[data-gap="lg"] { gap: var(--u4); }
37
+ .spread[data-gap="xl"] { gap: var(--u5); }
38
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { ClassValue } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ type GapSize = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
4
+ interface Props {
5
+ /** HTML element to render as. @default 'div' */
6
+ as?: string;
7
+ /** Space between children. @default 'none' */
8
+ gap?: GapSize;
9
+ children?: Snippet;
10
+ class?: ClassValue | null;
11
+ style?: string | null;
12
+ [key: string]: unknown;
13
+ }
14
+ declare const Spread: import("svelte").Component<Props, {}, "">;
15
+ type Spread = ReturnType<typeof Spread>;
16
+ export default Spread;
@@ -0,0 +1,90 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Stack from "./Stack.svelte";
5
+ import Button from "../primitives/Button.svelte";
6
+ import TagPill from "../primitives/TagPill.svelte";
7
+
8
+ const { Story } = defineMeta({
9
+ title: "Layout/Stack",
10
+ component: Stack,
11
+ tags: ["autodocs"],
12
+ });
13
+ </script>
14
+
15
+ <Story name="Default Stack" args={{ gap: "sm", "aria-label": "test-label" }}
16
+ play={async ({ canvasElement }) => {
17
+ const root = canvasElement.firstElementChild as HTMLElement;
18
+ await expect(root).toBeVisible();
19
+ const style = getComputedStyle(root);
20
+ await expect(style.display).toBe("flex");
21
+ await expect(style.flexDirection).toBe("column");
22
+ // gap="sm" → var(--u2) → 16px
23
+ const gap = style.gap || style.columnGap;
24
+ await expect(gap.includes("16px")).toBe(true);
25
+ // aria-label forwarded via ...rest
26
+ await expect(root.getAttribute("aria-label")).toBe("test-label");
27
+ }}>
28
+ <Button variant="ghost">View All Hardware →</Button>
29
+ <Button variant="ghost">View Projects →</Button>
30
+ <Button variant="ghost">Read the Docs →</Button>
31
+ </Story>
32
+
33
+ <Story name="Large Gap" args={{ gap: "lg" }}
34
+ play={async ({ canvasElement }) => {
35
+ const root = canvasElement.firstElementChild as HTMLElement;
36
+ const style = getComputedStyle(root);
37
+ const gap = style.gap || style.rowGap;
38
+ await expect(gap.includes("32px")).toBe(true);
39
+ }}>
40
+ <TagPill variant="amber">Featured</TagPill>
41
+ <TagPill>Utility</TagPill>
42
+ </Story>
43
+
44
+ <Story name="Small Gap" args={{ gap: "xs" }}
45
+ play={async ({ canvasElement }) => {
46
+ const root = canvasElement.firstElementChild as HTMLElement;
47
+ const style = getComputedStyle(root);
48
+ const gap = style.gap || style.rowGap;
49
+ await expect(gap.includes("8px")).toBe(true);
50
+ }}>
51
+ <span>Item 1</span>
52
+ <span>Item 2</span>
53
+ <span>Item 3</span>
54
+ <span>Item 4</span>
55
+ </Story>
56
+
57
+ <Story name="As Section" args={{ as: "section", gap: "md" }}
58
+ play={async ({ canvasElement }) => {
59
+ const root = canvasElement.firstElementChild as HTMLElement;
60
+ await expect(root.tagName).toBe("SECTION");
61
+ // AC-16 / AC-9: gap="md" → var(--u3) → 24px (fixed from erroneous 16px)
62
+ const style = getComputedStyle(root);
63
+ const gap = style.gap || style.rowGap;
64
+ await expect(gap.includes("24px")).toBe(true);
65
+ }}>
66
+ <h2>Section Title</h2>
67
+ <p>Section content paragraph text goes here.</p>
68
+ </Story>
69
+
70
+ <Story name="None Gap" args={{ gap: "none" }}
71
+ play={async ({ canvasElement }) => {
72
+ const root = canvasElement.firstElementChild as HTMLElement;
73
+ const style = getComputedStyle(root);
74
+ const gap = style.gap || style.rowGap;
75
+ await expect(gap === "0px" || gap === "0").toBe(true);
76
+ }}>
77
+ <Button variant="ghost">A</Button>
78
+ <Button variant="ghost">B</Button>
79
+ </Story>
80
+
81
+ <Story name="XL Gap" args={{ gap: "xl" }}
82
+ play={async ({ canvasElement }) => {
83
+ const root = canvasElement.firstElementChild as HTMLElement;
84
+ const style = getComputedStyle(root);
85
+ const gap = style.gap || style.rowGap;
86
+ await expect(gap.includes("40px")).toBe(true);
87
+ }}>
88
+ <Button variant="ghost">Top</Button>
89
+ <Button variant="ghost">Bottom</Button>
90
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Stack from "./Stack.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 Stack: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Stack = InstanceType<typeof Stack>;
19
+ export default Stack;
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import type { ClassValue } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ type GapSize = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
6
+
7
+ interface Props {
8
+ /** HTML element to render as. @default 'div' */
9
+ as?: string
10
+ /** Space between children. @default 'sm' */
11
+ gap?: GapSize
12
+ children?: Snippet
13
+ class?: ClassValue | null
14
+ style?: string | null
15
+ [key: string]: unknown
16
+ }
17
+
18
+ let { as = 'div', gap = 'sm', children, class: klass = '', ...rest }: Props = $props()
19
+ </script>
20
+
21
+ <svelte:element this={as} class={['stack', klass]} data-gap={gap} {...rest}>
22
+ {@render children?.()}
23
+ </svelte:element>
24
+
25
+ <style>
26
+ .stack {
27
+ display: flex;
28
+ flex-direction: column;
29
+ }
30
+
31
+ .stack[data-gap="none"] { gap: 0; }
32
+ .stack[data-gap="xs"] { gap: var(--u); }
33
+ .stack[data-gap="sm"] { gap: var(--u2); }
34
+ .stack[data-gap="md"] { gap: var(--u3); }
35
+ .stack[data-gap="lg"] { gap: var(--u4); }
36
+ .stack[data-gap="xl"] { gap: var(--u5); }
37
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { ClassValue } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ type GapSize = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
4
+ interface Props {
5
+ /** HTML element to render as. @default 'div' */
6
+ as?: string;
7
+ /** Space between children. @default 'sm' */
8
+ gap?: GapSize;
9
+ children?: Snippet;
10
+ class?: ClassValue | null;
11
+ style?: string | null;
12
+ [key: string]: unknown;
13
+ }
14
+ declare const Stack: import("svelte").Component<Props, {}, "">;
15
+ type Stack = ReturnType<typeof Stack>;
16
+ export default Stack;
@@ -0,0 +1,7 @@
1
+ export { default as Stack } from './Stack.svelte';
2
+ export { default as Inline } from './Inline.svelte';
3
+ export { default as Spread } from './Spread.svelte';
4
+ export { default as Grid } from './Grid.svelte';
5
+ export { default as Container } from './Container.svelte';
6
+ export { default as Rule } from './Rule.svelte';
7
+ export { default as Prose } from './Prose.svelte';
@@ -0,0 +1,7 @@
1
+ export { default as Stack } from './Stack.svelte';
2
+ export { default as Inline } from './Inline.svelte';
3
+ export { default as Spread } from './Spread.svelte';
4
+ export { default as Grid } from './Grid.svelte';
5
+ export { default as Container } from './Container.svelte';
6
+ export { default as Rule } from './Rule.svelte';
7
+ export { default as Prose } from './Prose.svelte';
@@ -0,0 +1,122 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Breadcrumb from "./Breadcrumb.svelte";
5
+
6
+ const { Story } = defineMeta({
7
+ title: "Navigation/Breadcrumb",
8
+ component: Breadcrumb,
9
+ tags: ["autodocs"],
10
+ });
11
+ </script>
12
+
13
+ <!-- AC 25: Default story — 3-crumb trail -->
14
+ <Story
15
+ name="Default"
16
+ args={{
17
+ crumbs: [
18
+ { label: "Home", href: "/" },
19
+ { label: "Catalogue", href: "/catalogue" },
20
+ { label: "Conduit PDX-2", href: "/catalogue/conduit-pdx2" },
21
+ ],
22
+ }}
23
+ play={async ({ canvasElement }) => {
24
+ // AC 5 / AC 25: <nav> with aria-label="breadcrumb" is present
25
+ const nav = within(canvasElement).getByRole("navigation", {
26
+ name: "breadcrumb",
27
+ });
28
+ await expect(nav).toBeVisible();
29
+
30
+ // AC 6: direct child is <ol>
31
+ const ol = nav.querySelector("ol");
32
+ await expect(ol).not.toBeNull();
33
+ await expect(ol!.tagName).toBe("OL");
34
+
35
+ // AC 8 / AC 25: exactly 3 <li> elements
36
+ const items = ol!.querySelectorAll("li");
37
+ await expect(items.length).toBe(3);
38
+
39
+ // AC 11 / AC 25: "Home" does NOT have aria-current
40
+ const homeLink = within(canvasElement).getByRole("link", { name: "Home" });
41
+ await expect(homeLink).not.toHaveAttribute("aria-current");
42
+
43
+ // AC 11 / AC 25: "Catalogue" does NOT have aria-current
44
+ const catalogueLink = within(canvasElement).getByRole("link", {
45
+ name: "Catalogue",
46
+ });
47
+ await expect(catalogueLink).not.toHaveAttribute("aria-current");
48
+
49
+ // AC 10 / AC 25: last crumb "Conduit PDX-2" has aria-current="page"
50
+ const currentLink = within(canvasElement).getByRole("link", {
51
+ name: "Conduit PDX-2",
52
+ });
53
+ await expect(currentLink).toHaveAttribute("aria-current", "page");
54
+
55
+ // AC 12 / AC 25: exactly 2 aria-hidden separator elements
56
+ const separators = canvasElement.querySelectorAll('[aria-hidden="true"]');
57
+ await expect(separators.length).toBe(2);
58
+
59
+ // AC 9: "Home" link href is "/"
60
+ await expect(homeLink).toHaveAttribute("href", "/");
61
+
62
+ // AC 9: "Catalogue" link href is "/catalogue"
63
+ await expect(catalogueLink).toHaveAttribute("href", "/catalogue");
64
+ }}
65
+ />
66
+
67
+ <!-- AC 26: Root Only — single crumb -->
68
+ <Story
69
+ name="Root Only"
70
+ args={{
71
+ crumbs: [{ label: "Home", href: "/" }],
72
+ }}
73
+ play={async ({ canvasElement }) => {
74
+ // AC 5: <nav> with aria-label="breadcrumb" is present
75
+ const nav = within(canvasElement).getByRole("navigation", {
76
+ name: "breadcrumb",
77
+ });
78
+ await expect(nav).toBeVisible();
79
+
80
+ // AC 15 / AC 26: exactly 1 <li> element
81
+ const items = canvasElement.querySelectorAll("li");
82
+ await expect(items.length).toBe(1);
83
+
84
+ // AC 13 / AC 26: single crumb has aria-current="page"
85
+ const homeLink = within(canvasElement).getByRole("link", { name: "Home" });
86
+ await expect(homeLink).toHaveAttribute("aria-current", "page");
87
+
88
+ // AC 14 / AC 26: zero separator elements
89
+ const separators = canvasElement.querySelectorAll('[aria-hidden="true"]');
90
+ await expect(separators.length).toBe(0);
91
+ }}
92
+ />
93
+
94
+ <!-- AC 27: Two Crumbs — intermediate depth -->
95
+ <Story
96
+ name="Two Crumbs"
97
+ args={{
98
+ crumbs: [
99
+ { label: "Home", href: "/" },
100
+ { label: "Projects", href: "/projects" },
101
+ ],
102
+ }}
103
+ play={async ({ canvasElement }) => {
104
+ // AC 27: exactly 2 <li> elements
105
+ const items = canvasElement.querySelectorAll("li");
106
+ await expect(items.length).toBe(2);
107
+
108
+ // AC 27: "Home" does NOT have aria-current
109
+ const homeLink = within(canvasElement).getByRole("link", { name: "Home" });
110
+ await expect(homeLink).not.toHaveAttribute("aria-current");
111
+
112
+ // AC 27: "Projects" has aria-current="page"
113
+ const projectsLink = within(canvasElement).getByRole("link", {
114
+ name: "Projects",
115
+ });
116
+ await expect(projectsLink).toHaveAttribute("aria-current", "page");
117
+
118
+ // AC 27: exactly 1 separator element
119
+ const separators = canvasElement.querySelectorAll('[aria-hidden="true"]');
120
+ await expect(separators.length).toBe(1);
121
+ }}
122
+ />
@@ -0,0 +1,19 @@
1
+ import Breadcrumb from "./Breadcrumb.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 Breadcrumb: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Breadcrumb = InstanceType<typeof Breadcrumb>;
19
+ export default Breadcrumb;