@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
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # dxlb-design
2
+
3
+ Design system for [dexterlabs.nl](https://dexterlabs.nl). SvelteKit component library with Phosphor (dark) and Paper (light) palettes, built with Svelte 5 and documented in Storybook.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add dxlb-design
9
+ ```
10
+
11
+ ## Peer dependencies
12
+
13
+ ```json
14
+ {
15
+ "svelte": "^5.0.0",
16
+ "@sveltejs/kit": "^2.0.0"
17
+ }
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ Import the design token CSS in your global stylesheet or root layout:
23
+
24
+ ```js
25
+ // src/app.css or src/routes/+layout.svelte
26
+ import 'dxlb-design/tokens/tokens.css';
27
+ import 'dxlb-design/tokens/typography.css';
28
+ ```
29
+
30
+ `tokens.css` defines all CSS custom properties (colours, spacing, typography scale, transitions).
31
+ `typography.css` adds base element resets and global typography classes.
32
+
33
+ ## Palette
34
+
35
+ Set `data-palette` on the `<html>` element to activate a palette:
36
+
37
+ ```html
38
+ <!-- Phosphor — dark, terminal-green accent, amber highlights -->
39
+ <html data-palette="phosphor">
40
+
41
+ <!-- Paper — light, warm off-white background, same amber highlights -->
42
+ <html data-palette="paper">
43
+ ```
44
+
45
+ Both palettes use the same CSS custom property names (`--ink`, `--bg`, `--amber`, etc.) so components switch automatically.
46
+
47
+ ## Usage
48
+
49
+ ```svelte
50
+ <script>
51
+ import { Button, Stack, Heading } from 'dxlb-design';
52
+ </script>
53
+
54
+ <Stack gap="md">
55
+ <Heading level={2}>Deploy module</Heading>
56
+ <Button variant="primary">Confirm</Button>
57
+ <Button variant="ghost">Cancel</Button>
58
+ </Stack>
59
+ ```
60
+
61
+ ### Components
62
+
63
+ | Category | Components |
64
+ |----------|-----------|
65
+ | Primitives | `Button`, `Led`, `TagPill`, `Text`, `Heading` |
66
+ | Layout | `Stack`, `Inline`, `Spread`, `Grid`, `Container`, `Rule`, `Prose` |
67
+ | Cards | `Card`, `ProductCard`, `ProjectCard`, `NoteCard` |
68
+ | Navigation | `Nav`, `Breadcrumb` |
69
+ | Forms | `Input`, `Textarea`, `Select`, `InputWrap`, `Field`, `Checkbox`, `Radio`, `RadioGroup`, `Switch` |
70
+ | Feedback | `Modal`, `Alert`, `Toast`, `ToastRegion` |
71
+ | Patterns | `CtaBlock`, `StatCard`, `KvList`, `ProgressBar`, `ActivityRow`, `SectionHead`, `SectionFoot`, `PageHero` |
72
+ | Data | `Accordion`, `AccordionItem`, `Tabs`, `Table` |
73
+
74
+ ### Toast store
75
+
76
+ ```svelte
77
+ <script>
78
+ import { toast, ToastRegion } from 'dxlb-design';
79
+ </script>
80
+
81
+ <!-- Mount once in your root layout -->
82
+ <ToastRegion />
83
+
84
+ <!-- Push a notification from anywhere -->
85
+ <button onclick={() => toast.push('Saved', { variant: 'ok' })}>Save</button>
86
+ ```
87
+
88
+ ## Storybook
89
+
90
+ Interactive component explorer with play-function tests:
91
+
92
+ ```bash
93
+ pnpm storybook # starts at http://localhost:6006
94
+ ```
@@ -0,0 +1,82 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import Card from "./Card.svelte";
5
+ import { resolveTokenColor, resolveTokenFgColor } from "../../storybook-utils.js";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Cards/Card",
9
+ component: Card,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="Default"
15
+ play={async ({ canvasElement }) => {
16
+ const root = canvasElement.firstElementChild as HTMLElement;
17
+ await expect(root).toBeVisible();
18
+
19
+ const style = getComputedStyle(root);
20
+
21
+ // background-color matches --bg-rail token
22
+ const bgRail = resolveTokenColor("--bg-rail");
23
+ await expect(style.backgroundColor).toBe(bgRail);
24
+
25
+ // border is 1px solid with color matching --rule
26
+ await expect(style.borderTopStyle).toBe("solid");
27
+ await expect(style.borderTopWidth).toBe("1px");
28
+ const ruleColor = resolveTokenFgColor("--rule");
29
+ await expect(style.borderColor).toBe(ruleColor);
30
+
31
+ // display flex, flex-direction column
32
+ await expect(style.display).toBe("flex");
33
+ await expect(style.flexDirection).toBe("column");
34
+ }}>
35
+ <p>A simple card body.</p>
36
+ </Story>
37
+
38
+ <Story name="As Article" args={{ as: "article" }}
39
+ play={async ({ canvasElement }) => {
40
+ const root = canvasElement.firstElementChild as HTMLElement;
41
+ await expect(root.tagName).toBe("ARTICLE");
42
+ await expect(root).toBeVisible();
43
+ }}>
44
+ <h2>Title</h2>
45
+ <p>Body.</p>
46
+ </Story>
47
+
48
+ <Story name="As Link" args={{ as: "a", href: "#demo" }}
49
+ play={async ({ canvasElement }) => {
50
+ const canvas = within(canvasElement);
51
+ const link = canvas.getByRole("link");
52
+ await expect(link).toBeVisible();
53
+ await expect(link.getAttribute("href")).toBe("#demo");
54
+ }}>
55
+ <span>View demo</span>
56
+ </Story>
57
+
58
+ <Story name="Computed Styles"
59
+ play={async ({ canvasElement }) => {
60
+ const root = canvasElement.firstElementChild!;
61
+ const bgRail = resolveTokenColor("--bg-rail");
62
+ await expect(getComputedStyle(root).backgroundColor).toBe(bgRail);
63
+ const ruleColor = resolveTokenFgColor("--rule");
64
+ await expect(getComputedStyle(root).borderTopColor).toBe(ruleColor);
65
+ await expect(getComputedStyle(root).borderTopWidth).toBe("1px");
66
+ await expect(getComputedStyle(root).borderTopStyle).toBe("solid");
67
+ await expect(getComputedStyle(root).overflow).toBe("hidden");
68
+ }}>
69
+ <p>Card with computed style assertions.</p>
70
+ </Story>
71
+
72
+ <Story name="Hover Border"
73
+ play={async ({ canvasElement }) => {
74
+ // Card base component has no :hover CSS rule — hover behaviour lives in
75
+ // composite cards (ProjectCard, ProductCard, NoteCard).
76
+ // This story verifies the card renders correctly.
77
+ // On hover: composite cards transition border-color to var(--amber).
78
+ const root = canvasElement.firstElementChild!;
79
+ await expect(root).toBeVisible();
80
+ }}>
81
+ <p>Hover documentation story — card renders and is visible.</p>
82
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Card from "./Card.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 Card: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Card = InstanceType<typeof Card>;
19
+ export default Card;
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { ClassValue } from 'svelte/elements'
4
+
5
+ interface Props {
6
+ /** HTML element to render as. @default 'div' */
7
+ as?: string
8
+ children?: Snippet
9
+ class?: ClassValue | null
10
+ [key: string]: unknown
11
+ }
12
+
13
+ let { as = 'div', children, class: klass = '', ...rest }: Props = $props()
14
+ </script>
15
+
16
+ <svelte:element this={as} class={['card', klass]} {...rest}>
17
+ {@render children?.()}
18
+ </svelte:element>
19
+
20
+ <style>
21
+ .card {
22
+ border: 1px solid var(--rule);
23
+ background: var(--bg-rail);
24
+ display: flex;
25
+ flex-direction: column;
26
+ overflow: hidden;
27
+ }
28
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ClassValue } from 'svelte/elements';
3
+ interface Props {
4
+ /** HTML element to render as. @default 'div' */
5
+ as?: string;
6
+ children?: Snippet;
7
+ class?: ClassValue | null;
8
+ [key: string]: unknown;
9
+ }
10
+ declare const Card: import("svelte").Component<Props, {}, "">;
11
+ type Card = ReturnType<typeof Card>;
12
+ export default Card;
@@ -0,0 +1,94 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import NoteCard from "./NoteCard.svelte";
5
+ import { resolveTokenFgColor } from "../../storybook-utils.js";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Cards/NoteCard",
9
+ component: NoteCard,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="With Lede" args={{ href: "#note-0x06", idx: 6, kind: "PROJECT-LOG", title: "Shipping the Site", lede: "Turning a messy prototype into a stable place to document the work and sell hardware.", date: "1 May 2026" }}
15
+ play={async ({ canvasElement }) => {
16
+ const canvas = within(canvasElement);
17
+
18
+ // renders as <a> by default
19
+ const root = canvasElement.firstElementChild as HTMLElement;
20
+ await expect(root).toBeVisible();
21
+
22
+ // idx=6 → "0x06" hex ID displayed
23
+ const hexEl = canvas.getByText("0x06");
24
+ await expect(hexEl).toBeVisible();
25
+
26
+ // kind label rendered in uppercase
27
+ const kindEl = canvas.getByText("PROJECT-LOG");
28
+ await expect(kindEl).toBeVisible();
29
+
30
+ // title renders as h3
31
+ const heading = canvasElement.querySelector("h3") as HTMLElement;
32
+ await expect(heading).toBeVisible();
33
+ await expect(heading.textContent).toContain("Shipping the Site");
34
+
35
+ // lede renders as a <p> element
36
+ const ledeEl = canvasElement.querySelector("p.note-lede") as HTMLElement;
37
+ await expect(ledeEl).toBeVisible();
38
+ await expect(ledeEl.textContent).toContain("Turning a messy prototype");
39
+
40
+ // date rendered in footer
41
+ const dateEl = canvas.getByText(/1 May 2026/);
42
+ await expect(dateEl).toBeVisible();
43
+
44
+ // Footer "READ" is present
45
+ const readEl = canvas.getByText(/READ/);
46
+ await expect(readEl).toBeVisible();
47
+
48
+ // resting border-color matches --rule token
49
+ const ruleColor = resolveTokenFgColor("--rule");
50
+ await expect(getComputedStyle(root).borderColor).toBe(ruleColor);
51
+ }} />
52
+
53
+ <Story name="Minimal" args={{ href: "#note-0x01", idx: 1, kind: "LOG", title: "First entry" }}
54
+ play={async ({ canvasElement }) => {
55
+ const canvas = within(canvasElement);
56
+
57
+ const root = canvasElement.firstElementChild as HTMLElement;
58
+ await expect(root).toBeVisible();
59
+
60
+ // idx=1 → "0x01"
61
+ const hexEl = canvas.getByText("0x01");
62
+ await expect(hexEl).toBeVisible();
63
+
64
+ // no lede — .note-lede element should not be present
65
+ const ledeEl = canvasElement.querySelector(".note-lede");
66
+ await expect(ledeEl).toBeNull();
67
+
68
+ // no date — .note-foot element should not be present
69
+ const footEl = canvasElement.querySelector(".note-foot");
70
+ await expect(footEl).toBeNull();
71
+ }} />
72
+
73
+ <Story name="High Index" args={{ href: "#note-0xff", idx: 255, title: "High index" }}
74
+ play={async ({ canvasElement }) => {
75
+ const canvas = within(canvasElement);
76
+ const hexEl = canvas.getByText("0xFF");
77
+ await expect(hexEl).toBeVisible();
78
+ }} />
79
+
80
+ <Story name="As Div" args={{ as: "div", idx: 1, title: "Test" }}
81
+ play={async ({ canvasElement }) => {
82
+ const root = canvasElement.firstElementChild as HTMLElement;
83
+ await expect(root.tagName).toBe("DIV");
84
+ await expect(root).toBeVisible();
85
+ }} />
86
+
87
+ <!-- B27 AC-21: .note-lede Text must have no style="flex: 1" attribute -->
88
+ <Story name="No Inline Flex Style" args={{ href: "#note-0x03", idx: 3, title: "Bench Notes", lede: "An excerpt from the bench." }}
89
+ play={async ({ canvasElement }) => {
90
+ // Before B27: <Text class="note-lede" style="flex: 1;"> carries a style= attribute.
91
+ // After B27: flex: 1 moves to scoped CSS via .note-lede rule; no style= on the element.
92
+ const ledeEl = canvasElement.querySelector('.note-lede') as HTMLElement;
93
+ await expect(ledeEl.getAttribute('style')).toBeNull();
94
+ }} />
@@ -0,0 +1,19 @@
1
+ import NoteCard from "./NoteCard.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 NoteCard: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type NoteCard = InstanceType<typeof NoteCard>;
19
+ export default NoteCard;
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import Card from './Card.svelte'
3
+ import Stack from '../layout/Stack.svelte'
4
+ import Spread from '../layout/Spread.svelte'
5
+ import Text from '../primitives/Text.svelte'
6
+ import Heading from '../primitives/Heading.svelte'
7
+
8
+ interface Props {
9
+ /** HTML element to render as. @default 'a' */
10
+ as?: string
11
+ /** Numeric index — rendered as a hex ID (e.g. `0x0A`). */
12
+ idx: number
13
+ /** Tag shown in the top-right corner. @default 'LOG' */
14
+ kind?: string
15
+ /** Note title. */
16
+ title: string
17
+ /** Optional lede / excerpt text. */
18
+ lede?: string
19
+ /** Optional date string shown in the footer. */
20
+ date?: string
21
+ [key: string]: unknown
22
+ }
23
+
24
+ let {
25
+ as = 'a',
26
+ idx,
27
+ kind = 'LOG',
28
+ title,
29
+ lede,
30
+ date,
31
+ ...rest
32
+ }: Props = $props()
33
+
34
+ const hexId = $derived('0x' + idx.toString(16).padStart(2, '0').toUpperCase())
35
+ </script>
36
+
37
+ <Card as={as} class="note-card" {...rest}>
38
+ <div class="card-body">
39
+ <Stack gap="xs">
40
+ <Spread>
41
+ <Text variant="mono" color="faint" size="xs">{hexId}</Text>
42
+ <Text variant="mono" color="cyan" size="xs">{kind.toUpperCase()}</Text>
43
+ </Spread>
44
+ <Heading level={3} size="lg">{title}</Heading>
45
+ {#if lede}
46
+ <Text variant="body" class="note-lede">{lede}</Text>
47
+ {/if}
48
+ {#if date}
49
+ <div class="note-foot">
50
+ <Spread>
51
+ <Text variant="mono" color="faint" size="xs">{date}</Text>
52
+ <Text variant="mono" color="amber" size="xs" aria-hidden="true">READ →</Text>
53
+ </Spread>
54
+ </div>
55
+ {/if}
56
+ </Stack>
57
+ </div>
58
+ </Card>
59
+
60
+ <style>
61
+ .card-body {
62
+ padding: 20px;
63
+ flex: 1;
64
+ display: flex;
65
+ flex-direction: column;
66
+ }
67
+
68
+ :global(.note-card) {
69
+ text-decoration: none;
70
+ color: inherit;
71
+ cursor: pointer;
72
+ transition: border-color var(--transition);
73
+ }
74
+
75
+ :global(.note-card):hover {
76
+ border-color: var(--amber);
77
+ }
78
+
79
+ .card-body :global(.note-lede) {
80
+ flex: 1;
81
+ color: var(--ink-dim);
82
+ }
83
+
84
+ .note-foot {
85
+ margin-top: 8px;
86
+ padding-top: 12px;
87
+ border-top: 1px dashed var(--rule);
88
+ }
89
+ </style>
@@ -0,0 +1,18 @@
1
+ interface Props {
2
+ /** HTML element to render as. @default 'a' */
3
+ as?: string;
4
+ /** Numeric index — rendered as a hex ID (e.g. `0x0A`). */
5
+ idx: number;
6
+ /** Tag shown in the top-right corner. @default 'LOG' */
7
+ kind?: string;
8
+ /** Note title. */
9
+ title: string;
10
+ /** Optional lede / excerpt text. */
11
+ lede?: string;
12
+ /** Optional date string shown in the footer. */
13
+ date?: string;
14
+ [key: string]: unknown;
15
+ }
16
+ declare const NoteCard: import("svelte").Component<Props, {}, "">;
17
+ type NoteCard = ReturnType<typeof NoteCard>;
18
+ export default NoteCard;
@@ -0,0 +1,98 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from "@storybook/addon-svelte-csf";
3
+ import { expect, within } from "storybook/test";
4
+ import ProductCard from "./ProductCard.svelte";
5
+ import { resolveTokenColor } from "../../storybook-utils.js";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Cards/ProductCard",
9
+ component: ProductCard,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <Story name="Coming Soon" args={{ href: "#conduit-pdx2", sku: "CONDUIT-PDX2", name: "Conduit PDX-2", description: "Power your entire Eurorack from a laptop charger.", price: "€200", status: "coming-soon" }}
15
+ play={async ({ canvasElement }) => {
16
+ const canvas = within(canvasElement);
17
+
18
+ // renders as <a> by default
19
+ const root = canvasElement.firstElementChild as HTMLElement;
20
+ await expect(root).toBeVisible();
21
+ await expect(root.tagName).toBe("A");
22
+
23
+ // href forwarded via ...rest
24
+ await expect(root.getAttribute("href")).toBe("#conduit-pdx2");
25
+
26
+ // name renders as heading (h3)
27
+ const heading = canvas.getByText("Conduit PDX-2");
28
+ await expect(heading).toBeVisible();
29
+
30
+ // SKU renders as visible mono text
31
+ const sku = canvas.getByText("CONDUIT-PDX2");
32
+ await expect(sku).toBeVisible();
33
+
34
+ // price rendered when provided
35
+ const price = canvas.getByText(/€200/);
36
+ await expect(price).toBeVisible();
37
+
38
+ // status="coming-soon" → footer default "PRE-ORDER"
39
+ const cta = canvas.getByText(/PRE-ORDER/);
40
+ await expect(cta).toBeVisible();
41
+
42
+ // .card-img hatch gradient present and aspect-ratio is 14/9
43
+ const cardImg = canvasElement.querySelector(".card-img") as HTMLElement;
44
+ const imgBg = getComputedStyle(cardImg).backgroundImage;
45
+ await expect(imgBg).not.toBe("none");
46
+ await expect(getComputedStyle(cardImg).aspectRatio).toBe("14 / 9");
47
+ }} />
48
+
49
+ <Story name="In Stock" args={{ sku: "DISTRANS-AR1", name: "Distrans AR-1", description: "A precision attenuverter.", price: "€150", status: "in-stock" }}
50
+ play={async ({ canvasElement }) => {
51
+ const canvas = within(canvasElement);
52
+
53
+ // status="in-stock" → footer label "BUY NOW"
54
+ const cta = canvas.getByText(/BUY NOW/);
55
+ await expect(cta).toBeVisible();
56
+
57
+ // Led renders with color="ok" — find the led-ok span and check its background
58
+ const ledOk = canvasElement.querySelector(".led-ok") as HTMLElement;
59
+ await expect(ledOk).toBeVisible();
60
+ const okColor = resolveTokenColor("--ok");
61
+ await expect(getComputedStyle(ledOk).backgroundColor).toBe(okColor);
62
+ }} />
63
+
64
+ <Story name="Out of Stock" args={{ sku: "LEGACY-X1", name: "Legacy X-1", description: "Discontinued.", status: "out-of-stock" }}
65
+ play={async ({ canvasElement }) => {
66
+ const canvas = within(canvasElement);
67
+
68
+ // status="out-of-stock" → footer label "VIEW DETAILS"
69
+ const cta = canvas.getByText(/VIEW DETAILS/);
70
+ await expect(cta).toBeVisible();
71
+
72
+ // no price rendered when price prop is omitted
73
+ const priceEl = canvasElement.querySelector(".card-price");
74
+ await expect(priceEl).toBeNull();
75
+ }} />
76
+
77
+ <Story name="Low Stock" args={{ sku: "DISTRANS-AR1", name: "Distrans AR-1", description: "Last few units remaining.", price: "€150", status: "low-stock" }}
78
+ play={async ({ canvasElement }) => {
79
+ const canvas = within(canvasElement);
80
+
81
+ // status="low-stock" → default footer label "BUY NOW"
82
+ const cta = canvas.getByText(/BUY NOW/);
83
+ await expect(cta).toBeVisible();
84
+ }} />
85
+
86
+ <Story name="Custom CTA" args={{ sku: "CONDUIT-PDX2", name: "Conduit PDX-2", description: "Power your entire Eurorack from a laptop charger.", price: "€200", status: "coming-soon", ctaLabel: "RESERVE NOW" }}
87
+ play={async ({ canvasElement }) => {
88
+ // explicit ctaLabel overrides status-derived default
89
+ const cta = within(canvasElement).getByText(/RESERVE NOW/);
90
+ await expect(cta).toBeVisible();
91
+ }} />
92
+
93
+ <Story name="As Div" args={{ as: "div", sku: "CONDUIT-PDX2", name: "Conduit PDX-2", description: "Power your entire Eurorack from a laptop charger.", price: "€200", status: "coming-soon" }}
94
+ play={async ({ canvasElement }) => {
95
+ const root = canvasElement.firstElementChild as HTMLElement;
96
+ await expect(root.tagName).toBe("DIV");
97
+ await expect(root).toBeVisible();
98
+ }} />
@@ -0,0 +1,19 @@
1
+ import ProductCard from "./ProductCard.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 ProductCard: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type ProductCard = InstanceType<typeof ProductCard>;
19
+ export default ProductCard;