@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,137 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import { resolveTokenColor, resolveTokenFgColor } from "../../storybook-utils.js";
5
+ import Table from "./Table.svelte";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Data/Table",
9
+ component: Table,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="Basic"
15
+ args={{
16
+ headers: ['ID', 'Product', 'Category'],
17
+ rows: [
18
+ ['CONDUIT-PDX2', 'Conduit PDX-2', 'Power'],
19
+ ['DISTRANS-AR1', 'Distrans AR-1', 'Envelope'],
20
+ ]
21
+ }}
22
+ play={async ({ canvasElement }) => {
23
+ const canvas = within(canvasElement);
24
+
25
+ const table = canvas.getByRole("table");
26
+ await expect(table).toBeVisible();
27
+ await expect(table).toHaveClass("dxl-table");
28
+
29
+ const headers = canvas.getAllByRole("columnheader");
30
+ await expect(headers.length).toBe(3);
31
+ await expect(canvas.getByRole("columnheader", { name: /id/i })).toBeVisible();
32
+ await expect(canvas.getByRole("columnheader", { name: /product/i })).toBeVisible();
33
+ await expect(canvas.getByRole("columnheader", { name: /category/i })).toBeVisible();
34
+
35
+ await expect(canvas.getByRole("cell", { name: "CONDUIT-PDX2" })).toBeVisible();
36
+ await expect(canvas.getByRole("cell", { name: "Conduit PDX-2" })).toBeVisible();
37
+ await expect(canvas.getByRole("cell", { name: "Power" })).toBeVisible();
38
+ await expect(canvas.getByRole("cell", { name: "DISTRANS-AR1" })).toBeVisible();
39
+
40
+ const allTh = canvasElement.querySelectorAll("th");
41
+ await expect(allTh.length).toBe(3);
42
+ const allTd = canvasElement.querySelectorAll("td");
43
+ await expect(allTd.length).toBe(6);
44
+
45
+ await expect(getComputedStyle(table).borderCollapse).toBe("collapse");
46
+
47
+ const fontFamily = getComputedStyle(table).fontFamily.toLowerCase();
48
+ await expect(fontFamily.includes("jetbrains") || fontFamily.includes("mono")).toBe(true);
49
+ }} />
50
+
51
+ <Story name="With Caption"
52
+ args={{
53
+ headers: ['ID', 'Product', 'Category'],
54
+ rows: [['CONDUIT-PDX2', 'Conduit PDX-2', 'Power']],
55
+ caption: 'Product inventory',
56
+ }}
57
+ play={async ({ canvasElement }) => {
58
+ const caption = canvasElement.querySelector("caption");
59
+ await expect(caption).not.toBeNull();
60
+ await expect(caption!.textContent!.trim()).toBe("Product inventory");
61
+ }} />
62
+
63
+ <Story name="Empty rows"
64
+ args={{
65
+ headers: ['Col A', 'Col B'],
66
+ }}
67
+ play={async ({ canvasElement }) => {
68
+ const canvas = within(canvasElement);
69
+
70
+ await expect(canvas.getByRole("columnheader", { name: /col a/i })).toBeVisible();
71
+ await expect(canvas.getByRole("columnheader", { name: /col b/i })).toBeVisible();
72
+
73
+ const tbody = canvasElement.querySelector("tbody");
74
+ await expect(tbody).not.toBeNull();
75
+ const rows = tbody!.querySelectorAll("tr");
76
+ await expect(rows.length).toBe(0);
77
+
78
+ const caption = canvasElement.querySelector("caption");
79
+ await expect(caption).toBeNull();
80
+ }} />
81
+
82
+ <Story name="Header cell styles"
83
+ args={{
84
+ headers: ['Name', 'Value'],
85
+ rows: [['Alpha', '1']],
86
+ }}
87
+ play={async ({ canvasElement }) => {
88
+ const canvas = within(canvasElement);
89
+ const th = canvas.getByRole("columnheader", { name: /name/i });
90
+ const style = getComputedStyle(th);
91
+
92
+ await expect(style.textTransform).toBe("uppercase");
93
+ await expect(style.fontWeight).toBe("700");
94
+
95
+ const inkFaint = resolveTokenFgColor("--ink-faint");
96
+ await expect(style.color).toBe(inkFaint);
97
+
98
+ const ruleStrong = resolveTokenFgColor("--rule-strong");
99
+ await expect(style.borderBottomColor).toBe(ruleStrong);
100
+ await expect(style.borderBottomStyle).toBe("solid");
101
+
102
+ await expect(style.fontSize).toBe("10px");
103
+ await expect(style.letterSpacing).toBe("1px");
104
+ }} />
105
+
106
+ <Story name="Body cell styles"
107
+ args={{
108
+ headers: ['Name', 'Value'],
109
+ rows: [['Alpha', '1']],
110
+ }}
111
+ play={async ({ canvasElement }) => {
112
+ const td = canvasElement.querySelector("td");
113
+ await expect(td).not.toBeNull();
114
+ const style = getComputedStyle(td!);
115
+
116
+ const inkDim = resolveTokenFgColor("--ink-dim");
117
+ await expect(style.color).toBe(inkDim);
118
+
119
+ await expect(style.verticalAlign).toBe("middle");
120
+
121
+ await expect(style.borderBottomStyle).toBe("dashed");
122
+ await expect(style.borderBottomWidth).toBe("1px");
123
+
124
+ // On hover: tbody tr:hover td { background: var(--bg-rail) } — visual documentation only;
125
+ // CSS :hover pseudo-class is not reliably triggered in Vitest browser mode.
126
+ }} />
127
+
128
+ <Story name="Attribute forwarding"
129
+ args={{
130
+ headers: ['Col'],
131
+ 'aria-label': 'Test table',
132
+ }}
133
+ play={async ({ canvasElement }) => {
134
+ const table = canvasElement.querySelector("table.dxl-table");
135
+ await expect(table).not.toBeNull();
136
+ await expect(table!.getAttribute("aria-label")).toBe("Test table");
137
+ }} />
@@ -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;
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import type { HTMLTableAttributes } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ interface TableProps extends HTMLTableAttributes {
6
+ /** Column header labels. */
7
+ headers: string[]
8
+ /** Data rows — each inner array must match the length of `headers`. */
9
+ rows?: string[][]
10
+ children?: Snippet
11
+ /** Optional `<caption>` text for the table. */
12
+ caption?: string
13
+ }
14
+
15
+ let { headers, rows, children, caption, ...rest }: TableProps = $props()
16
+ </script>
17
+
18
+ <table class="dxl-table" {...rest}>
19
+ {#if caption}
20
+ <caption class="dxl-table-caption">{caption}</caption>
21
+ {/if}
22
+ <thead>
23
+ <tr>
24
+ {#each headers as header}
25
+ <th scope="col">{header}</th>
26
+ {/each}
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ {#if children}
31
+ {@render children()}
32
+ {:else if rows}
33
+ {#each rows as row}
34
+ <tr>
35
+ {#each row as cell}
36
+ <td>{cell}</td>
37
+ {/each}
38
+ </tr>
39
+ {/each}
40
+ {/if}
41
+ </tbody>
42
+ </table>
43
+
44
+ <style>
45
+ .dxl-table {
46
+ width: 100%;
47
+ border-collapse: collapse;
48
+ font-family: var(--mono);
49
+ }
50
+
51
+ .dxl-table-caption {
52
+ font-size: 10px;
53
+ letter-spacing: 0.1em;
54
+ text-transform: uppercase;
55
+ color: var(--ink-faint);
56
+ text-align: left;
57
+ padding-bottom: 6px;
58
+ }
59
+
60
+ .dxl-table thead th {
61
+ text-align: left;
62
+ padding: 8px 12px;
63
+ border-bottom: 1px solid var(--rule-strong);
64
+ font-size: 10px;
65
+ letter-spacing: 0.1em;
66
+ text-transform: uppercase;
67
+ color: var(--ink-faint);
68
+ font-weight: 700;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .dxl-table tbody td {
73
+ padding: 8px 12px;
74
+ border-bottom: 1px dashed var(--rule);
75
+ color: var(--ink-dim);
76
+ vertical-align: middle;
77
+ font-size: 12px;
78
+ }
79
+
80
+ .dxl-table tbody tr:hover td {
81
+ background: var(--bg-rail);
82
+ }
83
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { HTMLTableAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ interface TableProps extends HTMLTableAttributes {
4
+ /** Column header labels. */
5
+ headers: string[];
6
+ /** Data rows — each inner array must match the length of `headers`. */
7
+ rows?: string[][];
8
+ children?: Snippet;
9
+ /** Optional `<caption>` text for the table. */
10
+ caption?: string;
11
+ }
12
+ declare const Table: import("svelte").Component<TableProps, {}, "">;
13
+ type Table = ReturnType<typeof Table>;
14
+ export default Table;
@@ -0,0 +1,386 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import { resolveTokenColor, resolveTokenFgColor } from "../../storybook-utils.js";
5
+ import Tabs from "./Tabs.svelte";
6
+
7
+ // No component: — Tabs requires Snippet values for tab.panel,
8
+ // which cannot be passed through plain args. Full <Tabs> is rendered
9
+ // in each story slot (composition pattern).
10
+ const { Story } = defineMeta({
11
+ title: "Data/Tabs",
12
+ tags: ["autodocs"],
13
+ });
14
+ </script>
15
+
16
+ <!-- AC-46: "Underline" story — tablist, aria-selected, panel visibility -->
17
+ <Story name="Underline"
18
+ play={async ({ canvasElement }) => {
19
+ const canvas = within(canvasElement);
20
+
21
+ // AC-25: tab bar has role="tablist"
22
+ const tablist = canvas.getByRole("tablist");
23
+ await expect(tablist).toBeVisible();
24
+
25
+ // AC-24: root div has class tabs and tabs--underline
26
+ const root = canvasElement.querySelector(".tabs") as HTMLElement;
27
+ await expect(root).not.toBeNull();
28
+ await expect(root).toHaveClass("tabs--underline");
29
+
30
+ // AC-26: each tab is a button with role="tab"
31
+ const tabs = canvas.getAllByRole("tab");
32
+ await expect(tabs.length).toBe(3);
33
+
34
+ // AC-29: first tab (Overview) is active by default
35
+ const overviewTab = canvas.getByRole("tab", { name: /overview/i });
36
+ await expect(overviewTab).toHaveAttribute("aria-selected", "true");
37
+
38
+ // AC-30: other tabs are inactive
39
+ const specsTab = canvas.getByRole("tab", { name: /specs/i });
40
+ await expect(specsTab).toHaveAttribute("aria-selected", "false");
41
+ const notesTab = canvas.getByRole("tab", { name: /notes/i });
42
+ await expect(notesTab).toHaveAttribute("aria-selected", "false");
43
+
44
+ // AC-27: each tab has id="tab-{id}"
45
+ await expect(overviewTab.getAttribute("id")).toBe("tab-overview");
46
+ await expect(specsTab.getAttribute("id")).toBe("tab-specs");
47
+
48
+ // AC-28: each tab has aria-controls="panel-{id}"
49
+ await expect(overviewTab.getAttribute("aria-controls")).toBe("panel-overview");
50
+
51
+ // AC-31: panels have role="tabpanel", id, aria-labelledby
52
+ const overviewPanel = canvasElement.querySelector("#panel-overview") as HTMLElement;
53
+ await expect(overviewPanel).not.toBeNull();
54
+ await expect(overviewPanel.getAttribute("role")).toBe("tabpanel");
55
+ await expect(overviewPanel.getAttribute("aria-labelledby")).toBe("tab-overview");
56
+
57
+ // AC-32: active panel is visible (no hidden attribute)
58
+ await expect(overviewPanel.hasAttribute("hidden")).toBe(false);
59
+ await expect(within(overviewPanel).getByText(/Overview panel content/)).toBeVisible();
60
+
61
+ // AC-33: inactive panels have hidden attribute
62
+ const specsPanel = canvasElement.querySelector("#panel-specs") as HTMLElement;
63
+ await expect(specsPanel.hasAttribute("hidden")).toBe(true);
64
+ const notesPanel = canvasElement.querySelector("#panel-notes") as HTMLElement;
65
+ await expect(notesPanel.hasAttribute("hidden")).toBe(true);
66
+ }}>
67
+ {#snippet children()}
68
+ {#snippet overviewPanel()}
69
+ <p>Overview panel content — introducing the system.</p>
70
+ {/snippet}
71
+ {#snippet specsPanel()}
72
+ <p>Technical specifications go here.</p>
73
+ {/snippet}
74
+ {#snippet notesPanel()}
75
+ <p>Design notes and rationale.</p>
76
+ {/snippet}
77
+ <Tabs tabs={[
78
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
79
+ { id: 'specs', label: 'Specs', panel: specsPanel },
80
+ { id: 'notes', label: 'Notes', panel: notesPanel },
81
+ ]} />
82
+ {/snippet}
83
+ </Story>
84
+
85
+ <!-- AC-48: "Pill variant" story -->
86
+ <Story name="Pill variant"
87
+ play={async ({ canvasElement }) => {
88
+ // AC-24: root has tabs--pill class
89
+ const root = canvasElement.querySelector(".tabs") as HTMLElement;
90
+ await expect(root).not.toBeNull();
91
+ await expect(root).toHaveClass("tabs--pill");
92
+
93
+ // AC-40: active tab background resolves to var(--amber)
94
+ const activeTab = within(canvasElement).getByRole("tab", { name: /overview/i });
95
+ await expect(activeTab).toHaveAttribute("aria-selected", "true");
96
+ const amberBg = resolveTokenColor("--amber");
97
+ await expect(getComputedStyle(activeTab).backgroundColor).toBe(amberBg);
98
+
99
+ // AC-41: active tab color resolves to var(--bg)
100
+ const bgColor = resolveTokenColor("--bg");
101
+ await expect(getComputedStyle(activeTab).color).toBe(bgColor);
102
+ }}>
103
+ {#snippet children()}
104
+ {#snippet overviewPanel()}
105
+ <p>Overview content.</p>
106
+ {/snippet}
107
+ {#snippet specsPanel()}
108
+ <p>Specs content.</p>
109
+ {/snippet}
110
+ {#snippet notesPanel()}
111
+ <p>Notes content.</p>
112
+ {/snippet}
113
+ <Tabs variant="pill" tabs={[
114
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
115
+ { id: 'specs', label: 'Specs', panel: specsPanel },
116
+ { id: 'notes', label: 'Notes', panel: notesPanel },
117
+ ]} />
118
+ {/snippet}
119
+ </Story>
120
+
121
+ <!-- AC-47: "Tab switch interaction" story -->
122
+ <Story name="Tab switch interaction"
123
+ play={async ({ canvasElement, userEvent }) => {
124
+ const canvas = within(canvasElement);
125
+
126
+ // Initial state: Overview active
127
+ const overviewTab = canvas.getByRole("tab", { name: /overview/i });
128
+ await expect(overviewTab).toHaveAttribute("aria-selected", "true");
129
+
130
+ const overviewPanel = canvasElement.querySelector("#panel-overview") as HTMLElement;
131
+ await expect(overviewPanel.hasAttribute("hidden")).toBe(false);
132
+
133
+ // AC-34: click Specs tab
134
+ const specsTab = canvas.getByRole("tab", { name: /specs/i });
135
+ await userEvent.click(specsTab);
136
+
137
+ // AC-34: Specs tab becomes aria-selected="true"
138
+ await expect(specsTab).toHaveAttribute("aria-selected", "true");
139
+
140
+ // AC-34: Overview tab loses aria-selected
141
+ await expect(overviewTab).toHaveAttribute("aria-selected", "false");
142
+
143
+ // AC-35: Specs panel becomes visible (no hidden attribute)
144
+ const specsPanel = canvasElement.querySelector("#panel-specs") as HTMLElement;
145
+ await expect(specsPanel.hasAttribute("hidden")).toBe(false);
146
+ await expect(within(specsPanel).getByText(/Technical specifications/)).toBeVisible();
147
+
148
+ // AC-35: Overview panel gains hidden attribute
149
+ await expect(overviewPanel.hasAttribute("hidden")).toBe(true);
150
+ }}>
151
+ {#snippet children()}
152
+ {#snippet overviewPanel()}
153
+ <p>This is the overview panel content.</p>
154
+ {/snippet}
155
+ {#snippet specsPanel()}
156
+ <p>Technical specifications go here.</p>
157
+ {/snippet}
158
+ {#snippet notesPanel()}
159
+ <p>Design notes and rationale.</p>
160
+ {/snippet}
161
+ <Tabs tabs={[
162
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
163
+ { id: 'specs', label: 'Specs', panel: specsPanel },
164
+ { id: 'notes', label: 'Notes', panel: notesPanel },
165
+ ]} />
166
+ {/snippet}
167
+ </Story>
168
+
169
+ <!-- AC-37: "Second tab initially active" story -->
170
+ <Story name="Second tab initially active"
171
+ play={async ({ canvasElement }) => {
172
+ const canvas = within(canvasElement);
173
+
174
+ // AC-37: second tab has aria-selected="true" on initial render
175
+ const specsTab = canvas.getByRole("tab", { name: /specs/i });
176
+ await expect(specsTab).toHaveAttribute("aria-selected", "true");
177
+
178
+ const overviewTab = canvas.getByRole("tab", { name: /overview/i });
179
+ await expect(overviewTab).toHaveAttribute("aria-selected", "false");
180
+
181
+ // AC-37: second panel is visible, first is hidden
182
+ const specsPanel = canvasElement.querySelector("#panel-specs") as HTMLElement;
183
+ await expect(specsPanel.hasAttribute("hidden")).toBe(false);
184
+
185
+ const overviewPanel = canvasElement.querySelector("#panel-overview") as HTMLElement;
186
+ await expect(overviewPanel.hasAttribute("hidden")).toBe(true);
187
+ }}>
188
+ {#snippet children()}
189
+ {#snippet overviewPanel()}
190
+ <p>Overview content.</p>
191
+ {/snippet}
192
+ {#snippet specsPanel()}
193
+ <p>Specs panel — initially active.</p>
194
+ {/snippet}
195
+ {#snippet notesPanel()}
196
+ <p>Notes content.</p>
197
+ {/snippet}
198
+ <Tabs active="specs" tabs={[
199
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
200
+ { id: 'specs', label: 'Specs', panel: specsPanel },
201
+ { id: 'notes', label: 'Notes', panel: notesPanel },
202
+ ]} />
203
+ {/snippet}
204
+ </Story>
205
+
206
+ <!-- AC-38 / AC-39: Underline active tab amber border-bottom, inactive transparent -->
207
+ <Story name="Underline active border"
208
+ play={async ({ canvasElement }) => {
209
+ const canvas = within(canvasElement);
210
+
211
+ // AC-38: active tab border-bottom-color resolves to var(--amber)
212
+ const activeTab = canvas.getByRole("tab", { name: /overview/i }) as HTMLElement;
213
+ await expect(activeTab).toHaveAttribute("aria-selected", "true");
214
+ const amberColor = resolveTokenFgColor("--amber");
215
+ await expect(getComputedStyle(activeTab).borderBottomColor).toBe(amberColor);
216
+
217
+ // AC-39: inactive tab border-bottom-color is transparent
218
+ const inactiveTab = canvas.getByRole("tab", { name: /specs/i }) as HTMLElement;
219
+ await expect(getComputedStyle(inactiveTab).borderBottomColor).toBe("rgba(0, 0, 0, 0)");
220
+ }}>
221
+ {#snippet children()}
222
+ {#snippet overviewPanel()}
223
+ <p>Overview content.</p>
224
+ {/snippet}
225
+ {#snippet specsPanel()}
226
+ <p>Specs content.</p>
227
+ {/snippet}
228
+ <Tabs tabs={[
229
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
230
+ { id: 'specs', label: 'Specs', panel: specsPanel },
231
+ ]} />
232
+ {/snippet}
233
+ </Story>
234
+
235
+ <!-- AC-42 / AC-43: Tab labels use uppercase mono font -->
236
+ <Story name="Tab label typography"
237
+ play={async ({ canvasElement }) => {
238
+ const canvas = within(canvasElement);
239
+ const tab = canvas.getByRole("tab", { name: /overview/i }) as HTMLElement;
240
+
241
+ // AC-42: text-transform: uppercase
242
+ await expect(getComputedStyle(tab).textTransform).toBe("uppercase");
243
+
244
+ // AC-43: font-family includes mono/JetBrains
245
+ const fontFamily = getComputedStyle(tab).fontFamily.toLowerCase();
246
+ await expect(fontFamily.includes("jetbrains") || fontFamily.includes("mono")).toBe(true);
247
+ }}>
248
+ {#snippet children()}
249
+ {#snippet overviewPanel()}
250
+ <p>Content.</p>
251
+ {/snippet}
252
+ {#snippet specsPanel()}
253
+ <p>Content.</p>
254
+ {/snippet}
255
+ <Tabs tabs={[
256
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
257
+ { id: 'specs', label: 'Specs', panel: specsPanel },
258
+ ]} />
259
+ {/snippet}
260
+ </Story>
261
+
262
+ <!-- B15: Keyboard Navigation story — exercises the ARIA Tabs keyboard pattern.
263
+ This story MUST FAIL until Tabs.svelte implements:
264
+ - onkeydown handler on each tab <button> for ArrowLeft / ArrowRight / Home / End
265
+ - automatic activation (arrow key moves focus AND activates the tab)
266
+ -->
267
+ <Story name="Keyboard Navigation"
268
+ play={async ({ canvasElement, userEvent }) => {
269
+ const canvas = within(canvasElement);
270
+
271
+ // Step 1: click Overview tab to ensure keyboard focus is on it
272
+ const overviewTab = canvas.getByRole("tab", { name: /overview/i });
273
+ const specsTab = canvas.getByRole("tab", { name: /specs/i });
274
+ const notesTab = canvas.getByRole("tab", { name: /notes/i });
275
+ await userEvent.click(overviewTab);
276
+ await expect(overviewTab).toHaveAttribute("aria-selected", "true");
277
+
278
+ // Step 2: ArrowRight → Specs tab becomes active
279
+ // AC-23, AC-30, AC-35 (panel switching on keyboard nav)
280
+ await userEvent.keyboard("{ArrowRight}");
281
+ await expect(specsTab).toHaveAttribute("aria-selected", "true");
282
+ await expect(overviewTab).toHaveAttribute("aria-selected", "false");
283
+ // Panel visibility follows keyboard activation
284
+ const overviewPanel = canvasElement.querySelector("#panel-overview") as HTMLElement;
285
+ const specsPanel = canvasElement.querySelector("#panel-specs") as HTMLElement;
286
+ await expect(specsPanel.hasAttribute("hidden")).toBe(false);
287
+ await expect(overviewPanel.hasAttribute("hidden")).toBe(true);
288
+
289
+ // Step 3: ArrowRight → Notes tab becomes active
290
+ await userEvent.keyboard("{ArrowRight}");
291
+ await expect(notesTab).toHaveAttribute("aria-selected", "true");
292
+ await expect(specsTab).toHaveAttribute("aria-selected", "false");
293
+
294
+ // Step 4: ArrowRight wraps to first tab (Overview)
295
+ // AC-24
296
+ await userEvent.keyboard("{ArrowRight}");
297
+ await expect(overviewTab).toHaveAttribute("aria-selected", "true");
298
+ await expect(notesTab).toHaveAttribute("aria-selected", "false");
299
+
300
+ // Step 5: End → Notes tab (last)
301
+ // AC-29
302
+ await userEvent.keyboard("{End}");
303
+ await expect(notesTab).toHaveAttribute("aria-selected", "true");
304
+ await expect(overviewTab).toHaveAttribute("aria-selected", "false");
305
+
306
+ // Step 6: Home → Overview tab (first)
307
+ // AC-28
308
+ await userEvent.keyboard("{Home}");
309
+ await expect(overviewTab).toHaveAttribute("aria-selected", "true");
310
+ await expect(notesTab).toHaveAttribute("aria-selected", "false");
311
+
312
+ // Step 7: ArrowLeft wraps from first to last (Notes)
313
+ // AC-27
314
+ await userEvent.keyboard("{ArrowLeft}");
315
+ await expect(notesTab).toHaveAttribute("aria-selected", "true");
316
+ await expect(overviewTab).toHaveAttribute("aria-selected", "false");
317
+
318
+ // AC-26: ArrowLeft non-wrap — from Notes (last) to Specs (prev)
319
+ await userEvent.keyboard("{ArrowLeft}");
320
+ await expect(specsTab).toHaveAttribute("aria-selected", "true");
321
+ await expect(notesTab).toHaveAttribute("aria-selected", "false");
322
+
323
+ // AC-31: assert exactly one tab has aria-selected="true" at this point
324
+ const allTabs = canvas.getAllByRole("tab");
325
+ const selectedTabs = allTabs.filter(t => t.getAttribute("aria-selected") === "true");
326
+ await expect(selectedTabs.length).toBe(1);
327
+ }}>
328
+ {#snippet children()}
329
+ {#snippet overviewPanel()}
330
+ <p>Overview panel</p>
331
+ {/snippet}
332
+ {#snippet specsPanel()}
333
+ <p>Specs panel</p>
334
+ {/snippet}
335
+ {#snippet notesPanel()}
336
+ <p>Notes panel</p>
337
+ {/snippet}
338
+ <Tabs tabs={[
339
+ { id: 'overview', label: 'Overview', panel: overviewPanel },
340
+ { id: 'specs', label: 'Specs', panel: specsPanel },
341
+ { id: 'notes', label: 'Notes', panel: notesPanel },
342
+ ]} />
343
+ {/snippet}
344
+ </Story>
345
+
346
+ <!-- AC-32: single-tab edge case — arrow keys stay on the only tab, no error -->
347
+ <Story name="Single Tab"
348
+ play={async ({ canvasElement, userEvent }) => {
349
+ const canvas = within(canvasElement);
350
+ const tab = canvas.getByRole("tab");
351
+ await userEvent.click(tab);
352
+ await expect(tab).toHaveAttribute("aria-selected", "true");
353
+ await userEvent.keyboard("{ArrowRight}");
354
+ await expect(tab).toHaveAttribute("aria-selected", "true");
355
+ await userEvent.keyboard("{ArrowLeft}");
356
+ await expect(tab).toHaveAttribute("aria-selected", "true");
357
+ await userEvent.keyboard("{Home}");
358
+ await expect(tab).toHaveAttribute("aria-selected", "true");
359
+ await userEvent.keyboard("{End}");
360
+ await expect(tab).toHaveAttribute("aria-selected", "true");
361
+ }}>
362
+ {#snippet children()}
363
+ {#snippet onlyPanel()}
364
+ <p>Only panel</p>
365
+ {/snippet}
366
+ <Tabs tabs={[{ id: 'only', label: 'Only', panel: onlyPanel }]} />
367
+ {/snippet}
368
+ </Story>
369
+
370
+ <!-- AC-44: Tabs forwards ...rest attributes to root div -->
371
+ <Story name="Attribute forwarding"
372
+ play={async ({ canvasElement }) => {
373
+ const root = canvasElement.querySelector(".tabs") as HTMLElement;
374
+ await expect(root).not.toBeNull();
375
+ await expect(root.getAttribute("id")).toBe("my-tabs");
376
+ await expect(root.getAttribute("aria-label")).toBe("Product information");
377
+ }}>
378
+ {#snippet children()}
379
+ {#snippet panelA()}
380
+ <p>Content A.</p>
381
+ {/snippet}
382
+ <Tabs id="my-tabs" aria-label="Product information" tabs={[
383
+ { id: 'a', label: 'Tab A', panel: panelA },
384
+ ]} />
385
+ {/snippet}
386
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Tabs from "./Tabs.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 Tabs: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Tabs = InstanceType<typeof Tabs>;
19
+ export default Tabs;