@pure-ds/storybook 0.7.25 → 0.7.26

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 (68) hide show
  1. package/.storybook/preview.js +5 -4
  2. package/dist/pds-reference.json +2266 -1017
  3. package/package.json +2 -2
  4. package/public/assets/js/app.js +1 -1
  5. package/public/assets/js/pds-ask.js +6 -6
  6. package/public/assets/js/pds-manager.js +104 -29
  7. package/public/assets/pds/components/pds-calendar.js +91 -159
  8. package/public/assets/pds/components/pds-daterange.js +683 -0
  9. package/public/assets/pds/components/pds-form.js +123 -21
  10. package/public/assets/pds/components/pds-rating.js +648 -0
  11. package/public/assets/pds/components/pds-tags.js +802 -0
  12. package/public/assets/pds/core/pds-ask.js +6 -6
  13. package/public/assets/pds/core/pds-manager.js +104 -29
  14. package/public/assets/pds/custom-elements.json +1099 -74
  15. package/public/assets/pds/pds-css-complete.json +7 -2
  16. package/public/assets/pds/pds-runtime-config.json +1 -1
  17. package/public/assets/pds/pds.css-data.json +4 -4
  18. package/public/assets/pds/styles/pds-components.css +96 -24
  19. package/public/assets/pds/styles/pds-components.css.js +192 -48
  20. package/public/assets/pds/styles/pds-primitives.css +6 -3
  21. package/public/assets/pds/styles/pds-primitives.css.js +12 -6
  22. package/public/assets/pds/styles/pds-styles.css +104 -29
  23. package/public/assets/pds/styles/pds-styles.css.js +208 -58
  24. package/public/assets/pds/styles/pds-utilities.css +2 -2
  25. package/public/assets/pds/styles/pds-utilities.css.js +4 -4
  26. package/public/assets/pds/vscode-custom-data.json +97 -0
  27. package/src/js/common/ask.js +122 -12
  28. package/src/js/pds-core/pds-generator.js +103 -28
  29. package/src/js/pds-core/pds-ontology.js +2 -2
  30. package/stories/components/PdsCalendar.stories.js +650 -168
  31. package/stories/components/PdsDaterange.stories.js +85 -0
  32. package/stories/components/PdsForm.Basics.stories.js +16 -0
  33. package/stories/components/PdsForm.ConditionalAndCalculated.stories.js +16 -0
  34. package/stories/components/PdsForm.CustomContent.stories.js +10 -0
  35. package/stories/components/PdsForm.Dialogs.stories.js +7 -0
  36. package/stories/components/PdsForm.Layout.stories.js +16 -0
  37. package/stories/components/PdsForm.SelectionAndArrays.stories.js +12 -0
  38. package/stories/components/PdsForm.stories.js +179 -219
  39. package/stories/components/PdsIcon.stories.js +17 -7
  40. package/stories/components/PdsOmnibox.stories.js +1 -60
  41. package/stories/components/PdsRating.stories.js +126 -0
  42. package/stories/components/PdsTags.stories.js +224 -0
  43. package/stories/components/PdsToaster.stories.js +1 -1
  44. package/stories/components/omnibox-countries-api-settings.js +63 -0
  45. package/stories/foundations/Colors.stories.js +6 -1
  46. package/stories/foundations/HTMLDefaults.stories.js +6 -6
  47. package/stories/foundations/Icons.stories.js +4 -4
  48. package/stories/foundations/MeshGradients.stories.js +13 -3
  49. package/stories/foundations/SmartSurfaces.stories.js +43 -31
  50. package/stories/foundations/Typography.stories.js +30 -108
  51. package/stories/foundations/ZIndex.stories.js +3 -3
  52. package/stories/layout/LayoutOverview.stories.js +3 -3
  53. package/stories/layout/LayoutSystem.stories.js +24 -18
  54. package/stories/patterns/BorderEffects.stories.js +28 -16
  55. package/stories/patterns/InteractiveStates.stories.js +39 -24
  56. package/stories/patterns/Utilities.stories.js +45 -23
  57. package/stories/primitives/ArticleLayout.stories.js +176 -0
  58. package/stories/primitives/Badges.stories.js +24 -8
  59. package/stories/primitives/Buttons.stories.js +29 -9
  60. package/stories/primitives/Callouts.stories.js +8 -7
  61. package/stories/primitives/Cards.stories.js +4 -1
  62. package/stories/primitives/FormElements.stories.js +8 -2
  63. package/stories/primitives/HtmlFormElements.stories.js +8 -2
  64. package/stories/primitives/HtmlFormGroups.stories.js +5 -2
  65. package/stories/primitives/Media.stories.js +44 -30
  66. package/stories/primitives/Tables.stories.js +25 -7
  67. package/stories/utils/PdsAsk.stories.js +17 -5
  68. package/stories/utils/PdsObjectApi.stories.js +16 -4
@@ -0,0 +1,126 @@
1
+ import { html } from 'lit';
2
+
3
+ const docsParameters = {
4
+ description: {
5
+ component: 'Form-associated star rating control with keyboard, pointer, and half-step support.'
6
+ }
7
+ };
8
+
9
+ if (typeof window !== 'undefined') {
10
+ import('../reference/reference-docs.js')
11
+ .then(({ createComponentDocsPage }) => {
12
+ docsParameters.page = createComponentDocsPage('pds-rating');
13
+ })
14
+ .catch((error) => {
15
+ console.warn('storybook: docs page failed to load for pds-rating', error);
16
+ });
17
+ }
18
+
19
+ export default {
20
+ title: 'Components/pds-rating',
21
+ tags: ['autodocs', 'rating', 'stars', 'form', 'input'],
22
+ parameters: {
23
+ pds: {
24
+ tags: ['rating', 'stars', 'form', 'input', 'pds-rating']
25
+ },
26
+ docs: docsParameters
27
+ },
28
+ argTypes: {
29
+ max: {
30
+ control: { type: 'number', min: 1, max: 10, step: 1 },
31
+ description: 'Maximum rating / star count'
32
+ },
33
+ value: {
34
+ control: { type: 'number', min: 0, max: 10, step: 0.5 },
35
+ description: 'Current rating value'
36
+ },
37
+ name: {
38
+ control: 'text',
39
+ description: 'Form field name'
40
+ },
41
+ color: {
42
+ control: 'text',
43
+ description: 'Active star color (CSS color value)'
44
+ },
45
+ disabled: {
46
+ control: 'boolean',
47
+ description: 'Disable interaction and submission value'
48
+ },
49
+ required: {
50
+ control: 'boolean',
51
+ description: 'Require a value in form validation'
52
+ },
53
+ readonly: {
54
+ control: 'boolean',
55
+ description: 'Read-only but still visible value'
56
+ }
57
+ }
58
+ };
59
+
60
+ const baseTemplate = (args) => html`
61
+ <pds-rating
62
+ max="${args.max}"
63
+ value="${args.value}"
64
+ name="${args.name}"
65
+ color="${args.color || ''}"
66
+ ?disabled="${args.disabled}"
67
+ ?required="${args.required}"
68
+ ?readonly="${args.readonly}">
69
+ </pds-rating>
70
+ `;
71
+
72
+ export const Default = {
73
+ name: 'Interactive Playground',
74
+ render: baseTemplate,
75
+ args: {
76
+ max: 5,
77
+ value: 3.5,
78
+ name: 'rating',
79
+ color: '',
80
+ disabled: false,
81
+ required: false,
82
+ readonly: false
83
+ }
84
+ };
85
+
86
+ export const States = {
87
+ render: () => html`
88
+ <div class="stack-md">
89
+ <label class="stack-2xs">
90
+ <span>Default (editable)</span>
91
+ <pds-rating max="5" value="4" name="default-rating"></pds-rating>
92
+ </label>
93
+ <label class="stack-2xs">
94
+ <span>Accent color variant</span>
95
+ <pds-rating max="5" value="3.5" color="var(--color-accent-500)" name="color-rating"></pds-rating>
96
+ </label>
97
+ <label class="stack-2xs">
98
+ <span>Read-only</span>
99
+ <pds-rating max="5" value="2.5" readonly name="readonly-rating"></pds-rating>
100
+ </label>
101
+ <label class="stack-2xs">
102
+ <span>Disabled</span>
103
+ <pds-rating max="5" value="3" disabled name="disabled-rating"></pds-rating>
104
+ </label>
105
+ </div>
106
+ `
107
+ };
108
+
109
+ export const InForm = {
110
+ render: () => html`
111
+ <form id="rating-story-form" class="stack-sm" @submit="${(event) => {
112
+ event.preventDefault();
113
+ const form = event.currentTarget;
114
+ const data = new FormData(form);
115
+ const output = form.querySelector('[data-output]');
116
+ output.textContent = `Submitted rating: ${String(data.get('experience') ?? '(none)')}`;
117
+ }}">
118
+ <label class="stack-2xs">
119
+ <span>How was your experience?</span>
120
+ <pds-rating max="5" value="4.5" required name="experience"></pds-rating>
121
+ </label>
122
+ <button type="submit" class="btn-primary">Submit</button>
123
+ <p class="text-muted" data-output>Submitted rating: (none)</p>
124
+ </form>
125
+ `
126
+ };
@@ -0,0 +1,224 @@
1
+ import { html, lazyProps } from "#pds/lit";
2
+
3
+ const languageOptions = [
4
+ { id: "et", text: "Amhaars", description: "ET · Ethiopia", category: "Languages" },
5
+ { id: "bd", text: "Bengaals", description: "BD · Bangladesh", category: "Languages" },
6
+ { id: "nl", text: "Nederlands", description: "NL · Netherlands", category: "Languages" },
7
+ { id: "ar", text: "Arabisch", description: "SA · Arabic", category: "Languages" },
8
+ { id: "bs", text: "Bosnisch", description: "BA · Bosnia and Herzegovina", category: "Languages" },
9
+ { id: "bg", text: "Bulgaars", description: "BG · Bulgaria", category: "Languages" },
10
+ { id: "fr", text: "Frans", description: "FR · France", category: "Languages" },
11
+ { id: "de", text: "Duits", description: "DE · Germany", category: "Languages" },
12
+ { id: "es", text: "Spaans", description: "ES · Spain", category: "Languages" },
13
+ { id: "en", text: "Engels", description: "US · United Kingdom", category: "Languages" },
14
+ ];
15
+
16
+ const groupedOptions = [
17
+ { id: "et", text: "Amhaars", description: "ET · Ethiopia", category: "Languages" },
18
+ { id: "nl", text: "Nederlands", description: "NL · Netherlands", category: "Languages" },
19
+ { id: "react", text: "React", description: "Frontend", category: "Skills" },
20
+ { id: "wc", text: "Web Components", description: "Frontend", category: "Skills" },
21
+ { id: "figma", text: "Figma", description: "Design tooling", category: "Tools" },
22
+ { id: "storybook", text: "Storybook", description: "Component docs", category: "Tools" },
23
+ ];
24
+
25
+ const createSettingsFromItems = (items) => {
26
+ const byCategory = items.reduce((map, item) => {
27
+ const key = item.category || "Options";
28
+ if (!map.has(key)) map.set(key, []);
29
+ map.get(key).push(item);
30
+ return map;
31
+ }, new Map());
32
+
33
+ const categories = {};
34
+ for (const [categoryName, entries] of byCategory.entries()) {
35
+ categories[categoryName] = {
36
+ trigger: () => true,
37
+ getItems: async (options) => {
38
+ const query = (options?.search || "").trim().toLowerCase();
39
+ return entries.filter((item) => {
40
+ if (!query) return true;
41
+ return (
42
+ item.text.toLowerCase().includes(query)
43
+ || item.id.toLowerCase().includes(query)
44
+ || (item.description || "").toLowerCase().includes(query)
45
+ );
46
+ });
47
+ },
48
+ };
49
+ }
50
+
51
+ return {
52
+ hideCategory: false,
53
+ categories,
54
+ };
55
+ };
56
+
57
+ const languageSettings = createSettingsFromItems(languageOptions);
58
+ const groupedSettings = createSettingsFromItems(groupedOptions);
59
+
60
+ const sourceCode = `<pds-tags
61
+ name="languages"
62
+ placeholder="Zoek taal..."
63
+ value="et,bd,nl"
64
+ ${"${lazyProps({ settings: languageSettings })}"}
65
+ ></pds-tags>`;
66
+
67
+ const defaultDocs = {
68
+ description: {
69
+ component: "Compound multi-select built with pds-omnibox. Select/unselect values from suggestions and manage them as tags.",
70
+ },
71
+ };
72
+
73
+ export default {
74
+ title: "Components/pds-tags",
75
+ tags: ["autodocs", "forms", "autocomplete", "multiselect", "pds-tags"],
76
+ parameters: {
77
+ docs: defaultDocs,
78
+ },
79
+ argTypes: {
80
+ name: { control: "text" },
81
+ placeholder: { control: "text" },
82
+ value: { control: "text" },
83
+ required: { control: "boolean" },
84
+ disabled: { control: "boolean" },
85
+ },
86
+ };
87
+
88
+ const Template = ({ name, placeholder, value, required, disabled }) => html`
89
+ <div class="stack-md">
90
+ <pds-tags
91
+ name=${name}
92
+ placeholder=${placeholder}
93
+ value=${value}
94
+ ?required=${required}
95
+ ?disabled=${disabled}
96
+ ${lazyProps({ settings: languageSettings })}
97
+ ></pds-tags>
98
+ </div>
99
+ `;
100
+
101
+ const FormTemplate = ({ required }) => {
102
+ setTimeout(() => {
103
+ const form = document.querySelector("#tags-form");
104
+ const output = document.querySelector("#tags-output");
105
+ if (!form || !output || form.dataset.bound === "true") return;
106
+
107
+ form.dataset.bound = "true";
108
+ form.addEventListener("submit", (event) => {
109
+ event.preventDefault();
110
+ if (!form.reportValidity()) {
111
+ output.textContent = "Form invalid";
112
+ return;
113
+ }
114
+ const data = new FormData(form);
115
+ const languages = data.getAll("languages");
116
+ output.textContent = JSON.stringify({ languages }, null, 2);
117
+ });
118
+
119
+ form.addEventListener("reset", () => {
120
+ setTimeout(() => {
121
+ output.textContent = "{}";
122
+ }, 0);
123
+ });
124
+ }, 0);
125
+
126
+ return html`
127
+ <section class="card stack-md">
128
+ <h4>Form Integration</h4>
129
+ <form id="tags-form" class="stack-md">
130
+ <pds-tags
131
+ name="languages"
132
+ placeholder="Select one or more languages..."
133
+ ?required=${required}
134
+ ${lazyProps({ settings: languageSettings })}
135
+ ></pds-tags>
136
+ <div class="flex gap-sm">
137
+ <button class="btn-primary" type="submit">Submit</button>
138
+ <button class="btn-secondary" type="reset">Reset</button>
139
+ </div>
140
+ </form>
141
+ <pre id="tags-output" class="card surface-subtle">{}</pre>
142
+ </section>
143
+ `;
144
+ };
145
+
146
+ export const Default = {
147
+ args: {
148
+ name: "languages",
149
+ placeholder: "Zoek taal...",
150
+ value: "et,bd,nl",
151
+ required: false,
152
+ disabled: false,
153
+ },
154
+ render: Template,
155
+ parameters: {
156
+ docs: {
157
+ source: {
158
+ code: sourceCode,
159
+ },
160
+ },
161
+ },
162
+ };
163
+
164
+ export const Empty = {
165
+ args: {
166
+ name: "languages",
167
+ placeholder: "Zoek taal...",
168
+ value: "",
169
+ required: false,
170
+ disabled: false,
171
+ },
172
+ render: Template,
173
+ };
174
+
175
+ export const Required = {
176
+ args: {
177
+ name: "languages",
178
+ placeholder: "Select at least one language...",
179
+ value: "",
180
+ required: true,
181
+ disabled: false,
182
+ },
183
+ render: Template,
184
+ };
185
+
186
+ export const Disabled = {
187
+ args: {
188
+ name: "languages",
189
+ placeholder: "Zoek taal...",
190
+ value: "et,nl",
191
+ required: false,
192
+ disabled: true,
193
+ },
194
+ render: Template,
195
+ };
196
+
197
+ export const GroupedOptions = {
198
+ args: {
199
+ name: "selections",
200
+ placeholder: "Search languages, skills, or tools...",
201
+ value: "nl,wc,figma",
202
+ required: false,
203
+ disabled: false,
204
+ },
205
+ render: ({ name, placeholder, value, required, disabled }) => html`
206
+ <div class="stack-md">
207
+ <pds-tags
208
+ name=${name}
209
+ placeholder=${placeholder}
210
+ value=${value}
211
+ ?required=${required}
212
+ ?disabled=${disabled}
213
+ ${lazyProps({ settings: groupedSettings })}
214
+ ></pds-tags>
215
+ </div>
216
+ `,
217
+ };
218
+
219
+ export const FormSubmission = {
220
+ args: {
221
+ required: true,
222
+ },
223
+ render: FormTemplate,
224
+ };
@@ -70,7 +70,7 @@ export const Default = () => {
70
70
  <pds-icon icon="info" size="sm"></pds-icon>
71
71
  Info
72
72
  </button>
73
- <button id="toast-warning" class="btn-warning btn-sm">
73
+ <button id="toast-warning" class="btn-outline btn-sm">
74
74
  <pds-icon icon="warning" size="sm"></pds-icon>
75
75
  Warning
76
76
  </button>
@@ -0,0 +1,63 @@
1
+ let countryListPromise;
2
+
3
+ const loadCountries = async () => {
4
+ if (!countryListPromise) {
5
+ countryListPromise = fetch("https://restcountries.com/v3.1/all?fields=name,cca2")
6
+ .then((res) => {
7
+ if (!res.ok) {
8
+ throw new Error(`Failed to fetch countries: ${res.status}`);
9
+ }
10
+ return res.json();
11
+ })
12
+ .then((rows) =>
13
+ rows
14
+ .map((row) => ({
15
+ id: row?.cca2 || row?.name?.common || "",
16
+ text: row?.name?.common || "",
17
+ icon: "globe",
18
+ }))
19
+ .filter((row) => row.id && row.text)
20
+ .sort((a, b) => a.text.localeCompare(b.text)),
21
+ );
22
+ }
23
+
24
+ return countryListPromise;
25
+ };
26
+
27
+ export const countriesApiSettings = {
28
+ hideCategory: true,
29
+ itemGrid: "0 1fr 0",
30
+ iconHandler: () => "",
31
+ categories: {
32
+ Featured: {
33
+ sortIndex: 2,
34
+ trigger: () => true,
35
+ getItems: async (options) => {
36
+ const q = (options.search || "").trim().toLowerCase();
37
+ const shortlist = [
38
+ { id: "NL", text: "Netherlands" },
39
+ { id: "US", text: "United States" },
40
+ ];
41
+
42
+ return q
43
+ ? shortlist.filter((item) => item.text.toLowerCase().includes(q))
44
+ : shortlist;
45
+ },
46
+ useIconForInput: false,
47
+ },
48
+ Countries: {
49
+ sortIndex: 1,
50
+ trigger: (options) => (options.search || "").trim().length >= 2,
51
+ getItems: async (options) => {
52
+ const q = (options.search || "").trim().toLowerCase();
53
+ if (!q) return [];
54
+
55
+ const countries = await loadCountries();
56
+ return countries
57
+ .filter((item) => item.text.toLowerCase().includes(q))
58
+ .slice(0, 30);
59
+ },
60
+ useIconForInput: false,
61
+ },
62
+ },
63
+ };
@@ -267,7 +267,12 @@ export const Default = {
267
267
  return html`
268
268
  ${colorScaleStoryStyles}
269
269
  <div class="story-container color-scale-story-container">
270
- <h2>Color Scales</h2>
270
+ <header>
271
+ <h3>Color Scales</h3>
272
+ <small class="text-muted">
273
+ Explore the full tokenized scales for brand and semantic colors.
274
+ </small>
275
+ </header>
271
276
  ${renderColorScale('primary')}
272
277
  ${renderColorScale('secondary')}
273
278
  ${renderColorScale('accent')}
@@ -21,7 +21,7 @@ export const Overview = {
21
21
  const container = document.createElement('div');
22
22
  container.innerHTML = `
23
23
  <header >
24
- <h1>HTML Defaults</h1>
24
+ <h3>HTML Defaults</h3>
25
25
  <small class="text-muted">
26
26
  PDS applies an opinionated reset to semantic HTML elements using low-specificity
27
27
  <code>:where()</code> selectors. This means you get a polished, modern look out of the box,
@@ -164,7 +164,7 @@ export const StructuralElements = {
164
164
  const container = document.createElement('div');
165
165
  container.innerHTML = `
166
166
  <header class="card">
167
- <h2>Structural Elements</h2>
167
+ <h3>Structural Elements</h3>
168
168
  <small class="text-muted">
169
169
  Semantic containers that organize page content
170
170
  </small>
@@ -230,7 +230,7 @@ export const TextElements = {
230
230
  const container = document.createElement('div');
231
231
  container.innerHTML = `
232
232
  <header class="card">
233
- <h2>Text & Quotation Elements</h2>
233
+ <h3>Text & Quotation Elements</h3>
234
234
  <small class="text-muted">
235
235
  Semantic text elements with automatic styling
236
236
  </small>
@@ -292,7 +292,7 @@ TextElements.storyName = 'Text Elements';
292
292
 
293
293
  export const InlineElements = () => html`
294
294
  <header class="card">
295
- <h2>Inline Elements</h2>
295
+ <h3>Inline Elements</h3>
296
296
  <small class="text-muted">
297
297
  Small semantic elements for inline content
298
298
  </small>
@@ -347,7 +347,7 @@ export const DetailsAndSummary = {
347
347
  const container = document.createElement('div');
348
348
  container.innerHTML = `
349
349
  <header class="card">
350
- <h2>Details & Summary</h2>
350
+ <h3>Details & Summary</h3>
351
351
  <small class="text-muted">
352
352
  Native collapsible content without JavaScript
353
353
  </small>
@@ -409,7 +409,7 @@ export const RelatedDocumentation = () => {
409
409
  container.className = 'stack gap-md';
410
410
  container.innerHTML = `
411
411
  <header class="card">
412
- <h2>Related Documentation</h2>
412
+ <h3>Related Documentation</h3>
413
413
  <small class="text-muted">
414
414
  Detailed coverage of specific element categories
415
415
  </small>
@@ -78,8 +78,8 @@ export const Overview = () => {
78
78
 
79
79
  container.innerHTML = /*html*/`
80
80
  <header>
81
- <h2>PDS Icon System</h2>
82
- <p class="story-intro">PDS ships a simple, sprite-based icon solution and a lightweight <code>&lt;pds-icon&gt;</code> web component.</p>
81
+ <h3>PDS Icon System</h3>
82
+ <small class="text-muted story-intro">PDS ships a simple, sprite-based icon solution and a lightweight <code>&lt;pds-icon&gt;</code> web component.</small>
83
83
  </header>
84
84
 
85
85
  <section class="card">
@@ -518,7 +518,7 @@ export const AllIcons = () => {
518
518
  ${iconGridStyles}
519
519
  <section class="stack-lg story-section">
520
520
  <header>
521
- <h2>All Available Icons</h2>
521
+ <h3>All Available Icons</h3>
522
522
  <small class="text-muted">Complete icon set from Phosphor Icons, organized by category. Click any icon name to copy it to clipboard.</small>
523
523
  </header>
524
524
 
@@ -607,7 +607,7 @@ AllIcons.storyName = 'All Icons';
607
607
  export const ExternalIcons = () => html`
608
608
  <section class="stack-lg story-section">
609
609
  <header>
610
- <h2>External Icons (On-Demand Loading)</h2>
610
+ <h3>External Icons (On-Demand Loading)</h3>
611
611
  <small class="text-muted">
612
612
  Icons not in the sprite sheet are automatically fetched from an external path.
613
613
  This provides the best of both worlds: core icons load instantly from the cached
@@ -77,7 +77,7 @@ export const AllMeshGradients = () => html`
77
77
  ${meshGradientsStoryStyles}
78
78
  <section class="stack gap-lg">
79
79
  <header>
80
- <h2>Mesh Gradient Backgrounds</h2>
80
+ <h3>Mesh Gradient Backgrounds</h3>
81
81
  <small class="text-muted">
82
82
  Subtle, beautiful mesh gradient backgrounds using
83
83
  <code>--background-mesh-01</code> through
@@ -123,7 +123,12 @@ AllMeshGradients.storyName = "All Mesh Gradients";
123
123
  export const MeshUsageExamples = () => html`
124
124
  ${meshGradientsStoryStyles}
125
125
  <section class="stack gap-lg">
126
- <h2>Usage Examples</h2>
126
+ <header>
127
+ <h3>Usage Examples</h3>
128
+ <small class="text-muted">
129
+ Practical composition patterns that use mesh tokens in real UI blocks.
130
+ </small>
131
+ </header>
127
132
 
128
133
  <h3>Hero Section</h3>
129
134
  <div
@@ -211,7 +216,12 @@ export const CodeSamples = () => {
211
216
 
212
217
  container.innerHTML = /*html*/ `
213
218
  ${meshGradientsStoryStyles.strings[0]}
214
- <h2>Code Samples</h2>
219
+ <header>
220
+ <h3>Code Samples</h3>
221
+ <small class="text-muted">
222
+ Copy-ready snippets and available mesh custom properties.
223
+ </small>
224
+ </header>
215
225
 
216
226
  <h3>Apply as Background</h3>
217
227
  <div class="code-css"></div>
@@ -57,11 +57,13 @@ export default {
57
57
 
58
58
  export const SurfaceVariants = () => html`
59
59
  <div class="card">
60
- <h2>Surface Variants</h2>
61
- <p class="text-muted">
62
- All surfaces automatically adjust text, icon, shadow, and border colors to
63
- maintain proper contrast.
64
- </p>
60
+ <header>
61
+ <h3>Surface Variants</h3>
62
+ <small class="text-muted">
63
+ All surfaces automatically adjust text, icon, shadow, and border colors
64
+ to maintain proper contrast.
65
+ </small>
66
+ </header>
65
67
 
66
68
  <div class="grid grid-auto-sm gap-md">
67
69
  <div class="card surface-base">
@@ -108,7 +110,7 @@ SurfaceVariants.storyName = "Surface Variants";
108
110
  export const ContextAwareShadows = () => html`
109
111
  <div>
110
112
  <header>
111
- <h2>Context-Aware Shadows</h2>
113
+ <h3>Context-Aware Shadows</h3>
112
114
  <small class="text-muted">
113
115
  Shadows automatically invert in dark mode: dark shadows on light surfaces,
114
116
  light shadows on dark surfaces.
@@ -141,11 +143,13 @@ ContextAwareShadows.storyName = "Context-Aware Shadows";
141
143
 
142
144
  export const NestedSurfaces = () => html`
143
145
  <div class="card">
144
- <h2>Nested Surfaces</h2>
145
- <p class="text-muted">
146
- Surfaces can be nested infinitely. Each level maintains proper contrast
147
- and visual hierarchy automatically.
148
- </p>
146
+ <header>
147
+ <h3>Nested Surfaces</h3>
148
+ <small class="text-muted">
149
+ Surfaces can be nested infinitely. Each level maintains proper contrast
150
+ and visual hierarchy automatically.
151
+ </small>
152
+ </header>
149
153
 
150
154
  <div class="card surface-base">
151
155
  <h4 class="flex items-center gap-xs">
@@ -201,11 +205,13 @@ NestedSurfaces.storyName = "Nested Surfaces";
201
205
 
202
206
  export const SurfaceInversion = () => html`
203
207
  <div class="card">
204
- <h2>Surface Inversion</h2>
205
- <p class="text-muted">
206
- The smart surface system automatically inverts text and icon colors on
207
- contrasting backgrounds. Toggle dark mode to see the magic!
208
- </p>
208
+ <header>
209
+ <h3>Surface Inversion</h3>
210
+ <small class="text-muted">
211
+ The smart surface system automatically inverts text and icon colors on
212
+ contrasting backgrounds. Toggle dark mode to see the magic!
213
+ </small>
214
+ </header>
209
215
 
210
216
  <div class="grid grid-auto-md gap-md">
211
217
  <div class="card surface-inverse">
@@ -236,11 +242,13 @@ SurfaceInversion.storyName = "Surface Inversion";
236
242
 
237
243
  export const BorderGradients = () => html`
238
244
  <div class="card">
239
- <h2>Surface Border Effects</h2>
240
- <p class="text-muted">
241
- Advanced border gradient effects for standout surfaces. All gradients are
242
- animated and adapt to your theme.
243
- </p>
245
+ <header>
246
+ <h3>Surface Border Effects</h3>
247
+ <small class="text-muted">
248
+ Advanced border gradient effects for standout surfaces. All gradients
249
+ are animated and adapt to your theme.
250
+ </small>
251
+ </header>
244
252
 
245
253
  <div class="grid grid-auto-md gap-lg">
246
254
  <article class="card border-gradient">
@@ -309,11 +317,13 @@ BorderGradients.storyName = "Border Gradients";
309
317
  export const TranslucentSurfaces = () => html`
310
318
  ${smartSurfacesStoryStyles}
311
319
  <div class="card">
312
- <h2>Translucent Surfaces</h2>
313
- <p class="text-muted">
314
- Semi-transparent surfaces for glassmorphism effects and layered UI. Works
315
- beautifully over images and gradients.
316
- </p>
320
+ <header>
321
+ <h3>Translucent Surfaces</h3>
322
+ <small class="text-muted">
323
+ Semi-transparent surfaces for glassmorphism effects and layered UI.
324
+ Works beautifully over images and gradients.
325
+ </small>
326
+ </header>
317
327
 
318
328
  <div class="story-translucent-container">
319
329
  <img
@@ -355,11 +365,13 @@ TranslucentSurfaces.storyName = "Translucent Surfaces";
355
365
 
356
366
  export const SurfaceInverseExpanded = () => html`
357
367
  <div class="card">
358
- <h2>Inverse Surface Deep Dive</h2>
359
- <p class="text-muted">
360
- The <code>.surface-inverse</code> class automatically inverts all text,
361
- icon, and border colors for perfect contrast on dark backgrounds.
362
- </p>
368
+ <header>
369
+ <h3>Inverse Surface Deep Dive</h3>
370
+ <small class="text-muted">
371
+ The <code>.surface-inverse</code> class automatically inverts all text,
372
+ icon, and border colors for perfect contrast on dark backgrounds.
373
+ </small>
374
+ </header>
363
375
 
364
376
  <div class="grid grid-auto-md gap-lg">
365
377
  <article class="card surface-inverse">