@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.
- package/README.md +94 -0
- package/dist/components/cards/Card.stories.svelte +82 -0
- package/dist/components/cards/Card.stories.svelte.d.ts +19 -0
- package/dist/components/cards/Card.svelte +28 -0
- package/dist/components/cards/Card.svelte.d.ts +12 -0
- package/dist/components/cards/NoteCard.stories.svelte +94 -0
- package/dist/components/cards/NoteCard.stories.svelte.d.ts +19 -0
- package/dist/components/cards/NoteCard.svelte +89 -0
- package/dist/components/cards/NoteCard.svelte.d.ts +18 -0
- package/dist/components/cards/ProductCard.stories.svelte +98 -0
- package/dist/components/cards/ProductCard.stories.svelte.d.ts +19 -0
- package/dist/components/cards/ProductCard.svelte +150 -0
- package/dist/components/cards/ProductCard.svelte.d.ts +22 -0
- package/dist/components/cards/ProjectCard.stories.svelte +88 -0
- package/dist/components/cards/ProjectCard.stories.svelte.d.ts +19 -0
- package/dist/components/cards/ProjectCard.svelte +109 -0
- package/dist/components/cards/ProjectCard.svelte.d.ts +20 -0
- package/dist/components/cards/index.d.ts +4 -0
- package/dist/components/cards/index.js +4 -0
- package/dist/components/data/Accordion.stories.svelte +316 -0
- package/dist/components/data/Accordion.stories.svelte.d.ts +19 -0
- package/dist/components/data/Accordion.svelte +23 -0
- package/dist/components/data/Accordion.svelte.d.ts +9 -0
- package/dist/components/data/AccordionItem.svelte +112 -0
- package/dist/components/data/AccordionItem.svelte.d.ts +11 -0
- package/dist/components/data/Table.composition.stories.svelte +67 -0
- package/dist/components/data/Table.composition.stories.svelte.d.ts +19 -0
- package/dist/components/data/Table.stories.svelte +137 -0
- package/dist/components/data/Table.stories.svelte.d.ts +19 -0
- package/dist/components/data/Table.svelte +83 -0
- package/dist/components/data/Table.svelte.d.ts +14 -0
- package/dist/components/data/Tabs.stories.svelte +386 -0
- package/dist/components/data/Tabs.stories.svelte.d.ts +19 -0
- package/dist/components/data/Tabs.svelte +142 -0
- package/dist/components/data/Tabs.svelte.d.ts +19 -0
- package/dist/components/data/index.d.ts +4 -0
- package/dist/components/data/index.js +4 -0
- package/dist/components/feedback/Modal.stories.svelte +192 -0
- package/dist/components/feedback/Modal.stories.svelte.d.ts +4 -0
- package/dist/components/feedback/Modal.svelte +185 -0
- package/dist/components/feedback/Modal.svelte.d.ts +19 -0
- package/dist/components/feedback/Toast.stories.svelte +203 -0
- package/dist/components/feedback/Toast.stories.svelte.d.ts +19 -0
- package/dist/components/feedback/Toast.svelte +109 -0
- package/dist/components/feedback/Toast.svelte.d.ts +15 -0
- package/dist/components/feedback/ToastRegion.stories.svelte +193 -0
- package/dist/components/feedback/ToastRegion.stories.svelte.d.ts +19 -0
- package/dist/components/feedback/ToastRegion.svelte +102 -0
- package/dist/components/feedback/ToastRegion.svelte.d.ts +9 -0
- package/dist/components/feedback/index.d.ts +3 -0
- package/dist/components/feedback/index.js +3 -0
- package/dist/components/forms/Checkbox.stories.svelte +103 -0
- package/dist/components/forms/Checkbox.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Checkbox.svelte +150 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +11 -0
- package/dist/components/forms/Field.stories.svelte +113 -0
- package/dist/components/forms/Field.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Field.svelte +77 -0
- package/dist/components/forms/Field.svelte.d.ts +17 -0
- package/dist/components/forms/Input.stories.svelte +58 -0
- package/dist/components/forms/Input.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Input.svelte +64 -0
- package/dist/components/forms/Input.svelte.d.ts +9 -0
- package/dist/components/forms/InputWrap.composition.stories.svelte +32 -0
- package/dist/components/forms/InputWrap.composition.stories.svelte.d.ts +19 -0
- package/dist/components/forms/InputWrap.stories.svelte +53 -0
- package/dist/components/forms/InputWrap.stories.svelte.d.ts +19 -0
- package/dist/components/forms/InputWrap.svelte +128 -0
- package/dist/components/forms/InputWrap.svelte.d.ts +21 -0
- package/dist/components/forms/Radio.stories.svelte +70 -0
- package/dist/components/forms/Radio.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Radio.svelte +109 -0
- package/dist/components/forms/Radio.svelte.d.ts +9 -0
- package/dist/components/forms/RadioGroup.stories.svelte +115 -0
- package/dist/components/forms/RadioGroup.stories.svelte.d.ts +19 -0
- package/dist/components/forms/RadioGroup.svelte +116 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts +24 -0
- package/dist/components/forms/Select.stories.svelte +168 -0
- package/dist/components/forms/Select.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Select.svelte +262 -0
- package/dist/components/forms/Select.svelte.d.ts +23 -0
- package/dist/components/forms/Switch.stories.svelte +86 -0
- package/dist/components/forms/Switch.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Switch.svelte +113 -0
- package/dist/components/forms/Switch.svelte.d.ts +11 -0
- package/dist/components/forms/Textarea.stories.svelte +40 -0
- package/dist/components/forms/Textarea.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Textarea.svelte +66 -0
- package/dist/components/forms/Textarea.svelte.d.ts +9 -0
- package/dist/components/forms/field-context.d.ts +7 -0
- package/dist/components/forms/field-context.js +1 -0
- package/dist/components/forms/index.d.ts +9 -0
- package/dist/components/forms/index.js +9 -0
- package/dist/components/layout/Container.stories.svelte +67 -0
- package/dist/components/layout/Container.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Container.svelte +52 -0
- package/dist/components/layout/Container.svelte.d.ts +14 -0
- package/dist/components/layout/Grid.stories.svelte +109 -0
- package/dist/components/layout/Grid.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Grid.svelte +54 -0
- package/dist/components/layout/Grid.svelte.d.ts +19 -0
- package/dist/components/layout/Inline.stories.svelte +136 -0
- package/dist/components/layout/Inline.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Inline.svelte +46 -0
- package/dist/components/layout/Inline.svelte.d.ts +19 -0
- package/dist/components/layout/Prose.stories.svelte +423 -0
- package/dist/components/layout/Prose.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Prose.svelte +176 -0
- package/dist/components/layout/Prose.svelte.d.ts +12 -0
- package/dist/components/layout/Rule.stories.svelte +80 -0
- package/dist/components/layout/Rule.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Rule.svelte +33 -0
- package/dist/components/layout/Rule.svelte.d.ts +9 -0
- package/dist/components/layout/Spread.stories.svelte +118 -0
- package/dist/components/layout/Spread.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Spread.svelte +38 -0
- package/dist/components/layout/Spread.svelte.d.ts +16 -0
- package/dist/components/layout/Stack.stories.svelte +90 -0
- package/dist/components/layout/Stack.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Stack.svelte +37 -0
- package/dist/components/layout/Stack.svelte.d.ts +16 -0
- package/dist/components/layout/index.d.ts +7 -0
- package/dist/components/layout/index.js +7 -0
- package/dist/components/navigation/Breadcrumb.stories.svelte +122 -0
- package/dist/components/navigation/Breadcrumb.stories.svelte.d.ts +19 -0
- package/dist/components/navigation/Breadcrumb.svelte +70 -0
- package/dist/components/navigation/Breadcrumb.svelte.d.ts +13 -0
- package/dist/components/navigation/Nav.stories.svelte +323 -0
- package/dist/components/navigation/Nav.stories.svelte.d.ts +19 -0
- package/dist/components/navigation/Nav.svelte +257 -0
- package/dist/components/navigation/Nav.svelte.d.ts +21 -0
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/patterns/ActivityRow.stories.svelte +45 -0
- package/dist/components/patterns/ActivityRow.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/ActivityRow.svelte +69 -0
- package/dist/components/patterns/ActivityRow.svelte.d.ts +16 -0
- package/dist/components/patterns/Alert.stories.svelte +63 -0
- package/dist/components/patterns/Alert.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/Alert.svelte +91 -0
- package/dist/components/patterns/Alert.svelte.d.ts +16 -0
- package/dist/components/patterns/CtaBlock.stories.svelte +62 -0
- package/dist/components/patterns/CtaBlock.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/CtaBlock.svelte +80 -0
- package/dist/components/patterns/CtaBlock.svelte.d.ts +16 -0
- package/dist/components/patterns/KvList.stories.svelte +48 -0
- package/dist/components/patterns/KvList.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/KvList.svelte +65 -0
- package/dist/components/patterns/KvList.svelte.d.ts +15 -0
- package/dist/components/patterns/PageHero.stories.svelte +62 -0
- package/dist/components/patterns/PageHero.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/PageHero.svelte +62 -0
- package/dist/components/patterns/PageHero.svelte.d.ts +14 -0
- package/dist/components/patterns/ProgressBar.stories.svelte +83 -0
- package/dist/components/patterns/ProgressBar.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/ProgressBar.svelte +71 -0
- package/dist/components/patterns/ProgressBar.svelte.d.ts +13 -0
- package/dist/components/patterns/SectionFoot.stories.svelte +37 -0
- package/dist/components/patterns/SectionFoot.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/SectionFoot.svelte +70 -0
- package/dist/components/patterns/SectionFoot.svelte.d.ts +15 -0
- package/dist/components/patterns/SectionHead.stories.svelte +67 -0
- package/dist/components/patterns/SectionHead.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/SectionHead.svelte +54 -0
- package/dist/components/patterns/SectionHead.svelte.d.ts +14 -0
- package/dist/components/patterns/StatCard.stories.svelte +59 -0
- package/dist/components/patterns/StatCard.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/StatCard.svelte +57 -0
- package/dist/components/patterns/StatCard.svelte.d.ts +15 -0
- package/dist/components/patterns/index.d.ts +9 -0
- package/dist/components/patterns/index.js +9 -0
- package/dist/components/primitives/Button.stories.svelte +132 -0
- package/dist/components/primitives/Button.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Button.svelte +142 -0
- package/dist/components/primitives/Button.svelte.d.ts +16 -0
- package/dist/components/primitives/Heading.stories.svelte +137 -0
- package/dist/components/primitives/Heading.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Heading.svelte +107 -0
- package/dist/components/primitives/Heading.svelte.d.ts +23 -0
- package/dist/components/primitives/Led.stories.svelte +63 -0
- package/dist/components/primitives/Led.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Led.svelte +65 -0
- package/dist/components/primitives/Led.svelte.d.ts +11 -0
- package/dist/components/primitives/TagPill.stories.svelte +90 -0
- package/dist/components/primitives/TagPill.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/TagPill.svelte +44 -0
- package/dist/components/primitives/TagPill.svelte.d.ts +9 -0
- package/dist/components/primitives/Text.stories.svelte +252 -0
- package/dist/components/primitives/Text.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Text.svelte +101 -0
- package/dist/components/primitives/Text.svelte.d.ts +25 -0
- package/dist/components/primitives/index.d.ts +5 -0
- package/dist/components/primitives/index.js +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/stores/toast.d.ts +19 -0
- package/dist/stores/toast.js +22 -0
- package/dist/storybook-utils.d.ts +11 -0
- package/dist/storybook-utils.js +29 -0
- package/dist/tokens/ColorSwatch.svelte +73 -0
- package/dist/tokens/ColorSwatch.svelte.d.ts +10 -0
- package/dist/tokens/layout.css +144 -0
- package/dist/tokens/patterns.css +281 -0
- package/dist/tokens/tokens.css +96 -0
- package/dist/tokens/tokens.stories.svelte +107 -0
- package/dist/tokens/tokens.stories.svelte.d.ts +18 -0
- package/dist/tokens/typography.css +159 -0
- 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;
|