@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,316 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within, waitFor } from "storybook/test";
4
+ import {
5
+ resolveTokenColor,
6
+ resolveTokenFgColor,
7
+ } from "../../storybook-utils.js";
8
+ import Accordion from "./Accordion.svelte";
9
+ import AccordionItem from "./AccordionItem.svelte";
10
+
11
+ const { Story } = defineMeta({
12
+ component: Accordion,
13
+ title: "Data/Accordion",
14
+ tags: ["autodocs"],
15
+ });
16
+ </script>
17
+
18
+ <!-- AC-20: "Default" story — three items, first open -->
19
+ <Story
20
+ name="Default"
21
+ play={async ({ canvasElement }) => {
22
+ const canvas = within(canvasElement);
23
+
24
+ // All summaries are visible (AC-3: summary is first child of details)
25
+ const summaries = canvasElement.querySelectorAll("summary.acc-trigger");
26
+ await expect(summaries.length).toBe(3);
27
+ for (const s of summaries) {
28
+ await expect(s).toBeVisible();
29
+ }
30
+
31
+ // AC-7: first <details> has open attribute on initial render
32
+ const allDetails = canvasElement.querySelectorAll("details.acc-item");
33
+ await expect(allDetails.length).toBe(3);
34
+ await expect(allDetails[0].hasAttribute("open")).toBe(true);
35
+
36
+ // AC-8: second and third items do NOT have open attribute
37
+ await expect(allDetails[1].hasAttribute("open")).toBe(false);
38
+ await expect(allDetails[2].hasAttribute("open")).toBe(false);
39
+
40
+ // AC-1: Accordion root has class accordion
41
+ const accordionRoot = canvasElement.querySelector(".accordion");
42
+ await expect(accordionRoot).not.toBeNull();
43
+
44
+ // AC-2: AccordionItem root has class acc-item
45
+ await expect(allDetails[0]).toHaveClass("acc-item");
46
+
47
+ // AC-4: summary contains acc-title span with label text
48
+ const firstTitle = allDetails[0].querySelector(".acc-title");
49
+ await expect(firstTitle).not.toBeNull();
50
+ await expect(firstTitle!.textContent).toContain("Getting Started");
51
+
52
+ // AC-5: summary contains acc-icon span with › glyph, aria-hidden="true"
53
+ const firstIcon = allDetails[0].querySelector(".acc-icon");
54
+ await expect(firstIcon).not.toBeNull();
55
+ await expect(firstIcon!.textContent!.trim()).toBe("›");
56
+ await expect(firstIcon!.getAttribute("aria-hidden")).toBe("true");
57
+
58
+ // AC-6: body content is in .acc-body sibling of summary
59
+ const firstBody = allDetails[0].querySelector(".acc-body");
60
+ await expect(firstBody).not.toBeNull();
61
+ await waitFor(() => expect(firstBody).toBeVisible());
62
+
63
+ // AC-10: body content of open item is visible
64
+ await waitFor(() =>
65
+ expect(
66
+ canvas.getByText(/Start by reading the quick-start guide/),
67
+ ).toBeVisible(),
68
+ );
69
+
70
+ // AC-17: summary has list-style: none (no browser disclosure triangle)
71
+ const firstSummary = allDetails[0].querySelector("summary") as HTMLElement;
72
+ await expect(getComputedStyle(firstSummary).listStyleType).toBe("none");
73
+ }}
74
+ >
75
+ <AccordionItem label="Getting Started" open={true}>
76
+ <p>
77
+ Start by reading the quick-start guide and familiarising yourself with the
78
+ token set.
79
+ </p>
80
+ </AccordionItem>
81
+ <AccordionItem label="Installation">
82
+ <p>Run pnpm install to set up all dependencies.</p>
83
+ </AccordionItem>
84
+ <AccordionItem label="Configuration">
85
+ <p>Edit the tokens file to match your brand palette.</p>
86
+ </AccordionItem>
87
+ </Story>
88
+
89
+ <!-- All items closed -->
90
+ <Story
91
+ name="All Closed"
92
+ play={async ({ canvasElement }) => {
93
+ // AC-8: all details elements lack open attribute
94
+ const allDetails = canvasElement.querySelectorAll("details.acc-item");
95
+ await expect(allDetails.length).toBe(3);
96
+ for (const d of allDetails) {
97
+ await expect(d.hasAttribute("open")).toBe(false);
98
+ }
99
+ }}
100
+ >
101
+ <AccordionItem label="Section Alpha">
102
+ <p>Content for Alpha.</p>
103
+ </AccordionItem>
104
+ <AccordionItem label="Section Beta">
105
+ <p>Content for Beta.</p>
106
+ </AccordionItem>
107
+ <AccordionItem label="Section Gamma">
108
+ <p>Content for Gamma.</p>
109
+ </AccordionItem>
110
+ </Story>
111
+
112
+ <!-- AC-21: "Toggle Interaction" story — click to open, click again to close -->
113
+ <Story
114
+ name="Toggle Interaction"
115
+ play={async ({ canvasElement, userEvent }) => {
116
+ const details = canvasElement.querySelector(
117
+ "details.acc-item",
118
+ ) as HTMLElement;
119
+ await expect(details).not.toBeNull();
120
+
121
+ // AC-8: starts closed
122
+ await expect(details.hasAttribute("open")).toBe(false);
123
+
124
+ // AC-11 / AC-12 / AC-13: check icon color and trigger bg when CLOSED
125
+ const icon = details.querySelector(".acc-icon") as HTMLElement;
126
+ const faintColor = resolveTokenFgColor("--ink-faint");
127
+ await expect(getComputedStyle(icon).color).toBe(faintColor);
128
+
129
+ // AC-9: click summary → details gains open attribute
130
+ const summary = details.querySelector("summary.acc-trigger") as HTMLElement;
131
+ await userEvent.click(summary);
132
+ await expect(details.hasAttribute("open")).toBe(true);
133
+
134
+ // AC-10: body content is visible after opening
135
+ const bodyText = within(canvasElement).getByText(/Interact with this item/);
136
+ await waitFor(() => expect(bodyText).toBeVisible());
137
+
138
+ // AC-11: icon has transform applied when open
139
+ await expect(getComputedStyle(icon).transform).not.toBe("none");
140
+
141
+ // AC-12: icon color resolves to var(--amber) when open
142
+ const amberColor = resolveTokenFgColor("--amber");
143
+ await expect(getComputedStyle(icon).color).toBe(amberColor);
144
+
145
+ // AC-13: trigger background resolves to var(--bg-elev) when open
146
+ const openTrigger = details.querySelector(".acc-trigger") as HTMLElement;
147
+ const bgElev = resolveTokenColor("--bg-elev");
148
+ await waitFor(() =>
149
+ expect(getComputedStyle(openTrigger).backgroundColor).toBe(bgElev),
150
+ );
151
+
152
+ // AC-9: click again → details loses open attribute
153
+ await userEvent.click(summary);
154
+ await expect(details.hasAttribute("open")).toBe(false);
155
+ }}
156
+ >
157
+ <AccordionItem label="Toggle Me">
158
+ <p>Interact with this item to test toggle behaviour.</p>
159
+ </AccordionItem>
160
+ </Story>
161
+
162
+ <!-- "Rich Body" — open item with structured content in the body -->
163
+ <Story
164
+ name="Rich Body"
165
+ play={async ({ canvasElement }) => {
166
+ // AC-6: body content rendered inside .acc-body
167
+ const body = canvasElement.querySelector(".acc-body");
168
+ await expect(body).not.toBeNull();
169
+ await waitFor(() => expect(body).toBeVisible());
170
+
171
+ // Body content is accessible
172
+ await waitFor(() =>
173
+ expect(within(canvasElement).getByText("VDD")).toBeVisible(),
174
+ );
175
+ await expect(within(canvasElement).getByText("+3.3V")).toBeVisible();
176
+ await expect(within(canvasElement).getByText("VCC")).toBeVisible();
177
+ await expect(within(canvasElement).getByText("+5V")).toBeVisible();
178
+ }}
179
+ >
180
+ <AccordionItem label="Power Rails" open={true}>
181
+ <dl
182
+ style="display:grid;grid-template-columns:1fr 1fr;gap:4px 16px;margin:0;"
183
+ >
184
+ <dt>VDD</dt>
185
+ <dd>+3.3V</dd>
186
+ <dt>VCC</dt>
187
+ <dd>+5V</dd>
188
+ <dt>VBUS</dt>
189
+ <dd>+12V</dd>
190
+ </dl>
191
+ </AccordionItem>
192
+ </Story>
193
+
194
+ <!-- AC-16: Accordion wrapper has 1px solid border -->
195
+ <Story
196
+ name="Wrapper Border"
197
+ play={async ({ canvasElement }) => {
198
+ const accordionRoot = canvasElement.querySelector(
199
+ ".accordion",
200
+ ) as HTMLElement;
201
+ await expect(accordionRoot).not.toBeNull();
202
+
203
+ const ruleColor = resolveTokenFgColor("--rule");
204
+ const style = getComputedStyle(accordionRoot);
205
+ await expect(style.borderTopStyle).toBe("solid");
206
+ await expect(style.borderTopWidth).toBe("1px");
207
+ await expect(style.borderTopColor).toBe(ruleColor);
208
+ }}
209
+ >
210
+ <AccordionItem label="Border Test">
211
+ <p>The accordion wrapper should have a 1px solid rule border.</p>
212
+ </AccordionItem>
213
+ </Story>
214
+
215
+ <!-- AC-14 / AC-15: last item has no border-bottom; non-last items do -->
216
+ <Story
217
+ name="Item Borders"
218
+ play={async ({ canvasElement }) => {
219
+ const allDetails = canvasElement.querySelectorAll("details.acc-item");
220
+ await expect(allDetails.length).toBe(3);
221
+
222
+ // AC-15: non-last items have a bottom border
223
+ const firstStyle = getComputedStyle(allDetails[0] as HTMLElement);
224
+ await expect(firstStyle.borderBottomStyle).toBe("solid");
225
+ await expect(firstStyle.borderBottomWidth).toBe("1px");
226
+
227
+ // AC-14: last item has no bottom border
228
+ const lastStyle = getComputedStyle(allDetails[2] as HTMLElement);
229
+ await expect(lastStyle.borderBottomWidth).toBe("0px");
230
+ }}
231
+ >
232
+ <AccordionItem label="First">
233
+ <p>First item.</p>
234
+ </AccordionItem>
235
+ <AccordionItem label="Second">
236
+ <p>Second item.</p>
237
+ </AccordionItem>
238
+ <AccordionItem label="Last">
239
+ <p>Last item — no bottom border.</p>
240
+ </AccordionItem>
241
+ </Story>
242
+
243
+ <!-- AC 41 / AC 42: "Animated" story — verifies DOM toggle behaviour of CSS animation -->
244
+ <Story
245
+ name="Animated"
246
+ play={async ({ canvasElement, userEvent }) => {
247
+ // AC 35: <details> starts without the open attribute (closed)
248
+ const details = canvasElement.querySelector(
249
+ "details.acc-item",
250
+ ) as HTMLElement;
251
+ await expect(details.hasAttribute("open")).toBe(false);
252
+
253
+ // AC 35: .acc-body is present in the DOM regardless of open state
254
+ const accBody = details.querySelector(".acc-body") as HTMLElement;
255
+ await expect(accBody).not.toBeNull();
256
+
257
+ // AC 36: click summary → <details> gains open attribute
258
+ const summary = details.querySelector("summary.acc-trigger") as HTMLElement;
259
+ await userEvent.click(summary);
260
+ await expect(details.hasAttribute("open")).toBe(true);
261
+
262
+ // AC 36: after opening, .acc-body is visible
263
+ await waitFor(() => expect(accBody).toBeVisible());
264
+
265
+ // AC 37: click summary again → <details> loses open attribute
266
+ await userEvent.click(summary);
267
+ await expect(details.hasAttribute("open")).toBe(false);
268
+ }}
269
+ >
270
+ <AccordionItem label="CSS Height Animation">
271
+ <p>
272
+ This item uses a CSS-only height transition via interpolate-size and
273
+ @starting-style.
274
+ </p>
275
+ </AccordionItem>
276
+ </Story>
277
+
278
+ <!-- B27 AC-20: .accordion Stack root must have no style="border: 1px solid var(--rule)" attribute -->
279
+ <Story
280
+ name="No Inline Border Style"
281
+ play={async ({ canvasElement }) => {
282
+ // Before B27: <Stack style="border: 1px solid var(--rule);"> carries a style= attribute.
283
+ // After B27: the border moves to scoped CSS on .accordion; no style= attribute on the root.
284
+ const accordionRoot = canvasElement.querySelector(
285
+ ".accordion",
286
+ ) as HTMLElement;
287
+ await expect(accordionRoot.getAttribute("style")).toBeNull();
288
+ }}
289
+ >
290
+ <AccordionItem label="Border Style Test">
291
+ <p>The accordion root must not carry an inline style= attribute.</p>
292
+ </AccordionItem>
293
+ </Story>
294
+
295
+ <!-- AC-18: Accordion forwards ...rest attributes to root div -->
296
+ <Story
297
+ name="Attribute Forwarding"
298
+ args={{
299
+ id: "test-accordion",
300
+ "aria-label": "Settings sections",
301
+ }}
302
+ play={async ({ canvasElement }) => {
303
+ const accordionRoot = canvasElement.querySelector(
304
+ ".accordion",
305
+ ) as HTMLElement;
306
+ await expect(accordionRoot).not.toBeNull();
307
+ await expect(accordionRoot.getAttribute("id")).toBe("test-accordion");
308
+ await expect(accordionRoot.getAttribute("aria-label")).toBe(
309
+ "Settings sections",
310
+ );
311
+ }}
312
+ >
313
+ <AccordionItem label="REST Forwarding">
314
+ <p>The wrapper must forward id and aria-label.</p>
315
+ </AccordionItem>
316
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Accordion from "./Accordion.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 Accordion: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Accordion = InstanceType<typeof Accordion>;
19
+ export default Accordion;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
6
+ children: Snippet
7
+ [key: string]: unknown
8
+ }
9
+
10
+ let { children, ...rest }: AccordionProps = $props()
11
+ </script>
12
+
13
+ <div class="accordion" {...rest}>
14
+ {@render children()}
15
+ </div>
16
+
17
+ <style>
18
+ .accordion {
19
+ display: flex;
20
+ flex-direction: column;
21
+ border: 1px solid var(--rule);
22
+ }
23
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
4
+ children: Snippet;
5
+ [key: string]: unknown;
6
+ }
7
+ declare const Accordion: import("svelte").Component<AccordionProps, {}, "">;
8
+ type Accordion = ReturnType<typeof Accordion>;
9
+ export default Accordion;
@@ -0,0 +1,112 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface AccordionItemProps {
5
+ /** Summary / trigger text for the accordion row. */
6
+ label: string
7
+ /** Whether the item starts expanded. @default false */
8
+ open?: boolean
9
+ children: Snippet
10
+ }
11
+
12
+ let { label, open = false, children }: AccordionItemProps = $props()
13
+ </script>
14
+
15
+ <details class="acc-item" {open}>
16
+ <summary class="acc-trigger">
17
+ <span class="acc-title">{label}</span>
18
+ <span class="acc-icon" aria-hidden="true">›</span>
19
+ </summary>
20
+ <div class="acc-body">
21
+ {@render children()}
22
+ </div>
23
+ </details>
24
+
25
+ <style>
26
+ .acc-item {
27
+ border-bottom: 1px solid var(--rule);
28
+ }
29
+
30
+ .acc-item:last-child {
31
+ border-bottom: none;
32
+ }
33
+
34
+ .acc-trigger {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+ padding: 12px 16px;
39
+ cursor: pointer;
40
+ background: var(--bg-rail);
41
+ transition: background var(--transition);
42
+ user-select: none;
43
+ list-style: none;
44
+ }
45
+
46
+ .acc-trigger::-webkit-details-marker {
47
+ display: none;
48
+ }
49
+
50
+ .acc-trigger:hover {
51
+ background: var(--bg-elev);
52
+ }
53
+
54
+ details[open] .acc-trigger {
55
+ background: var(--bg-elev);
56
+ }
57
+
58
+ .acc-title {
59
+ font-family: var(--mono);
60
+ font-size: 12px;
61
+ letter-spacing: 0.08em;
62
+ text-transform: uppercase;
63
+ color: var(--ink);
64
+ }
65
+
66
+ .acc-icon {
67
+ font-family: var(--mono);
68
+ font-size: 14px;
69
+ color: var(--ink-faint);
70
+ transition: transform var(--transition);
71
+ flex-shrink: 0;
72
+ }
73
+
74
+ details[open] .acc-icon {
75
+ transform: rotate(90deg);
76
+ color: var(--amber);
77
+ }
78
+
79
+ .acc-body {
80
+ padding: 14px 16px;
81
+ font-size: 13px;
82
+ line-height: 1.6;
83
+ color: var(--ink-dim);
84
+ border-top: 1px solid var(--rule);
85
+ background: var(--bg-sunken, var(--bg));
86
+ }
87
+
88
+ @supports (interpolate-size: allow-keywords) {
89
+ details.acc-item {
90
+ interpolate-size: allow-keywords;
91
+ }
92
+
93
+ .acc-body {
94
+ overflow: hidden;
95
+ height: 0;
96
+ transition: height var(--transition), opacity var(--transition);
97
+ opacity: 0;
98
+ }
99
+
100
+ details[open] .acc-body {
101
+ height: auto;
102
+ opacity: 1;
103
+ }
104
+
105
+ @starting-style {
106
+ details[open] .acc-body {
107
+ height: 0;
108
+ opacity: 0;
109
+ }
110
+ }
111
+ }
112
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface AccordionItemProps {
3
+ /** Summary / trigger text for the accordion row. */
4
+ label: string;
5
+ /** Whether the item starts expanded. @default false */
6
+ open?: boolean;
7
+ children: Snippet;
8
+ }
9
+ declare const AccordionItem: import("svelte").Component<AccordionItemProps, {}, "">;
10
+ type AccordionItem = ReturnType<typeof AccordionItem>;
11
+ export default AccordionItem;
@@ -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 Table from "./Table.svelte";
5
+ import TagPill from "../primitives/TagPill.svelte";
6
+
7
+ // No component: — this file covers the custom-rows (children snippet) story
8
+ // which renders <Table> directly in the slot with <tr> children containing
9
+ // sub-components (TagPill). Cannot use component: Table here because
10
+ // the children prop is a Snippet and cannot be passed via args.
11
+ const { Story } = defineMeta({
12
+ title: "Data/Table Composition",
13
+ tags: ["autodocs"],
14
+ });
15
+ </script>
16
+
17
+ <!-- AC-66: "Custom rows (snippet)" — children prop overrides rows; TagPill renders inside a cell -->
18
+ <Story name="Custom rows (snippet)"
19
+ play={async ({ canvasElement }) => {
20
+ const canvas = within(canvasElement);
21
+
22
+ // AC-51: table element exists
23
+ const table = canvas.getByRole("table");
24
+ await expect(table).toBeVisible();
25
+
26
+ // AC-66: children snippet renders inside tbody; rows prop is ignored
27
+ const tbody = canvasElement.querySelector("tbody") as HTMLElement;
28
+ await expect(tbody).not.toBeNull();
29
+ const rows = tbody.querySelectorAll("tr");
30
+ await expect(rows.length).toBe(2);
31
+
32
+ // TagPill text is visible inside a cell (AC-66: snippet slot renders)
33
+ await expect(canvas.getByText("Power")).toBeVisible();
34
+ await expect(canvas.getByText("Envelope")).toBeVisible();
35
+
36
+ // Column headers still present
37
+ await expect(canvas.getByRole("columnheader", { name: /id/i })).toBeVisible();
38
+ await expect(canvas.getByRole("columnheader", { name: /product/i })).toBeVisible();
39
+ await expect(canvas.getByRole("columnheader", { name: /category/i })).toBeVisible();
40
+
41
+ // rows prop data should NOT appear (children overrides rows)
42
+ const ignoredCells = canvasElement.querySelectorAll("td");
43
+ let foundIgnored = false;
44
+ for (const td of ignoredCells) {
45
+ if (td.textContent?.includes("IGNORED-ROW")) {
46
+ foundIgnored = true;
47
+ break;
48
+ }
49
+ }
50
+ await expect(foundIgnored).toBe(false);
51
+ }}>
52
+ <Table
53
+ headers={['ID', 'Product', 'Category']}
54
+ rows={[['IGNORED-ROW', 'Should not appear', 'None']]}
55
+ >
56
+ <tr>
57
+ <td>CONDUIT-PDX2</td>
58
+ <td>Conduit PDX-2</td>
59
+ <td><TagPill>Power</TagPill></td>
60
+ </tr>
61
+ <tr>
62
+ <td>DISTRANS-AR1</td>
63
+ <td>Distrans AR-1</td>
64
+ <td><TagPill variant="amber">Envelope</TagPill></td>
65
+ </tr>
66
+ </Table>
67
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Table from "./Table.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 Table: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Table = InstanceType<typeof Table>;
19
+ export default Table;