@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,423 @@
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 Prose from "./Prose.svelte";
6
+
7
+ const { Story } = defineMeta({
8
+ title: "Layout/Prose",
9
+ component: Prose,
10
+ tags: ["autodocs"],
11
+ });
12
+ </script>
13
+
14
+ <!-- AC 1, 3, 6, 10, 15, 26, 32 — Default story: all elements, article tag, prose class -->
15
+ <Story name="Default"
16
+ play={async ({ canvasElement }) => {
17
+ // AC 1: renders <article> by default
18
+ const article = canvasElement.querySelector('article');
19
+ await expect(article).not.toBeNull();
20
+ await expect(article!.tagName).toBe('ARTICLE');
21
+
22
+ // AC 3: root element has class "prose"
23
+ await expect(article!.classList.contains('prose')).toBe(true);
24
+
25
+ // AC 6: children are rendered inside .prose
26
+ const h1 = canvasElement.querySelector('h1');
27
+ await expect(h1).not.toBeNull();
28
+ await expect(h1!.closest('.prose')).not.toBeNull();
29
+ const p = canvasElement.querySelector('p');
30
+ await expect(p).not.toBeNull();
31
+ await expect(p!.closest('.prose')).not.toBeNull();
32
+
33
+ // AC 10: h1 has font-size 72px
34
+ const h1Style = getComputedStyle(h1!);
35
+ await expect(h1Style.fontSize).toBe('72px');
36
+
37
+ // AC 15: anchor has text-decoration: none, color resolves to --ink-faint
38
+ const anchor = canvasElement.querySelector('a');
39
+ await expect(anchor).not.toBeNull();
40
+ await expect(getComputedStyle(anchor!).textDecoration).toContain('none');
41
+ const inkFaintColor = resolveTokenFgColor('--ink-faint');
42
+ await expect(getComputedStyle(anchor!).color).toBe(inkFaintColor);
43
+
44
+ // AC 26: blockquote has border-left-color resolving to --amber
45
+ const blockquote = canvasElement.querySelector('blockquote');
46
+ await expect(blockquote).not.toBeNull();
47
+ const amberColor = resolveTokenColor('--amber');
48
+ await expect(getComputedStyle(blockquote!).borderLeftColor).toBe(amberColor);
49
+
50
+ // AC 32: hr has border-top of 1px solid resolving to --rule, border-bottom-width: 0px
51
+ const hr = canvasElement.querySelector('hr');
52
+ await expect(hr).not.toBeNull();
53
+ const hrStyle = getComputedStyle(hr!);
54
+ await expect(hrStyle.borderBottomWidth).toBe('0px');
55
+ const ruleColor = resolveTokenColor('--rule');
56
+ await expect(hrStyle.borderTopColor).toBe(ruleColor);
57
+
58
+ // pre has overflow-x: auto (AC 23)
59
+ const pre = canvasElement.querySelector('pre');
60
+ await expect(pre).not.toBeNull();
61
+ await expect(getComputedStyle(pre!).overflowX).toBe('auto');
62
+
63
+ // th has text-transform: uppercase (AC 29)
64
+ const th = canvasElement.querySelector('th');
65
+ await expect(th).not.toBeNull();
66
+ await expect(getComputedStyle(th!).textTransform).toBe('uppercase');
67
+
68
+ // AC 8: second p (after hr) has margin-top 24px (adjacent-sibling --u3 rule)
69
+ const allPs = canvasElement.querySelectorAll('p');
70
+ // The story has a p before the hr and a p after — the last p should have margin-top 24px
71
+ const lastP = allPs[allPs.length - 1];
72
+ await expect(getComputedStyle(lastP).marginTop).toBe('24px');
73
+
74
+ // inline code (not inside pre) has color resolving to --cyan (AC 21)
75
+ const inlineCode = canvasElement.querySelector('p > code');
76
+ await expect(inlineCode).not.toBeNull();
77
+ const cyanColor = resolveTokenFgColor('--cyan');
78
+ await expect(getComputedStyle(inlineCode!).color).toBe(cyanColor);
79
+ }}>
80
+ <h1>Heading one</h1>
81
+ <h2>Heading two</h2>
82
+ <h3>Heading three</h3>
83
+ <h4>Heading four</h4>
84
+ <p>A paragraph of body text with an <a href="/example">inline link</a> and some <code>inline code</code> for good measure.</p>
85
+ <ul><li>Item one</li><li>Item two</li><li>Item three</li></ul>
86
+ <ol><li>First</li><li>Second</li><li>Third</li></ol>
87
+ <blockquote>A blockquote with amber left border and italic text.</blockquote>
88
+ <pre><code>const x = 42; // block code</code></pre>
89
+ <table>
90
+ <thead><tr><th>Name</th><th>Value</th></tr></thead>
91
+ <tbody><tr><td>alpha</td><td>1</td></tr><tr><td>beta</td><td>2</td></tr></tbody>
92
+ </table>
93
+ <img src="/logo.svg" alt="Dexterlabs logo" />
94
+ <hr />
95
+ <p>Paragraph after the rule.</p>
96
+ </Story>
97
+
98
+ <!-- AC 2, 3, 4 — as="section", maxWidth="60ch" -->
99
+ <Story name="Polymorphic" args={{ as: 'section', maxWidth: '60ch' }}
100
+ play={async ({ canvasElement }) => {
101
+ // AC 2: renders <section> when as="section"
102
+ const section = canvasElement.querySelector('section');
103
+ await expect(section).not.toBeNull();
104
+ const article = canvasElement.querySelector('article');
105
+ await expect(article).toBeNull();
106
+
107
+ // AC 3: still has class "prose"
108
+ await expect(section!.classList.contains('prose')).toBe(true);
109
+
110
+ // AC 4: maxWidth="60ch" is applied as inline style
111
+ await expect((canvasElement.firstElementChild as HTMLElement).style.maxWidth).toBe('60ch');
112
+ }}>
113
+ <h2>Section title</h2>
114
+ <p>Body paragraph.</p>
115
+ </Story>
116
+
117
+ <!-- AC 5 — rest props forwarding: id and aria-label -->
118
+ <Story name="Rest Props" args={{ id: 'my-prose', 'aria-label': 'Article body' }}
119
+ play={async ({ canvasElement }) => {
120
+ // AC 5: id and aria-label forwarded to root element
121
+ const el = canvasElement.querySelector('#my-prose');
122
+ await expect(el).not.toBeNull();
123
+ await expect(el!.getAttribute('aria-label')).toBe('Article body');
124
+ }}>
125
+ <p>Content with forwarded props.</p>
126
+ </Story>
127
+
128
+ <!-- AC 9 — first child has margin-top: 0 -->
129
+ <Story name="First Child No Top Margin"
130
+ play={async ({ canvasElement }) => {
131
+ const firstChild = canvasElement.querySelector('.prose > h1') as HTMLElement;
132
+ await expect(firstChild).not.toBeNull();
133
+ // AC 9: first direct child has margin-top: 0
134
+ await expect(getComputedStyle(firstChild).marginTop).toBe('0px');
135
+ }}>
136
+ <h1>First heading — no top margin</h1>
137
+ <p>Second child — should have 24px top margin.</p>
138
+ </Story>
139
+
140
+ <!-- AC 10, 11, 12, 13 — all four heading sizes -->
141
+ <Story name="Headings"
142
+ play={async ({ canvasElement }) => {
143
+ const h1 = canvasElement.querySelector('h1') as HTMLElement;
144
+ const h2 = canvasElement.querySelector('h2') as HTMLElement;
145
+ const h3 = canvasElement.querySelector('h3') as HTMLElement;
146
+ const h4 = canvasElement.querySelector('h4') as HTMLElement;
147
+
148
+ // AC 10: h1 — 72px, weight 500, letter-spacing -0.03em, line-height 1
149
+ await expect(getComputedStyle(h1).fontSize).toBe('72px');
150
+ await expect(getComputedStyle(h1).fontWeight).toBe('500');
151
+ await expect(getComputedStyle(h1).letterSpacing).toBe('-2.16px');
152
+ await expect(getComputedStyle(h1).lineHeight).toBe('72px');
153
+
154
+ // AC 11: h2 — 36px, weight 500, letter-spacing -0.01em, line-height 1.1
155
+ await expect(getComputedStyle(h2).fontSize).toBe('36px');
156
+ await expect(getComputedStyle(h2).fontWeight).toBe('500');
157
+ await expect(getComputedStyle(h2).letterSpacing).toBe('-0.36px');
158
+ await expect(getComputedStyle(h2).lineHeight).toBe('39.6px');
159
+
160
+ // AC 12: h3 — 24px, weight 500, letter-spacing -0.01em, line-height 1.2
161
+ await expect(getComputedStyle(h3).fontSize).toBe('24px');
162
+ await expect(getComputedStyle(h3).fontWeight).toBe('500');
163
+ await expect(getComputedStyle(h3).letterSpacing).toBe('-0.24px');
164
+ await expect(getComputedStyle(h3).lineHeight).toBe('28.8px');
165
+
166
+ // AC 13: h4 — mono font, 14px, letter-spacing 0.08em, uppercase, color --ink-faint
167
+ const h4Style = getComputedStyle(h4);
168
+ await expect(h4Style.fontFamily.toLowerCase()).toContain('jetbrains mono');
169
+ await expect(h4Style.fontSize).toBe('14px');
170
+ await expect(h4Style.textTransform).toBe('uppercase');
171
+ const inkFaintColor = resolveTokenFgColor('--ink-faint');
172
+ await expect(h4Style.color).toBe(inkFaintColor);
173
+ }}>
174
+ <h1>The quick brown fox H1</h1>
175
+ <h2>The quick brown fox H2</h2>
176
+ <h3>The quick brown fox H3</h3>
177
+ <h4>The quick brown fox H4</h4>
178
+ </Story>
179
+
180
+ <!-- AC 14 — paragraph font-size and line-height -->
181
+ <Story name="Paragraph"
182
+ play={async ({ canvasElement }) => {
183
+ const p = canvasElement.querySelector('p') as HTMLElement;
184
+ await expect(p).not.toBeNull();
185
+ // AC 14: font-size 16px, line-height 1.65
186
+ await expect(getComputedStyle(p).fontSize).toBe('16px');
187
+ await expect(getComputedStyle(p).lineHeight).toBe('26.4px');
188
+ }}>
189
+ <p>A paragraph of body text for testing font-size and line-height values.</p>
190
+ </Story>
191
+
192
+ <!-- AC 15, 16 — anchor default colour and hover state -->
193
+ <Story name="Anchor Hover"
194
+ play={async ({ canvasElement }) => {
195
+ const anchor = canvasElement.querySelector('a') as HTMLElement;
196
+ await expect(anchor).not.toBeNull();
197
+
198
+ // AC 15: default — text-decoration: none, color: --ink-faint
199
+ await expect(getComputedStyle(anchor).textDecoration).toContain('none');
200
+ const inkFaintColor = resolveTokenFgColor('--ink-faint');
201
+ await expect(getComputedStyle(anchor).color).toBe(inkFaintColor);
202
+
203
+ // AC 16: hover styles are CSS-only (:hover pseudo-class); visual review only.
204
+ // The CSS rule `.prose :global(a:hover)` sets color: var(--amber) and
205
+ // text-decoration: underline but getComputedStyle cannot read :hover state in headless.
206
+ }}>
207
+ <p>Paragraph with an <a href="/example">inline link to hover</a> inside it.</p>
208
+ </Story>
209
+
210
+ <!-- AC 17, 18, 19, 20 — lists: ul, ol, li, li+li spacing -->
211
+ <Story name="Lists"
212
+ play={async ({ canvasElement }) => {
213
+ const ul = canvasElement.querySelector('ul') as HTMLElement;
214
+ const ol = canvasElement.querySelector('ol') as HTMLElement;
215
+ const liItems = canvasElement.querySelectorAll('ul > li');
216
+
217
+ // AC 17: ul has list-style: disc, padding-left: 24px
218
+ await expect(getComputedStyle(ul).listStyleType).toBe('disc');
219
+ await expect(getComputedStyle(ul).paddingLeft).toBe('24px');
220
+
221
+ // AC 18: ol has list-style: decimal, padding-left: 24px
222
+ await expect(getComputedStyle(ol).listStyleType).toBe('decimal');
223
+ await expect(getComputedStyle(ol).paddingLeft).toBe('24px');
224
+
225
+ // AC 19: li has line-height: 1.65 (= 26.4px at 16px font-size)
226
+ await expect(getComputedStyle(liItems[0] as HTMLElement).lineHeight).toBe('26.4px');
227
+
228
+ // AC 20: second li (li+li) has margin-top: 8px (--u)
229
+ await expect(getComputedStyle(liItems[1] as HTMLElement).marginTop).toBe('8px');
230
+ }}>
231
+ <ul><li>Unordered item one</li><li>Unordered item two</li><li>Unordered item three</li></ul>
232
+ <ol><li>Ordered item one</li><li>Ordered item two</li><li>Ordered item three</li></ol>
233
+ </Story>
234
+
235
+ <!-- AC 21, 22, 23, 24, 25 — inline code chip + pre block + pre code reset -->
236
+ <Story name="CodeBlocks"
237
+ play={async ({ canvasElement }) => {
238
+ // Inline code — not inside pre
239
+ const inlineCode = canvasElement.querySelector('p > code') as HTMLElement;
240
+ await expect(inlineCode).not.toBeNull();
241
+
242
+ // AC 21: background-color --bg-rail, color --cyan, border-radius --radius (2px), border 1px solid --rule
243
+ const bgRailColor = resolveTokenColor('--bg-rail');
244
+ await expect(getComputedStyle(inlineCode).backgroundColor).toBe(bgRailColor);
245
+ const cyanColor = resolveTokenFgColor('--cyan');
246
+ await expect(getComputedStyle(inlineCode).color).toBe(cyanColor);
247
+ await expect(getComputedStyle(inlineCode).borderRadius).toBe('2px');
248
+ const ruleColor = resolveTokenColor('--rule');
249
+ await expect(getComputedStyle(inlineCode).borderColor).toBe(ruleColor);
250
+
251
+ // AC 22: padding 1px 5px
252
+ await expect(getComputedStyle(inlineCode).paddingTop).toBe('1px');
253
+ await expect(getComputedStyle(inlineCode).paddingRight).toBe('5px');
254
+
255
+ // pre block
256
+ const pre = canvasElement.querySelector('pre') as HTMLElement;
257
+ await expect(pre).not.toBeNull();
258
+
259
+ // AC 23: pre background --bg-sunken, border 1px solid --rule, padding 16px 20px, overflow-x auto, line-height 1.6
260
+ const bgSunkenColor = resolveTokenColor('--bg-sunken');
261
+ await expect(getComputedStyle(pre).backgroundColor).toBe(bgSunkenColor);
262
+ await expect(getComputedStyle(pre).borderColor).toBe(ruleColor);
263
+ await expect(getComputedStyle(pre).paddingTop).toBe('16px');
264
+ await expect(getComputedStyle(pre).paddingLeft).toBe('20px');
265
+ await expect(getComputedStyle(pre).overflowX).toBe('auto');
266
+ await expect(getComputedStyle(pre).lineHeight).toBe('22.4px');
267
+
268
+ // pre > code reset
269
+ const preCode = canvasElement.querySelector('pre > code') as HTMLElement;
270
+ await expect(preCode).not.toBeNull();
271
+
272
+ // AC 24: background transparent, border none, padding 0
273
+ await expect(getComputedStyle(preCode).backgroundColor).toBe('rgba(0, 0, 0, 0)');
274
+ await expect(getComputedStyle(preCode).borderWidth).toBe('0px');
275
+ await expect(getComputedStyle(preCode).paddingTop).toBe('0px');
276
+ await expect(getComputedStyle(preCode).paddingRight).toBe('0px');
277
+
278
+ // AC 25: color resolves to --ink (via --shiki-foreground fallback)
279
+ const inkColor = resolveTokenFgColor('--ink');
280
+ await expect(getComputedStyle(preCode).color).toBe(inkColor);
281
+ }}>
282
+ <p>Use <code>code snippet</code> inline in a sentence.</p>
283
+ <pre><code>function hello() {'{'}
284
+ return 42;
285
+ {'}'}</code></pre>
286
+ </Story>
287
+
288
+ <!-- AC 26, 27 — blockquote: amber border, italic, dim colour, padding -->
289
+ <Story name="Blockquote"
290
+ play={async ({ canvasElement }) => {
291
+ const blockquote = canvasElement.querySelector('blockquote') as HTMLElement;
292
+ await expect(blockquote).not.toBeNull();
293
+ const bqStyle = getComputedStyle(blockquote);
294
+
295
+ // AC 26: border-left 2px solid --amber, color --ink-dim, font-style italic
296
+ await expect(bqStyle.borderLeftWidth).toBe('2px');
297
+ const amberColor = resolveTokenColor('--amber');
298
+ await expect(bqStyle.borderLeftColor).toBe(amberColor);
299
+ const inkDimColor = resolveTokenFgColor('--ink-dim');
300
+ await expect(bqStyle.color).toBe(inkDimColor);
301
+ await expect(bqStyle.fontStyle).toBe('italic');
302
+
303
+ // AC 27: padding 4px 0 4px 16px
304
+ await expect(bqStyle.paddingTop).toBe('4px');
305
+ await expect(bqStyle.paddingRight).toBe('0px');
306
+ await expect(bqStyle.paddingBottom).toBe('4px');
307
+ await expect(bqStyle.paddingLeft).toBe('16px');
308
+ }}>
309
+ <p>Before quote.</p>
310
+ <blockquote>Quoted text from somewhere meaningful.</blockquote>
311
+ <p>After quote.</p>
312
+ </Story>
313
+
314
+ <!-- AC 28, 29, 30 — table: border-collapse, th styles, td styles -->
315
+ <Story name="TableInProse"
316
+ play={async ({ canvasElement }) => {
317
+ const table = canvasElement.querySelector('table') as HTMLElement;
318
+ await expect(table).not.toBeNull();
319
+
320
+ // AC 28: width 100%, border-collapse collapse, font-family JetBrains Mono
321
+ await expect(getComputedStyle(table).borderCollapse).toBe('collapse');
322
+ await expect(getComputedStyle(table).fontFamily.toLowerCase()).toContain('jetbrains mono');
323
+
324
+ const th = canvasElement.querySelector('th') as HTMLElement;
325
+ await expect(th).not.toBeNull();
326
+ const thStyle = getComputedStyle(th);
327
+
328
+ // AC 29: th text-transform uppercase, font-weight 500, letter-spacing 0.1em, color --ink-faint, border-bottom 1px solid --rule-strong
329
+ await expect(thStyle.textTransform).toBe('uppercase');
330
+ await expect(thStyle.fontWeight).toBe('500');
331
+ const inkFaintColor = resolveTokenFgColor('--ink-faint');
332
+ await expect(thStyle.color).toBe(inkFaintColor);
333
+ await expect(thStyle.borderBottomWidth).toBe('1px');
334
+ const ruleStrongColor = resolveTokenColor('--rule-strong');
335
+ await expect(thStyle.borderBottomColor).toBe(ruleStrongColor);
336
+
337
+ const td = canvasElement.querySelector('td') as HTMLElement;
338
+ await expect(td).not.toBeNull();
339
+ const tdStyle = getComputedStyle(td);
340
+
341
+ // AC 30: td border-bottom dashed --rule, color --ink-dim
342
+ await expect(tdStyle.borderBottomStyle).toBe('dashed');
343
+ const ruleColor = resolveTokenColor('--rule');
344
+ await expect(tdStyle.borderBottomColor).toBe(ruleColor);
345
+ const inkDimColor = resolveTokenFgColor('--ink-dim');
346
+ await expect(tdStyle.color).toBe(inkDimColor);
347
+ }}>
348
+ <table>
349
+ <thead><tr><th>Name</th><th>Value</th></tr></thead>
350
+ <tbody>
351
+ <tr><td>alpha</td><td>1</td></tr>
352
+ <tr><td>beta</td><td>2</td></tr>
353
+ </tbody>
354
+ </table>
355
+ </Story>
356
+
357
+ <!-- AC 31 — img: max-width 100%, height auto, border 1px solid --rule -->
358
+ <Story name="Image"
359
+ play={async ({ canvasElement }) => {
360
+ const img = canvasElement.querySelector('img') as HTMLElement;
361
+ await expect(img).not.toBeNull();
362
+ const imgStyle = getComputedStyle(img);
363
+
364
+ // AC 31: max-width 100% (height: auto is set in CSS but getComputedStyle returns resolved pixels)
365
+ await expect(imgStyle.maxWidth).toBe('100%');
366
+
367
+ // border 1px solid --rule
368
+ await expect(imgStyle.borderWidth).toBe('1px');
369
+ const ruleColor = resolveTokenColor('--rule');
370
+ await expect(imgStyle.borderColor).toBe(ruleColor);
371
+ }}>
372
+ <img src="/logo.svg" alt="Dexterlabs logo" />
373
+ </Story>
374
+
375
+ <!-- AC 33 — PaperPalette: token colours flip for Paper palette -->
376
+ <Story name="PaperPalette"
377
+ play={async ({ canvasElement }) => {
378
+ // The Prose is wrapped in a div[data-palette="paper"] in the slot
379
+ // so we probe tokens with a probe element also inside that wrapper
380
+ const wrapper = canvasElement.querySelector('[data-palette="paper"]') as HTMLElement;
381
+ await expect(wrapper).not.toBeNull();
382
+
383
+ const anchor = wrapper.querySelector('a') as HTMLElement;
384
+ const blockquote = wrapper.querySelector('blockquote') as HTMLElement;
385
+ const pre = wrapper.querySelector('pre') as HTMLElement;
386
+
387
+ // Probe token values inside the paper-palette context
388
+ const probeEl = document.createElement('div');
389
+ probeEl.style.position = 'absolute';
390
+ probeEl.style.opacity = '0';
391
+ wrapper.appendChild(probeEl);
392
+
393
+ // Paper --ink-faint should be #5f5a4a ≈ rgb(95, 90, 74)
394
+ probeEl.style.color = 'var(--ink-faint)';
395
+ const paperInkFaint = getComputedStyle(probeEl).color;
396
+
397
+ // Paper --amber should be #a04e00 ≈ rgb(160, 78, 0)
398
+ probeEl.style.backgroundColor = 'var(--amber)';
399
+ const paperAmber = getComputedStyle(probeEl).backgroundColor;
400
+
401
+ // Paper --bg-sunken
402
+ probeEl.style.backgroundColor = 'var(--bg-sunken)';
403
+ const paperBgSunken = getComputedStyle(probeEl).backgroundColor;
404
+
405
+ wrapper.removeChild(probeEl);
406
+
407
+ // AC 33: anchor color is Paper --ink-faint
408
+ await expect(getComputedStyle(anchor).color).toBe(paperInkFaint);
409
+
410
+ // AC 33: blockquote border-left-color is Paper --amber
411
+ await expect(getComputedStyle(blockquote).borderLeftColor).toBe(paperAmber);
412
+
413
+ // AC 33: pre background is Paper --bg-sunken
414
+ await expect(getComputedStyle(pre).backgroundColor).toBe(paperBgSunken);
415
+ }}>
416
+ <div data-palette="paper" style="padding: 16px;">
417
+ <Prose>
418
+ <p>A paragraph with an <a href="/example">inline link</a> for palette testing.</p>
419
+ <blockquote>A quoted passage with amber border.</blockquote>
420
+ <pre><code>// block code in paper palette</code></pre>
421
+ </Prose>
422
+ </div>
423
+ </Story>
@@ -0,0 +1,19 @@
1
+ import Prose from "./Prose.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 Prose: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
+ [evt: string]: CustomEvent<any>;
17
+ }, {}, {}, string>;
18
+ type Prose = InstanceType<typeof Prose>;
19
+ export default Prose;
@@ -0,0 +1,176 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ interface ProseProps extends HTMLAttributes<HTMLElement> {
6
+ /** HTML element to render as. @default 'article' */
7
+ as?: string
8
+ /** Max-width constraint applied to the prose container. @default '72ch' */
9
+ maxWidth?: string
10
+ children?: Snippet
11
+ }
12
+
13
+ let { as = 'article', maxWidth = '72ch', children, class: klass = '', ...rest }: ProseProps = $props()
14
+ </script>
15
+
16
+ <svelte:element this={as} class={['prose', klass]} style:max-width={maxWidth} {...rest}>
17
+ {@render children?.()}
18
+ </svelte:element>
19
+
20
+ <style>
21
+ /* Adjacent-sibling block spacing */
22
+ :global(.prose > * + *) {
23
+ margin-top: var(--u3);
24
+ }
25
+
26
+ /* First child: no top margin */
27
+ :global(.prose > :first-child) {
28
+ margin-top: 0;
29
+ }
30
+
31
+ /* Headings */
32
+ .prose :global(h1) {
33
+ font-size: var(--t-h1);
34
+ font-weight: 500;
35
+ letter-spacing: -0.03em;
36
+ line-height: 1;
37
+ }
38
+
39
+ .prose :global(h2) {
40
+ font-size: var(--t-h2);
41
+ font-weight: 500;
42
+ letter-spacing: -0.01em;
43
+ line-height: 1.1;
44
+ }
45
+
46
+ .prose :global(h3) {
47
+ font-size: var(--t-h3);
48
+ font-weight: 500;
49
+ letter-spacing: -0.01em;
50
+ line-height: 1.2;
51
+ }
52
+
53
+ .prose :global(h4) {
54
+ font-family: var(--mono);
55
+ font-size: var(--t-mono);
56
+ font-weight: 500;
57
+ letter-spacing: 0.08em;
58
+ text-transform: uppercase;
59
+ color: var(--ink-faint);
60
+ }
61
+
62
+ /* Paragraph */
63
+ .prose :global(p) {
64
+ font-family: var(--sans);
65
+ font-size: var(--t-body);
66
+ line-height: 1.65;
67
+ }
68
+
69
+ /* Anchor */
70
+ .prose :global(a) {
71
+ color: var(--ink-faint);
72
+ text-decoration: none;
73
+ }
74
+
75
+ .prose :global(a:hover) {
76
+ color: var(--amber);
77
+ text-decoration: underline;
78
+ }
79
+
80
+ /* Lists */
81
+ .prose :global(ul) {
82
+ list-style: disc;
83
+ padding-left: var(--u3);
84
+ }
85
+
86
+ .prose :global(ol) {
87
+ list-style: decimal;
88
+ padding-left: var(--u3);
89
+ }
90
+
91
+ .prose :global(li) {
92
+ line-height: 1.65;
93
+ }
94
+
95
+ .prose :global(li + li) {
96
+ margin-top: var(--u);
97
+ }
98
+
99
+ /* Inline code chip */
100
+ .prose :global(code) {
101
+ font-family: var(--mono);
102
+ font-size: var(--t-mono);
103
+ background: var(--bg-rail);
104
+ color: var(--cyan);
105
+ padding: 1px 5px;
106
+ border: 1px solid var(--rule);
107
+ border-radius: var(--radius);
108
+ }
109
+
110
+ /* Block code — overrides inline code chip styles */
111
+ .prose :global(pre) {
112
+ font-family: var(--mono);
113
+ font-size: var(--t-mono);
114
+ background: var(--bg-sunken);
115
+ border: 1px solid var(--rule);
116
+ border-radius: var(--radius);
117
+ padding: 16px 20px;
118
+ overflow-x: auto;
119
+ line-height: 1.6;
120
+ }
121
+
122
+ .prose :global(pre code) {
123
+ background: transparent;
124
+ border: none;
125
+ padding: 0;
126
+ color: var(--shiki-foreground, var(--ink));
127
+ }
128
+
129
+ /* Blockquote */
130
+ .prose :global(blockquote) {
131
+ margin: 0;
132
+ padding: 4px 0 4px 16px;
133
+ border-left: 2px solid var(--amber);
134
+ color: var(--ink-dim);
135
+ font-style: italic;
136
+ }
137
+
138
+ /* Table */
139
+ .prose :global(table) {
140
+ width: 100%;
141
+ border-collapse: collapse;
142
+ font-family: var(--mono);
143
+ font-size: var(--t-mono);
144
+ }
145
+
146
+ .prose :global(th) {
147
+ text-align: left;
148
+ padding: 8px 0;
149
+ border-bottom: 1px solid var(--rule-strong);
150
+ color: var(--ink-faint);
151
+ letter-spacing: 0.1em;
152
+ text-transform: uppercase;
153
+ font-weight: 500;
154
+ }
155
+
156
+ .prose :global(td) {
157
+ padding: 8px 0;
158
+ border-bottom: 1px dashed var(--rule);
159
+ color: var(--ink-dim);
160
+ }
161
+
162
+ /* Image */
163
+ .prose :global(img) {
164
+ max-width: 100%;
165
+ height: auto;
166
+ border: 1px solid var(--rule);
167
+ border-radius: var(--radius);
168
+ }
169
+
170
+ /* Horizontal rule */
171
+ .prose :global(hr) {
172
+ border: none;
173
+ border-top: 1px solid var(--rule);
174
+ margin: 0;
175
+ }
176
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ interface ProseProps extends HTMLAttributes<HTMLElement> {
4
+ /** HTML element to render as. @default 'article' */
5
+ as?: string;
6
+ /** Max-width constraint applied to the prose container. @default '72ch' */
7
+ maxWidth?: string;
8
+ children?: Snippet;
9
+ }
10
+ declare const Prose: import("svelte").Component<ProseProps, {}, "">;
11
+ type Prose = ReturnType<typeof Prose>;
12
+ export default Prose;