@pure-ds/storybook 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 (129) hide show
  1. package/.storybook/addons/description/preview.js +15 -0
  2. package/.storybook/addons/description/register.js +60 -0
  3. package/.storybook/addons/html-preview/Panel.jsx +327 -0
  4. package/.storybook/addons/html-preview/constants.js +6 -0
  5. package/.storybook/addons/html-preview/preview.js +178 -0
  6. package/.storybook/addons/html-preview/register.js +16 -0
  7. package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
  8. package/.storybook/addons/pds-configurator/Tool.js +30 -0
  9. package/.storybook/addons/pds-configurator/constants.js +9 -0
  10. package/.storybook/addons/pds-configurator/preview.js +159 -0
  11. package/.storybook/addons/pds-configurator/register.js +24 -0
  12. package/.storybook/docs.css +35 -0
  13. package/.storybook/htmlPreview.css +103 -0
  14. package/.storybook/htmlPreview.js +271 -0
  15. package/.storybook/main.js +160 -0
  16. package/.storybook/preview-body.html +48 -0
  17. package/.storybook/preview-head.html +11 -0
  18. package/.storybook/preview.js +1563 -0
  19. package/README.md +266 -0
  20. package/bin/index.js +40 -0
  21. package/dist/pds-reference.json +2101 -0
  22. package/package.json +45 -0
  23. package/pds.config.js +6 -0
  24. package/public/assets/css/app.css +1216 -0
  25. package/public/assets/data/auto-design-advanced.json +704 -0
  26. package/public/assets/data/auto-design-simple.json +123 -0
  27. package/public/assets/img/icon-512x512.png +0 -0
  28. package/public/assets/img/logo-trans.png +0 -0
  29. package/public/assets/img/logo.png +0 -0
  30. package/public/assets/js/app.js +15088 -0
  31. package/public/assets/js/app.js.map +7 -0
  32. package/public/assets/js/lit.js +1176 -0
  33. package/public/assets/js/lit.js.map +7 -0
  34. package/public/assets/js/pds.js +9801 -0
  35. package/public/assets/js/pds.js.map +7 -0
  36. package/public/assets/pds/components/pds-calendar.js +837 -0
  37. package/public/assets/pds/components/pds-drawer.js +857 -0
  38. package/public/assets/pds/components/pds-icon.js +338 -0
  39. package/public/assets/pds/components/pds-jsonform.js +1775 -0
  40. package/public/assets/pds/components/pds-richtext.js +1035 -0
  41. package/public/assets/pds/components/pds-scrollrow.js +331 -0
  42. package/public/assets/pds/components/pds-splitpanel.js +401 -0
  43. package/public/assets/pds/components/pds-tabstrip.js +251 -0
  44. package/public/assets/pds/components/pds-toaster.js +446 -0
  45. package/public/assets/pds/components/pds-upload.js +657 -0
  46. package/public/assets/pds/custom-elements.json +2003 -0
  47. package/public/assets/pds/icons/pds-icons.svg +498 -0
  48. package/public/assets/pds/pds-css-complete.json +1861 -0
  49. package/public/assets/pds/pds-runtime-config.json +11 -0
  50. package/public/assets/pds/pds.css-data.json +2152 -0
  51. package/public/assets/pds/styles/pds-components.css +1944 -0
  52. package/public/assets/pds/styles/pds-components.css.js +3895 -0
  53. package/public/assets/pds/styles/pds-primitives.css +352 -0
  54. package/public/assets/pds/styles/pds-primitives.css.js +711 -0
  55. package/public/assets/pds/styles/pds-styles.css +3761 -0
  56. package/public/assets/pds/styles/pds-styles.css.js +7529 -0
  57. package/public/assets/pds/styles/pds-tokens.css +699 -0
  58. package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
  59. package/public/assets/pds/styles/pds-utilities.css +763 -0
  60. package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
  61. package/public/assets/pds/vscode-custom-data.json +824 -0
  62. package/scripts/build-pds-reference.mjs +807 -0
  63. package/scripts/generate-stories.js +542 -0
  64. package/scripts/package-build.js +86 -0
  65. package/src/js/app.js +17 -0
  66. package/src/js/common/ask.js +208 -0
  67. package/src/js/common/common.js +20 -0
  68. package/src/js/common/font-loader.js +200 -0
  69. package/src/js/common/msg.js +90 -0
  70. package/src/js/lit.js +40 -0
  71. package/src/js/pds-core/pds-config.js +1162 -0
  72. package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
  73. package/src/js/pds-core/pds-enhancers.js +357 -0
  74. package/src/js/pds-core/pds-enums.js +86 -0
  75. package/src/js/pds-core/pds-generator.js +5317 -0
  76. package/src/js/pds-core/pds-ontology.js +256 -0
  77. package/src/js/pds-core/pds-paths.js +109 -0
  78. package/src/js/pds-core/pds-query.js +571 -0
  79. package/src/js/pds-core/pds-registry.js +129 -0
  80. package/src/js/pds-core/pds.d.ts +129 -0
  81. package/src/js/pds.d.ts +408 -0
  82. package/src/js/pds.js +1579 -0
  83. package/src/pds-core/pds-api.js +105 -0
  84. package/stories/GettingStarted.md +96 -0
  85. package/stories/GettingStarted.stories.js +144 -0
  86. package/stories/WhatIsPDS.md +194 -0
  87. package/stories/WhatIsPDS.stories.js +144 -0
  88. package/stories/components/PdsCalendar.stories.js +263 -0
  89. package/stories/components/PdsDrawer.stories.js +623 -0
  90. package/stories/components/PdsIcon.stories.js +78 -0
  91. package/stories/components/PdsJsonform.stories.js +1444 -0
  92. package/stories/components/PdsRichtext.stories.js +367 -0
  93. package/stories/components/PdsScrollrow.stories.js +140 -0
  94. package/stories/components/PdsSplitpanel.stories.js +502 -0
  95. package/stories/components/PdsTabstrip.stories.js +442 -0
  96. package/stories/components/PdsToaster.stories.js +186 -0
  97. package/stories/components/PdsUpload.stories.js +66 -0
  98. package/stories/enhancements/Dropdowns.stories.js +185 -0
  99. package/stories/enhancements/InteractiveStates.stories.js +625 -0
  100. package/stories/enhancements/MeshGradients.stories.js +320 -0
  101. package/stories/enhancements/OpenGroups.stories.js +227 -0
  102. package/stories/enhancements/RangeSliders.stories.js +232 -0
  103. package/stories/enhancements/RequiredFields.stories.js +189 -0
  104. package/stories/enhancements/Toggles.stories.js +167 -0
  105. package/stories/foundations/Colors.stories.js +283 -0
  106. package/stories/foundations/Icons.stories.js +305 -0
  107. package/stories/foundations/SmartSurfaces.stories.js +367 -0
  108. package/stories/foundations/Spacing.stories.js +175 -0
  109. package/stories/foundations/Typography.stories.js +960 -0
  110. package/stories/foundations/ZIndex.stories.js +325 -0
  111. package/stories/patterns/BorderEffects.stories.js +72 -0
  112. package/stories/patterns/Layout.stories.js +99 -0
  113. package/stories/patterns/Utilities.stories.js +107 -0
  114. package/stories/primitives/Accordion.stories.js +359 -0
  115. package/stories/primitives/Alerts.stories.js +64 -0
  116. package/stories/primitives/Badges.stories.js +183 -0
  117. package/stories/primitives/Buttons.stories.js +229 -0
  118. package/stories/primitives/Cards.stories.js +353 -0
  119. package/stories/primitives/FormGroups.stories.js +569 -0
  120. package/stories/primitives/Forms.stories.js +131 -0
  121. package/stories/primitives/Media.stories.js +203 -0
  122. package/stories/primitives/Tables.stories.js +232 -0
  123. package/stories/reference/ReferenceCatalog.stories.js +28 -0
  124. package/stories/reference/reference-catalog.js +413 -0
  125. package/stories/reference/reference-docs.js +302 -0
  126. package/stories/reference/reference-helpers.js +310 -0
  127. package/stories/utilities/GridSystem.stories.js +208 -0
  128. package/stories/utils/PdsAsk.stories.js +420 -0
  129. package/stories/utils/toast-utils.js +148 -0
@@ -0,0 +1,1563 @@
1
+ import { addons } from '@storybook/preview-api';
2
+ import { SELECT_STORY } from '@storybook/core-events';
3
+ import React from 'react';
4
+ import { Title, Subtitle, Description as DocsDescription, Controls } from '@storybook/blocks';
5
+ import { PDS } from '@pds-src/js/pds.js';
6
+ import { presets } from '@pds-src/js/pds-core/pds-config.js';
7
+ import { config as userConfig } from '@user/pds-config';
8
+ import './addons/pds-configurator/preview.js';
9
+ import { withHTMLExtractor } from './addons/html-preview/preview.js';
10
+ import { withDescription } from './addons/description/preview.js';
11
+ import './htmlPreview.css';
12
+ import './docs.css';
13
+ import { toastFormData } from '../stories/utils/toast-utils.js';
14
+
15
+ // Expose toastFormData globally for inline event handlers
16
+ window.toastFormData = toastFormData;
17
+
18
+ // Get initial preset from storage or URL or default
19
+ const getInitialPreset = () => {
20
+ try {
21
+ // Check sessionStorage first (per-session)
22
+ const sessionPreset = sessionStorage.getItem('storybook-pds-preset');
23
+ if (sessionPreset) return sessionPreset;
24
+
25
+ // Check localStorage (persistent)
26
+ const storedPreset = localStorage.getItem('storybook-pds-preset');
27
+ if (storedPreset) return storedPreset;
28
+
29
+ // Check URL params
30
+ const urlParams = new URLSearchParams(window.location.search);
31
+ const urlPreset = urlParams.get('preset');
32
+ if (urlPreset) return urlPreset;
33
+ } catch (e) {
34
+ console.warn('Failed to read preset from storage:', e);
35
+ }
36
+
37
+ return 'default'; // Default preset
38
+ };
39
+
40
+ const initialPreset = getInitialPreset();
41
+ console.log('🎨 Starting PDS initialization with preset:', initialPreset);
42
+
43
+ // Wrap top-level await in IIFE for production build compatibility
44
+ (async () => {
45
+ const pdsOptions = {
46
+ mode: 'live',
47
+ preset: initialPreset,
48
+ autoDefine: {
49
+ baseURL: '/assets/pds/components/',
50
+ predefine: ['pds-icon'],
51
+ scanExisting: true,
52
+ observeShadows: true,
53
+ patchAttachShadow: true
54
+ },
55
+ applyGlobalStyles: true,
56
+ manageTheme: true
57
+ };
58
+
59
+ // Merge user config
60
+ if (userConfig) {
61
+ if (userConfig.mode) pdsOptions.mode = userConfig.mode;
62
+ if (userConfig.autoDefine) {
63
+ // Merge autoDefine options
64
+ pdsOptions.autoDefine = {
65
+ ...pdsOptions.autoDefine,
66
+ ...userConfig.autoDefine
67
+ };
68
+ }
69
+ }
70
+
71
+ await PDS.start(pdsOptions);
72
+
73
+ console.log('✨ PDS initialized in live mode for Storybook');
74
+ console.log('📦 AutoDefiner active at:', PDS.autoDefiner?.config?.baseURL);
75
+
76
+ // Store PDS designer globally for reuse
77
+ window.__pdsDesigner = PDS.registry._designer;
78
+ window.__pdsCurrentPreset = initialPreset;
79
+ })();
80
+
81
+ // Set up persistent style protection - monitor and restore PDS sheets if cleared
82
+ let protectionActive = false;
83
+ function ensurePDSStyles() {
84
+ const sheets = document.adoptedStyleSheets || [];
85
+ const hasPDS = sheets.some(s => s._pds === true);
86
+
87
+ if (!hasPDS && window.__pdsDesigner) {
88
+ console.log('🛡️ PDS sheets missing - restoring...');
89
+ PDS.Generator.applyStyles(window.__pdsDesigner);
90
+ }
91
+ }
92
+
93
+ // Check periodically
94
+ setInterval(ensurePDSStyles, 100);
95
+
96
+ /**
97
+ * Global decorator to ensure Shadow DOM components get PDS styles and run enhancers
98
+ */
99
+ const withPDS = (story, context) => {
100
+ console.log('🎬 withPDS decorator called for:', context.title);
101
+
102
+ // Check adoptedStyleSheets status
103
+ const currentSheets = document.adoptedStyleSheets || [];
104
+ const pdsSheets = currentSheets.filter(s => s._pds === true);
105
+ console.log('📋 Current adoptedStyleSheets:', currentSheets.length, 'PDS sheets:', pdsSheets.length);
106
+
107
+ // ALWAYS reapply PDS styles before each story render
108
+ const designer = window.__pdsDesigner || PDS.registry._designer;
109
+ if (designer) {
110
+ PDS.Generator.applyStyles(designer);
111
+
112
+ // Check again after applying
113
+ const afterSheets = document.adoptedStyleSheets || [];
114
+ const afterPdsSheets = afterSheets.filter(s => s._pds === true);
115
+ } else {
116
+ console.warn('⚠️ No designer found!');
117
+ }
118
+
119
+ // Render story
120
+ const storyResult = story();
121
+
122
+ // After render, ensure shadow roots get PDS styles and run enhancers
123
+ // Use MutationObserver to continuously adopt layers when components update
124
+ const adoptAllShadowStyles = async () => {
125
+ const container = document.querySelector('#storybook-root');
126
+ if (!container) return;
127
+
128
+ const walker = document.createTreeWalker(
129
+ container,
130
+ NodeFilter.SHOW_ELEMENT,
131
+ null
132
+ );
133
+
134
+ let node;
135
+ const shadowRoots = [];
136
+ while (node = walker.nextNode()) {
137
+ if (node.shadowRoot) {
138
+ shadowRoots.push({ root: node.shadowRoot, host: node.tagName });
139
+ }
140
+ }
141
+
142
+ // if (shadowRoots.length > 0) {
143
+ // console.log(`🎭 Adopting PDS layers for ${shadowRoots.length} shadow components`);
144
+ // }
145
+
146
+ // Check if shadow roots need PDS styles adoption
147
+ // DON'T re-adopt if they already have styles - this preserves component internal stylesheets
148
+ for (const { root, host } of shadowRoots) {
149
+ try {
150
+ const currentSheets = root.adoptedStyleSheets || [];
151
+
152
+ // Check if this shadow root already has PDS sheets
153
+ const hasPDSSheets = currentSheets.some(sheet => {
154
+ try {
155
+ // Check if it's a PDS sheet by looking for PDS-specific selectors
156
+ return Array.from(sheet.cssRules || []).some(rule =>
157
+ rule.selectorText?.includes(':where') ||
158
+ rule.cssText?.includes('--color-') ||
159
+ rule.cssText?.includes('--spacing-')
160
+ );
161
+ } catch {
162
+ return false;
163
+ }
164
+ });
165
+
166
+ if (hasPDSSheets && currentSheets.length > 0) {
167
+ //console.log(`⏭️ <${host.toLowerCase()}> already has ${currentSheets.length} sheets - skipping`);
168
+ continue;
169
+ }
170
+
171
+ // Only adopt if the component doesn't have PDS styles yet
172
+ // Get existing adopted sheets that aren't PDS sheets (preserve component styles)
173
+ const existingSheets = currentSheets.filter(sheet => !sheet._pds);
174
+
175
+ //console.log(`🎨 Adopting layers for <${host.toLowerCase()}> (had ${currentSheets.length} sheets, ${existingSheets.length} non-PDS)...`);
176
+
177
+ // Adopt full layer stack: primitives, components, utilities
178
+ await PDS.adoptLayers(root, ['primitives', 'components', 'utilities'], existingSheets);
179
+
180
+ //console.log(`✅ Adopted layers for <${host.toLowerCase()}> (now ${root.adoptedStyleSheets.length} sheets)`);
181
+ } catch (err) {
182
+ console.error(`❌ Failed to adopt PDS layers for <${host.toLowerCase()}>:`, err);
183
+ console.error(err.stack);
184
+ }
185
+ }
186
+
187
+ // Run enhancers on newly rendered content
188
+ if (PDS.enhancer && typeof PDS.enhancer.enhance === 'function') {
189
+ PDS.enhancer.enhance(container);
190
+ }
191
+ };
192
+
193
+ // Initial adoption - run multiple times to catch lazy components
194
+ setTimeout(adoptAllShadowStyles, 0);
195
+ setTimeout(adoptAllShadowStyles, 100);
196
+ setTimeout(adoptAllShadowStyles, 300);
197
+
198
+ // Re-adopt on any DOM changes (for re-renders) with debouncing
199
+ let adoptTimeout;
200
+ setTimeout(() => {
201
+ const container = document.querySelector('#storybook-root');
202
+ if (container && !container._pdsObserver) {
203
+ const debouncedAdopt = () => {
204
+ clearTimeout(adoptTimeout);
205
+ adoptTimeout = setTimeout(() => {
206
+ console.log('🔄 DOM changed - re-adopting primitives');
207
+ adoptAllShadowStyles();
208
+ }, 100); // Increased debounce to 100ms
209
+ };
210
+
211
+ const observer = new MutationObserver(debouncedAdopt);
212
+ observer.observe(container, {
213
+ childList: true,
214
+ subtree: true,
215
+ attributes: true,
216
+ characterData: true // Also watch text changes
217
+ });
218
+ container._pdsObserver = observer;
219
+
220
+ // Also re-adopt periodically as fallback (every 1 second)
221
+ setInterval(() => {
222
+ if (document.contains(container)) {
223
+ adoptAllShadowStyles();
224
+ }
225
+ }, 1000);
226
+ }
227
+ }, 100);
228
+
229
+ return storyResult;
230
+ };
231
+
232
+ // Add a decorator that has access to context.globals to handle preset/theme changes
233
+ const withGlobalsHandler = (story, context) => {
234
+ const { globals } = context;
235
+
236
+ // Handle preset changes via decorator (has access to globals)
237
+ if (globals?.preset && globals.preset !== window.__pdsCurrentPreset) {
238
+ console.log('🔄 Decorator detected preset change:', window.__pdsCurrentPreset, '→', globals.preset);
239
+
240
+ // Apply preset asynchronously
241
+ (async () => {
242
+ try {
243
+ window.__pdsCurrentPreset = globals.preset;
244
+
245
+ // Store for persistence
246
+ try {
247
+ sessionStorage.setItem('storybook-pds-preset', globals.preset);
248
+ localStorage.setItem('storybook-pds-preset', globals.preset);
249
+ } catch (e) {}
250
+
251
+ // Load and apply preset
252
+ const { presets } = await import('../../../src/js/pds-core/pds-config.js');
253
+ const presetConfig = presets[globals.preset];
254
+
255
+ if (presetConfig) {
256
+ console.log(`🎨 Applying preset via decorator: ${presetConfig.name || globals.preset}`);
257
+
258
+ const generatorOptions = {
259
+ design: structuredClone(presetConfig),
260
+ log: (...args) => console.log('🟦 [Generator]', ...args)
261
+ };
262
+
263
+ if (PDS.theme) generatorOptions.theme = PDS.theme;
264
+
265
+ const newDesigner = new PDS.Generator(generatorOptions);
266
+ await PDS.Generator.applyStyles(newDesigner);
267
+
268
+ PDS.registry._designer = newDesigner;
269
+ window.__pdsDesigner = newDesigner;
270
+
271
+ console.log(`✅ Preset applied via decorator: ${globals.preset}`);
272
+ }
273
+ } catch (err) {
274
+ console.error('❌ Failed to apply preset via decorator:', err);
275
+ }
276
+ })();
277
+ }
278
+
279
+ // Handle theme changes
280
+ if (globals?.theme && globals.theme !== document.body.getAttribute('data-theme')) {
281
+ console.log('🌙 Decorator detected theme change:', globals.theme);
282
+ document.body.setAttribute('data-theme', globals.theme);
283
+ PDS.theme = globals.theme;
284
+ }
285
+
286
+ return story();
287
+ };
288
+
289
+ const DEFAULT_STORY_TAGS = new Set(['dev', 'test', 'story', 'stories', 'autodocs', 'example', 'examples']);
290
+
291
+ const TAG_SYNONYMS = new Map([
292
+ ['padding', 'spacing'],
293
+ ['gap', 'spacing'],
294
+ ['grid', 'layout'],
295
+ ['flex', 'layout'],
296
+ ['dialogs', 'interaction'],
297
+ ['validation', 'interaction'],
298
+ ['confirmation', 'interaction'],
299
+ ['surfaces', 'surface'],
300
+ ['alert', 'alerts'],
301
+ ['badge', 'badges'],
302
+ ['pill', 'pills'],
303
+ ['color', 'colors']
304
+ ]);
305
+
306
+ const normalizeTag = (tag) => {
307
+ if (typeof tag !== 'string') return null;
308
+ const value = tag.trim().toLowerCase();
309
+ if (!value) return null;
310
+
311
+ const synonym = TAG_SYNONYMS.get(value);
312
+ const normalized = synonym || value;
313
+
314
+ if (DEFAULT_STORY_TAGS.has(normalized)) return null;
315
+
316
+ return normalized;
317
+ };
318
+
319
+ const SEMANTIC_TAG_RELATIONS = new Map([
320
+ // ['interaction', ['dialogs', 'buttons', 'forms']],
321
+ // ['buttons', ['interaction', 'controls']],
322
+ // ['forms', ['interaction', 'validation']],
323
+ // ['spacing', ['layout', 'gap', 'padding', 'cards', 'grid']],
324
+ // ['layout', ['spacing', 'grid', 'flex', 'cards']],
325
+ // ['grid', ['layout', 'spacing']],
326
+ // ['gap', ['spacing']],
327
+ // ['cards', ['layout', 'spacing', 'surface']],
328
+ // ['surface', ['cards', 'spacing']],
329
+ // ['alerts', ['interaction', 'colors']],
330
+ // ['badges', ['colors', 'pills']],
331
+ // ['pills', ['badges', 'colors']],
332
+ // ['colors', ['surface']],
333
+ // ['utilities', ['spacing', 'layout']],
334
+ // ['focus', ['interaction']],
335
+ // ['hover', ['interaction']],
336
+ // ['confirmation', ['dialogs', 'interaction']]
337
+ ]);
338
+
339
+ const expandSemanticTags = (input) => {
340
+ if (!input || input.size === 0) return input;
341
+ const expanded = new Set(input);
342
+ const queue = Array.from(input);
343
+
344
+ while (queue.length > 0) {
345
+ const tag = queue.pop();
346
+ const related = SEMANTIC_TAG_RELATIONS.get(tag);
347
+ if (!related) continue;
348
+ related.forEach((value) => {
349
+ const normalized = normalizeTag(value);
350
+ if (!normalized) return;
351
+ if (!expanded.has(normalized)) {
352
+ expanded.add(normalized);
353
+ queue.push(normalized);
354
+ }
355
+ });
356
+ }
357
+
358
+ return expanded;
359
+ };
360
+
361
+ const getStoryStore = () => {
362
+ if (typeof window === 'undefined') return null;
363
+ return window.__STORYBOOK_STORY_STORE__ || null;
364
+ };
365
+
366
+ const collectStoryIndexTags = (storyId) => {
367
+ if (!storyId) return undefined;
368
+ const storyStore = getStoryStore();
369
+ const index = storyStore?.storyIndex;
370
+ if (!index) return undefined;
371
+
372
+ const entries = index.entries || index;
373
+ const entry = entries?.[storyId];
374
+ return entry?.tags;
375
+ };
376
+
377
+ const collectStoryStoreEntryTags = (storyId) => {
378
+ const storyStore = getStoryStore();
379
+ if (!storyStore?.fromId || !storyId) return undefined;
380
+
381
+ try {
382
+ const entry = storyStore.fromId(storyId);
383
+ if (!entry) return undefined;
384
+
385
+ return mergeTagSets(
386
+ entry.meta?.tags,
387
+ entry.parameters?.tags,
388
+ entry.moduleExport?.default?.tags,
389
+ entry.moduleExport?.tags
390
+ );
391
+ } catch {
392
+ return undefined;
393
+ }
394
+ };
395
+
396
+ const ensureRelatedStyles = (() => {
397
+ let injected = false;
398
+ return () => {
399
+ if (injected || typeof document === 'undefined') return;
400
+
401
+ const style = document.createElement('style');
402
+ style.id = 'pds-related-footer-styles';
403
+ style.textContent = `
404
+ .pds-related-footer {
405
+ margin-top: var(--spacing-8);
406
+ padding-top: var(--spacing-5);
407
+ border-top: 1px solid var(--color-border);
408
+ }
409
+
410
+ .pds-related-footer h2 {
411
+ margin: 0 0 var(--spacing-3);
412
+ font-size: var(--font-size-sm);
413
+ text-transform: uppercase;
414
+ letter-spacing: var(--letter-spacing-wide, 0.08em);
415
+ color: var(--color-text-muted);
416
+ }
417
+
418
+ #pds-related-overlay {
419
+ position: fixed;
420
+ inset-block-end: var(--spacing-4);
421
+ inset-inline-end: 0;
422
+ z-index: var(--z-popover, 2147483647);
423
+ display: flex;
424
+ align-items: flex-end;
425
+ justify-content: flex-end;
426
+ gap: 0;
427
+ pointer-events: none;
428
+ transition: inset-inline-end var(--transition-fast), gap var(--transition-fast);
429
+ }
430
+
431
+ #pds-related-overlay.is-expanded {
432
+ inset-inline-end: var(--spacing-4);
433
+ gap: var(--spacing-3);
434
+ }
435
+
436
+ #pds-related-overlay > * {
437
+ pointer-events: auto;
438
+ }
439
+
440
+ #pds-related-overlay.is-expanded .pds-related-toggle {
441
+ background: var(--color-primary-100);
442
+ color: var(--color-primary-700);
443
+ border-color: var(--color-primary-500);
444
+ }
445
+
446
+ #pds-related-overlay.is-collapsed .pds-related-toggle {
447
+ border-top-left-radius: 0;
448
+ border-bottom-left-radius: 0;
449
+ }
450
+
451
+ .pds-related-toggle {
452
+ writing-mode: vertical-rl;
453
+ transform: rotate(180deg);
454
+ background: var(--color-surface-overlay);
455
+ color: var(--color-primary-text, var(--color-primary-600));
456
+ border: 1px solid var(--color-border);
457
+ border-radius: var(--radius-lg);
458
+ padding: var(--spacing-3) var(--spacing-2);
459
+ font-size: var(--font-size-xs);
460
+ font-weight: var(--font-weight-semibold);
461
+ letter-spacing: var(--letter-spacing-wide, 0.12em);
462
+ text-transform: uppercase;
463
+ cursor: pointer;
464
+ box-shadow: var(--shadow-sm);
465
+ transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
466
+ }
467
+
468
+ .pds-related-toggle:hover,
469
+ .pds-related-toggle:focus-visible {
470
+ background: var(--color-primary-100);
471
+ color: var(--color-primary-700);
472
+ border-color: var(--color-primary-500);
473
+ outline: none;
474
+ }
475
+
476
+ .pds-related-panel {
477
+ max-width: min(360px, 90vw);
478
+ width: clamp(260px, 32vw, 360px);
479
+ min-width: 0;
480
+ background: var(--color-surface-overlay);
481
+ border: 1px solid var(--color-border);
482
+ border-radius: var(--radius-lg);
483
+ padding: var(--spacing-5);
484
+ display: grid;
485
+ gap: var(--spacing-4);
486
+ box-shadow: var(--shadow-lg);
487
+ font-family: var(--font-family-body, var(--font-family-base, system-ui));
488
+ color: var(--color-text-primary);
489
+ backdrop-filter: var(--backdrop-filter, blur(12px));
490
+ overflow: hidden;
491
+ opacity: 1;
492
+ transform: translateY(0);
493
+ transition: opacity var(--transition-fast), transform var(--transition-fast), max-width var(--transition-fast), width var(--transition-fast), padding var(--transition-fast), border-width var(--transition-fast);
494
+ }
495
+
496
+ .pds-related-panel > * {
497
+ transition: opacity var(--transition-fast);
498
+ }
499
+
500
+ #pds-related-overlay.is-collapsed .pds-related-panel {
501
+ opacity: 0;
502
+ transform: translateY(8px);
503
+ max-width: 0;
504
+ width: 0;
505
+ padding: 0;
506
+ border-width: 0;
507
+ pointer-events: none;
508
+ box-shadow: none;
509
+ }
510
+
511
+ #pds-related-overlay.is-collapsed .pds-related-panel > * {
512
+ opacity: 0;
513
+ }
514
+
515
+ #pds-related-overlay.is-expanded .pds-related-panel {
516
+ opacity: 1;
517
+ transform: translateY(0);
518
+ }
519
+
520
+ #pds-related-overlay.is-expanded .pds-related-panel > * {
521
+ opacity: 1;
522
+ }
523
+
524
+ .pds-related-panel-header {
525
+ display: flex;
526
+ align-items: center;
527
+ justify-content: space-between;
528
+ gap: var(--spacing-3);
529
+ }
530
+
531
+ .pds-related-panel h2 {
532
+ margin: 0;
533
+ font-size: var(--font-size-xs);
534
+ text-transform: uppercase;
535
+ letter-spacing: var(--letter-spacing-wide, 0.12em);
536
+ color: var(--color-text-muted);
537
+ }
538
+
539
+ .pds-related-close {
540
+ display: inline-flex;
541
+ align-items: center;
542
+ justify-content: center;
543
+ width: var(--spacing-8);
544
+ height: var(--spacing-8);
545
+ border-radius: var(--radius-full);
546
+ border: 1px solid transparent;
547
+ background: transparent;
548
+ color: var(--color-text-muted);
549
+ cursor: pointer;
550
+ transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
551
+ }
552
+
553
+ .pds-related-close:hover,
554
+ .pds-related-close:focus-visible {
555
+ background: color-mix(in oklab, var(--color-primary-500) 15%, transparent);
556
+ color: var(--color-primary-600);
557
+ border-color: var(--color-primary-400);
558
+ outline: none;
559
+ }
560
+
561
+ .pds-related-close pds-icon {
562
+ width: var(--icon-size-sm, 20px);
563
+ height: var(--icon-size-sm, 20px);
564
+ }
565
+
566
+ .pds-related-accordion {
567
+ display: grid;
568
+ gap: var(--spacing-3);
569
+ }
570
+
571
+ .pds-related-accordion details {
572
+ border-radius: var(--radius-md);
573
+ overflow: hidden;
574
+ border: 1px solid var(--color-border);
575
+ background: var(--color-surface-base);
576
+ }
577
+
578
+ .pds-related-panel .pds-related-accordion details {
579
+ background: var(--color-surface-overlay);
580
+ }
581
+
582
+ .pds-related-footer .pds-related-accordion details {
583
+ background: var(--color-surface-base);
584
+ }
585
+
586
+ .pds-related-accordion summary {
587
+ display: flex;
588
+ align-items: center;
589
+ gap: var(--spacing-2);
590
+ padding: var(--spacing-3) var(--spacing-4);
591
+ font-weight: var(--font-weight-semibold);
592
+ font-size: var(--font-size-sm);
593
+ cursor: pointer;
594
+ list-style: none;
595
+ }
596
+
597
+ .pds-related-accordion summary::-webkit-details-marker {
598
+ display: none;
599
+ }
600
+
601
+ .pds-related-accordion summary::after {
602
+ content: '';
603
+ border: solid currentColor;
604
+ border-width: 0 2px 2px 0;
605
+ display: inline-block;
606
+ padding: 4px;
607
+ transform: rotate(-45deg);
608
+ margin-inline-start: auto;
609
+ transition: transform var(--transition-fast);
610
+ }
611
+
612
+ .pds-related-accordion details[open] summary::after {
613
+ transform: rotate(135deg);
614
+ }
615
+
616
+ .pds-related-tag-label {
617
+ text-transform: uppercase;
618
+ letter-spacing: var(--letter-spacing-wide, 0.08em);
619
+ font-size: var(--font-size-xs);
620
+ color: inherit;
621
+ }
622
+
623
+ .pds-related-count {
624
+ background: var(--color-primary-100);
625
+ color: var(--color-primary-700);
626
+ border-radius: var(--radius-full, 999px);
627
+ font-size: var(--font-size-xs);
628
+ font-weight: var(--font-weight-semibold);
629
+ line-height: 1;
630
+ padding: var(--spacing-1) var(--spacing-2);
631
+ }
632
+
633
+ .pds-related-list {
634
+ list-style: none;
635
+ display: grid;
636
+ gap: var(--spacing-2);
637
+ padding: 0 var(--spacing-4) var(--spacing-3);
638
+ margin: 0;
639
+ }
640
+
641
+ .pds-related-list-item {
642
+ display: flex;
643
+ flex-direction: row;
644
+ gap: var(--spacing-3);
645
+ align-items: baseline;
646
+ justify-content: space-between;
647
+ }
648
+
649
+ .pds-related-list a {
650
+ font-weight: var(--font-weight-semibold);
651
+ color: var(--color-primary-text, var(--color-primary-600));
652
+ text-decoration: none;
653
+ }
654
+
655
+ .pds-related-list a:hover {
656
+ text-decoration: underline;
657
+ }
658
+
659
+ .pds-related-list a[aria-current="page"] {
660
+ color: var(--color-text-muted);
661
+ cursor: default;
662
+ text-decoration: none;
663
+ }
664
+
665
+ .pds-related-tags {
666
+ font-size: var(--font-size-xs);
667
+ color: var(--color-text-muted);
668
+ }
669
+
670
+ .pds-related-footer .pds-related-tags {
671
+ color: var(--color-text-muted);
672
+ }
673
+
674
+ @media (max-width: 600px) {
675
+ #pds-related-overlay {
676
+ inset-block-end: var(--spacing-3);
677
+ }
678
+
679
+ #pds-related-overlay.is-expanded {
680
+ inset-inline-end: var(--spacing-3);
681
+ }
682
+
683
+ .pds-related-toggle {
684
+ padding: var(--spacing-2) var(--spacing-1);
685
+ }
686
+
687
+ .pds-related-panel {
688
+ width: min(90vw, 320px);
689
+ max-width: min(90vw, 320px);
690
+ padding: var(--spacing-4);
691
+ }
692
+ }
693
+ `;
694
+
695
+ document.head.appendChild(style);
696
+ injected = true;
697
+ };
698
+ })();
699
+
700
+ const getStorySlug = (storyId) => {
701
+ if (typeof storyId !== 'string') return undefined;
702
+ const [slug] = storyId.split('--');
703
+ return slug;
704
+ };
705
+
706
+ const selectStoryById = (storyId, viewMode = 'story') => {
707
+ if (!storyId) return false;
708
+
709
+ try {
710
+ const channel = addons?.getChannel?.();
711
+ if (channel) {
712
+ channel.emit(SELECT_STORY, { storyId, viewMode });
713
+ return true;
714
+ }
715
+ } catch (err) {
716
+ console.warn('PDS related overlay: channel navigation failed', err);
717
+ }
718
+
719
+ try {
720
+ window.parent?.postMessage(
721
+ {
722
+ source: 'pds-related-overlay',
723
+ type: 'pds-related:navigate',
724
+ storyId,
725
+ viewMode
726
+ },
727
+ '*'
728
+ );
729
+ } catch {}
730
+
731
+ const slug = getStorySlug(storyId);
732
+ const targetSearch = viewMode === 'docs' && slug
733
+ ? `?path=/docs/${slug}--docs`
734
+ : `?path=/story/${storyId}`;
735
+
736
+ try {
737
+ if (window.parent && window.parent !== window) {
738
+ window.parent.location.search = targetSearch;
739
+ } else {
740
+ window.location.search = targetSearch;
741
+ }
742
+ return true;
743
+ } catch (err) {
744
+ console.warn('PDS related overlay: fallback navigation failed', err);
745
+ }
746
+
747
+ return false;
748
+ };
749
+
750
+ const defaultRelatedLinkConfigurator = (link, item, preferredViewMode = 'docs') => {
751
+ if (!link || !item) return;
752
+
753
+ if (preferredViewMode === 'story' && item.storyId) {
754
+ link.href = `?path=/story/${item.storyId}`;
755
+ return;
756
+ }
757
+
758
+ if (preferredViewMode === 'docs' && item.slug) {
759
+ link.href = `?path=/docs/${item.slug}--docs`;
760
+ return;
761
+ }
762
+
763
+ if (item.storyId) {
764
+ link.href = `?path=/story/${item.storyId}`;
765
+ return;
766
+ }
767
+
768
+ if (item.slug) {
769
+ link.href = `?path=/docs/${item.slug}--docs`;
770
+ return;
771
+ }
772
+
773
+ link.href = '#';
774
+ };
775
+
776
+ const groupRelatedByTag = (related, currentTags) => {
777
+ const grouped = new Map();
778
+
779
+ related.forEach((item) => {
780
+ item.overlap.forEach((tag) => {
781
+ if (!grouped.has(tag)) grouped.set(tag, []);
782
+ grouped.get(tag).push(item);
783
+ });
784
+ });
785
+
786
+ const currentTagArray = Array.isArray(currentTags)
787
+ ? currentTags
788
+ : Array.from(currentTags || []);
789
+
790
+ const priority = new Map();
791
+ currentTagArray.forEach((tag, index) => {
792
+ if (!priority.has(tag)) priority.set(tag, index);
793
+ });
794
+
795
+ const orderedTags = Array.from(grouped.keys()).sort((a, b) => {
796
+ const priorityA = priority.has(a) ? priority.get(a) : Number.MAX_SAFE_INTEGER;
797
+ const priorityB = priority.has(b) ? priority.get(b) : Number.MAX_SAFE_INTEGER;
798
+
799
+ if (priorityA !== priorityB) return priorityA - priorityB;
800
+
801
+ const countDiff = grouped.get(b).length - grouped.get(a).length;
802
+ if (countDiff !== 0) return countDiff;
803
+
804
+ return a.localeCompare(b);
805
+ });
806
+
807
+ return { grouped, orderedTags };
808
+ };
809
+
810
+ const buildRelatedAccordion = (related, currentTags, options = {}) => {
811
+ if (!related?.length) return null;
812
+
813
+ const { grouped, orderedTags } = groupRelatedByTag(related, currentTags);
814
+ if (!orderedTags.length) return null;
815
+
816
+ const {
817
+ variant = 'overlay',
818
+ configureLink,
819
+ openFirst = false
820
+ } = options;
821
+
822
+ const accordion = document.createElement('section');
823
+ accordion.className = 'accordion pds-related-accordion';
824
+ accordion.classList.add(
825
+ variant === 'overlay' ? 'pds-related-accordion--overlay' : 'pds-related-accordion--docs'
826
+ );
827
+
828
+ orderedTags.forEach((tag, index) => {
829
+ const stories = grouped.get(tag);
830
+ if (!stories?.length) return;
831
+
832
+ const details = document.createElement('details');
833
+ if (openFirst && index === 0) {
834
+ details.open = true;
835
+ }
836
+
837
+ const summary = document.createElement('summary');
838
+ const tagLabel = document.createElement('span');
839
+ tagLabel.className = 'pds-related-tag-label';
840
+ tagLabel.textContent = tag;
841
+ summary.appendChild(tagLabel);
842
+
843
+ const count = document.createElement('span');
844
+ count.className = 'pds-related-count';
845
+ count.textContent = String(stories.length);
846
+ summary.appendChild(count);
847
+
848
+ details.appendChild(summary);
849
+
850
+ const list = document.createElement('ul');
851
+ list.className = 'pds-related-list';
852
+ const seen = new Set();
853
+
854
+ stories.forEach((item) => {
855
+ const key = item.storyId || `${item.slug || ''}::${item.title}`;
856
+ if (!key || seen.has(key)) return;
857
+ seen.add(key);
858
+
859
+ const listItem = document.createElement('li');
860
+ listItem.className = 'pds-related-list-item';
861
+
862
+ const link = document.createElement('a');
863
+ link.textContent = item.title;
864
+
865
+ if (typeof configureLink === 'function') {
866
+ configureLink(link, item, { tag, variant });
867
+ } else {
868
+ defaultRelatedLinkConfigurator(link, item, variant === 'overlay' ? 'story' : 'docs');
869
+ }
870
+
871
+ const otherTags = item.overlap.filter((value) => value !== tag);
872
+ const tags = document.createElement('span');
873
+ tags.className = 'pds-related-tags';
874
+ tags.textContent = otherTags.length ? otherTags.join(' ') : tag;
875
+
876
+ listItem.appendChild(link);
877
+ listItem.appendChild(tags);
878
+ list.appendChild(listItem);
879
+ });
880
+
881
+ if (!list.children.length) return;
882
+
883
+ details.appendChild(list);
884
+ accordion.appendChild(details);
885
+ });
886
+
887
+ if (!accordion.children.length) return null;
888
+
889
+ return accordion;
890
+ };
891
+
892
+ const createOverlayLinkConfigurator = (context) => (link, item) => {
893
+ defaultRelatedLinkConfigurator(link, item, 'story');
894
+
895
+ const currentId = context?.id || context?.storyId;
896
+ if (item.storyId && currentId && item.storyId === currentId) {
897
+ link.setAttribute('aria-current', 'page');
898
+ }
899
+
900
+ if (!item.storyId) return;
901
+
902
+ link.addEventListener('click', (event) => {
903
+ event.preventDefault();
904
+ event.stopPropagation();
905
+
906
+ const navigated = selectStoryById(item.storyId, 'story');
907
+ if (!navigated) {
908
+ const target = `?path=/story/${item.storyId}`;
909
+ try {
910
+ if (window.parent && window.parent !== window) {
911
+ window.parent.location.search = target;
912
+ } else {
913
+ window.location.search = target;
914
+ }
915
+ } catch {
916
+ window.location.search = target;
917
+ }
918
+ }
919
+ });
920
+ };
921
+
922
+ const createDocsLinkConfigurator = (context) => (link, item) => {
923
+ defaultRelatedLinkConfigurator(link, item, 'docs');
924
+
925
+ const currentSlug = getStorySlug(context?.id || context?.storyId);
926
+ if (item.slug && currentSlug && item.slug === currentSlug) {
927
+ link.setAttribute('aria-current', 'page');
928
+ }
929
+ };
930
+
931
+ const mergeTagSets = (...sets) => {
932
+ const merged = new Set();
933
+ sets.forEach((set) => {
934
+ if (!set) return;
935
+
936
+ if (Array.isArray(set)) {
937
+ set.forEach((tag) => {
938
+ const normalized = normalizeTag(tag);
939
+ if (normalized) merged.add(normalized);
940
+ });
941
+ return;
942
+ }
943
+
944
+ if (set instanceof Set) {
945
+ set.forEach((tag) => {
946
+ const normalized = normalizeTag(tag);
947
+ if (normalized) merged.add(normalized);
948
+ });
949
+ }
950
+ });
951
+ return merged;
952
+ };
953
+
954
+ const getContextTags = (context) => {
955
+ const storyId = context.id || context.storyId;
956
+ const initial = mergeTagSets(
957
+ context.parameters?.tags,
958
+ context.parameters?.pds?.tags,
959
+ context.component?.parameters?.tags,
960
+ context.component?.tags,
961
+ context.tags,
962
+ context.moduleExport?.default?.tags,
963
+ context.moduleExport?.default?.parameters?.pds?.tags,
964
+ context.moduleExport?.parameters?.pds?.tags,
965
+ context.moduleExport?.pds?.tags,
966
+ context.parameters?.pdsTags,
967
+ context.moduleExport?.tags,
968
+ collectStoryIndexTags(storyId),
969
+ collectStoryStoreEntryTags(storyId)
970
+ );
971
+
972
+ if (initial.size > 0) {
973
+ return expandSemanticTags(initial);
974
+ }
975
+
976
+ const fallback = new Set();
977
+ const clientApi = typeof window !== 'undefined' ? window.__STORYBOOK_CLIENT_API__ : null;
978
+ if (!clientApi?.raw) return fallback;
979
+
980
+ clientApi
981
+ .raw()
982
+ .filter((story) => story?.title === context.title)
983
+ .forEach((story) => {
984
+ getStoryTags(story).forEach((tag) => fallback.add(tag));
985
+ });
986
+
987
+ return expandSemanticTags(fallback);
988
+ };
989
+
990
+ const getStoryTags = (story) => {
991
+ const collected = mergeTagSets(
992
+ story.parameters?.tags,
993
+ story.parameters?.pds?.tags,
994
+ story.meta?.tags,
995
+ story.tags,
996
+ story.meta?.parameters?.pds?.tags,
997
+ story.moduleExport?.default?.parameters?.pds?.tags,
998
+ story.moduleExport?.parameters?.pds?.tags,
999
+ story.moduleExport?.pds?.tags,
1000
+ story.parameters?.pdsTags,
1001
+ story.moduleExport?.default?.tags,
1002
+ story.moduleExport?.tags,
1003
+ collectStoryIndexTags(story.id),
1004
+ collectStoryStoreEntryTags(story.id)
1005
+ );
1006
+
1007
+ if (collected.size > 0) {
1008
+ return expandSemanticTags(collected);
1009
+ }
1010
+
1011
+ const storyStore = getStoryStore();
1012
+ const storyIndex = storyStore?.storyIndex;
1013
+ if (storyIndex?.entries) {
1014
+ const entry = storyIndex.entries[story.id];
1015
+ if (entry?.tags) {
1016
+ return expandSemanticTags(mergeTagSets(entry.tags));
1017
+ }
1018
+ }
1019
+
1020
+ return expandSemanticTags(collected);
1021
+ };
1022
+
1023
+ const getAllStoriesForRelated = () => {
1024
+ const stories = [];
1025
+
1026
+ const clientApi = typeof window !== 'undefined' ? window.__STORYBOOK_CLIENT_API__ : null;
1027
+ if (clientApi?.raw) {
1028
+ stories.push(...clientApi.raw());
1029
+ return stories;
1030
+ }
1031
+
1032
+ const storyStore = getStoryStore();
1033
+ const indexEntries = storyStore?.storyIndex?.entries;
1034
+ if (!indexEntries) return stories;
1035
+
1036
+ Object.entries(indexEntries).forEach(([storyId, entry]) => {
1037
+ if (!entry || entry.type !== 'story') return;
1038
+
1039
+ let storeEntry;
1040
+ if (storyStore?.fromId) {
1041
+ try {
1042
+ storeEntry = storyStore.fromId(storyId);
1043
+ } catch {}
1044
+ }
1045
+
1046
+ const parameters = storeEntry?.parameters || storeEntry?.story?.parameters || entry.parameters || {};
1047
+ const meta = storeEntry?.meta || { title: entry.title };
1048
+ const moduleExport = storeEntry?.moduleExport;
1049
+ const tags = mergeTagSets(entry.tags, storeEntry?.tags);
1050
+
1051
+ stories.push({
1052
+ id: storyId,
1053
+ title: storeEntry?.story?.title || entry.title,
1054
+ name: entry.name,
1055
+ importPath: entry.importPath,
1056
+ parameters,
1057
+ meta,
1058
+ moduleExport,
1059
+ tags
1060
+ });
1061
+ });
1062
+
1063
+ return stories;
1064
+ };
1065
+
1066
+ const computeRelatedStories = (context) => {
1067
+ if (typeof window === 'undefined') return [];
1068
+
1069
+ const currentTags = getContextTags(context);
1070
+ if (currentTags.size === 0) return [];
1071
+
1072
+ const byTitle = new Map();
1073
+
1074
+ getAllStoriesForRelated().forEach((story) => {
1075
+ const title = story?.title;
1076
+ if (!title) return;
1077
+
1078
+ const tags = getStoryTags(story);
1079
+ if (tags.size === 0) return;
1080
+
1081
+ const storyId = story.id;
1082
+ if (!storyId) return;
1083
+ const slug = storyId.split('--')[0];
1084
+
1085
+ const existing = byTitle.get(title);
1086
+ if (existing) {
1087
+ tags.forEach((tag) => existing.tags.add(tag));
1088
+ if (!existing.slug) existing.slug = slug;
1089
+ if (!existing.storyId) existing.storyId = storyId;
1090
+ return;
1091
+ }
1092
+
1093
+ byTitle.set(title, {
1094
+ title,
1095
+ tags,
1096
+ slug,
1097
+ storyId
1098
+ });
1099
+ });
1100
+
1101
+ byTitle.delete(context.title);
1102
+
1103
+ const related = [];
1104
+
1105
+ byTitle.forEach((value) => {
1106
+ const overlap = Array.from(value.tags).filter((tag) => currentTags.has(tag));
1107
+ if (overlap.length === 0) return;
1108
+
1109
+ related.push({
1110
+ title: value.title,
1111
+ slug: value.slug,
1112
+ storyId: value.storyId,
1113
+ overlap,
1114
+ score: overlap.length
1115
+ });
1116
+ });
1117
+
1118
+ related.sort((a, b) => {
1119
+ if (b.score !== a.score) return b.score - a.score;
1120
+ return a.title.localeCompare(b.title);
1121
+ });
1122
+
1123
+ return related.slice(0, 6);
1124
+ };
1125
+
1126
+ const renderRelatedFooter = (context) => {
1127
+ if (typeof document === 'undefined') return;
1128
+
1129
+ const docsRoot = document.getElementById('docs-root');
1130
+ if (!docsRoot) return;
1131
+
1132
+ const content = docsRoot.querySelector('.sbdocs-content');
1133
+ if (!content) return;
1134
+
1135
+ const existing = content.querySelector('.pds-related-footer');
1136
+ if (existing) existing.remove();
1137
+
1138
+ const related = computeRelatedStories(context);
1139
+ if (!related.length) return;
1140
+
1141
+ const currentTags = getContextTags(context);
1142
+ const accordion = buildRelatedAccordion(related, currentTags, {
1143
+ variant: 'docs',
1144
+ configureLink: createDocsLinkConfigurator(context)
1145
+ });
1146
+
1147
+ if (!accordion) return;
1148
+
1149
+ ensureRelatedStyles();
1150
+
1151
+ const section = document.createElement('section');
1152
+ section.className = 'pds-related-footer sbdocs sbdocs-section';
1153
+
1154
+ const heading = document.createElement('h2');
1155
+ heading.textContent = 'Related';
1156
+ section.appendChild(heading);
1157
+
1158
+ section.appendChild(accordion);
1159
+ content.appendChild(section);
1160
+ };
1161
+
1162
+ const removeRelatedOverlay = () => {
1163
+ if (typeof document === 'undefined') return;
1164
+ const existing = document.getElementById('pds-related-overlay');
1165
+ if (existing) existing.remove();
1166
+ };
1167
+
1168
+ const renderRelatedOverlay = (context) => {
1169
+ if (typeof document === 'undefined') return;
1170
+
1171
+ const related = computeRelatedStories(context);
1172
+ if (!related.length) {
1173
+ removeRelatedOverlay();
1174
+ return;
1175
+ }
1176
+
1177
+ const currentTags = getContextTags(context);
1178
+ const accordion = buildRelatedAccordion(related, currentTags, {
1179
+ variant: 'overlay',
1180
+ configureLink: createOverlayLinkConfigurator(context)
1181
+ });
1182
+
1183
+ if (!accordion) {
1184
+ removeRelatedOverlay();
1185
+ return;
1186
+ }
1187
+
1188
+ ensureRelatedStyles();
1189
+
1190
+ let overlay = document.getElementById('pds-related-overlay');
1191
+ const initialExpanded = overlay ? overlay.classList.contains('is-expanded') : false;
1192
+
1193
+ if (!overlay) {
1194
+ overlay = document.createElement('aside');
1195
+ overlay.id = 'pds-related-overlay';
1196
+ document.body.appendChild(overlay);
1197
+ }
1198
+
1199
+ overlay.className = 'pds-related-overlay';
1200
+
1201
+ let toggle = overlay.querySelector('.pds-related-toggle');
1202
+ if (!toggle) {
1203
+ toggle = document.createElement('button');
1204
+ toggle.type = 'button';
1205
+ toggle.className = 'pds-related-toggle';
1206
+ toggle.setAttribute('aria-label', 'Show related stories');
1207
+ toggle.setAttribute('aria-expanded', 'false');
1208
+ toggle.setAttribute('aria-controls', 'pds-related-panel');
1209
+ toggle.textContent = 'Related';
1210
+ overlay.appendChild(toggle);
1211
+ }
1212
+
1213
+ let panel = overlay.querySelector('.pds-related-panel');
1214
+ if (!panel) {
1215
+ panel = document.createElement('div');
1216
+ panel.className = 'pds-related-panel';
1217
+ panel.id = 'pds-related-panel';
1218
+
1219
+ const header = document.createElement('div');
1220
+ header.className = 'pds-related-panel-header';
1221
+
1222
+ const heading = document.createElement('h2');
1223
+ heading.textContent = 'Related';
1224
+ header.appendChild(heading);
1225
+
1226
+ const closeButton = document.createElement('button');
1227
+ closeButton.type = 'button';
1228
+ closeButton.className = 'pds-related-close icon-only';
1229
+ closeButton.setAttribute('aria-label', 'Close related stories');
1230
+ closeButton.setAttribute('title', 'Close related stories');
1231
+
1232
+ const closeIcon = document.createElement('pds-icon');
1233
+ closeIcon.setAttribute('name', 'x');
1234
+ closeIcon.setAttribute('size', 'sm');
1235
+ closeButton.appendChild(closeIcon);
1236
+
1237
+ header.appendChild(closeButton);
1238
+
1239
+ const body = document.createElement('div');
1240
+ body.className = 'pds-related-panel-body';
1241
+
1242
+ panel.appendChild(header);
1243
+ panel.appendChild(body);
1244
+ overlay.appendChild(panel);
1245
+ }
1246
+
1247
+ const body = panel.querySelector('.pds-related-panel-body');
1248
+ if (!body) return;
1249
+
1250
+ body.textContent = '';
1251
+ body.appendChild(accordion);
1252
+
1253
+ const closeButton = panel.querySelector('.pds-related-close');
1254
+
1255
+ const setExpanded = (expanded) => {
1256
+ overlay.classList.toggle('is-expanded', expanded);
1257
+ overlay.classList.toggle('is-collapsed', !expanded);
1258
+ toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
1259
+ toggle.setAttribute('aria-label', expanded ? 'Hide related stories' : 'Show related stories');
1260
+ toggle.setAttribute('title', expanded ? 'Hide related stories' : 'Show related stories');
1261
+ panel.setAttribute('aria-hidden', expanded ? 'false' : 'true');
1262
+ };
1263
+
1264
+ toggle.onclick = () => {
1265
+ const next = !overlay.classList.contains('is-expanded');
1266
+ setExpanded(next);
1267
+ };
1268
+
1269
+ if (closeButton) {
1270
+ closeButton.onclick = (event) => {
1271
+ event.preventDefault();
1272
+ setExpanded(false);
1273
+ };
1274
+ }
1275
+
1276
+ setExpanded(initialExpanded);
1277
+ };
1278
+
1279
+ const pruneDocsStories = (() => {
1280
+ let observer;
1281
+
1282
+ const run = (root) => {
1283
+ if (!root) return;
1284
+
1285
+ const selectors = [
1286
+ '.docs-story',
1287
+ '.sbdocs-preview',
1288
+ '.sb-unstyled',
1289
+ '.docblock-canvas-wrapper',
1290
+ '.docblock-canvas',
1291
+ '.docblock-argstable-wrapper',
1292
+ '.docblock-argstable',
1293
+ '.docblock-source-wrapper',
1294
+ '.docblock-source',
1295
+ '.sbdocs-preview-wrapper',
1296
+ '.sb-story',
1297
+ '[id*="story--"]',
1298
+ '[class*="story-wrapper"]'
1299
+ ];
1300
+
1301
+ root.querySelectorAll(selectors.join(',')).forEach((node) => {
1302
+ node.remove();
1303
+ });
1304
+
1305
+ root.querySelectorAll('h2, h3').forEach((heading) => {
1306
+ if (!heading.textContent) return;
1307
+ const text = heading.textContent.trim().toLowerCase();
1308
+ if (text === 'stories' || text === 'story' || text === 'primary') {
1309
+ const section = heading.closest('section, div') || heading;
1310
+ section.remove();
1311
+ }
1312
+ });
1313
+ };
1314
+
1315
+ return () => {
1316
+ const docsRoot = document.getElementById('docs-root');
1317
+ if (!docsRoot) return;
1318
+
1319
+ run(docsRoot);
1320
+
1321
+ if (!observer) {
1322
+ observer = new MutationObserver(() => {
1323
+ run(docsRoot);
1324
+ });
1325
+ observer.observe(docsRoot, { childList: true, subtree: true });
1326
+ }
1327
+ };
1328
+ })();
1329
+
1330
+ const withRelatedStories = (story, context) => {
1331
+ const result = story();
1332
+ requestAnimationFrame(() => {
1333
+ requestAnimationFrame(() => {
1334
+ if (context.viewMode === 'docs') {
1335
+ pruneDocsStories();
1336
+ renderRelatedFooter(context);
1337
+ removeRelatedOverlay();
1338
+ return;
1339
+ }
1340
+
1341
+ if (context.viewMode === 'story') {
1342
+ renderRelatedOverlay(context);
1343
+ return;
1344
+ }
1345
+
1346
+ removeRelatedOverlay();
1347
+ });
1348
+ });
1349
+
1350
+ return result;
1351
+ };
1352
+
1353
+ const DocsPage = () => React.createElement(
1354
+ React.Fragment,
1355
+ null,
1356
+ React.createElement(Title, null),
1357
+ React.createElement(Subtitle, null),
1358
+ React.createElement(DocsDescription, null),
1359
+ React.createElement(Controls, null)
1360
+ );
1361
+
1362
+ /** @type { import('@storybook/web-components').Preview } */
1363
+ const preview = {
1364
+ decorators: [withGlobalsHandler, withPDS, withHTMLExtractor, withDescription, withRelatedStories],
1365
+ parameters: {
1366
+ controls: {
1367
+ matchers: {
1368
+ color: /(background|color)$/i,
1369
+ date: /Date$/i
1370
+ }
1371
+ },
1372
+
1373
+ backgrounds: {
1374
+ default: 'light',
1375
+ values: [
1376
+ { name: 'light', value: '#ffffff' },
1377
+ { name: 'dark', value: '#1a1a1a' },
1378
+ { name: 'surface', value: 'var(--surface-bg)' }
1379
+ ]
1380
+ },
1381
+ docs: {
1382
+ page: DocsPage
1383
+ },
1384
+ options: {
1385
+ storySort: {
1386
+ order: [
1387
+ 'General',
1388
+ ['What is PDS', 'Getting Started'],
1389
+ 'Foundations',
1390
+ ['Colors', 'Typography', 'Icons', 'Spacing', 'Smart Surfaces'],
1391
+ 'Primitives',
1392
+ ['Buttons', 'Forms', 'Form Groups', 'Alerts', 'Badges', 'Cards', 'Tables', 'Media', 'Accordion'],
1393
+ 'Utilities',
1394
+ ['Grid System'],
1395
+ 'Patterns',
1396
+ ['Layout', 'Border Effects', 'Utilities'],
1397
+ 'Enhancements',
1398
+ ['Mesh Gradients', 'Interactive States', 'Toggles', 'Dropdowns', 'Range Sliders', 'Required Fields'],
1399
+ 'Components',
1400
+ ['Pds Jsonform', 'Pds Icon', 'Pds Drawer', 'Pds Toaster', 'Pds Tabstrip', 'Pds Splitpanel', 'Pds Scrollrow', 'Pds Richtext', 'Pds Upload'],
1401
+ '*'
1402
+ ]
1403
+ }
1404
+ }
1405
+ },
1406
+ globalTypes: {
1407
+ theme: {
1408
+ name: 'Theme',
1409
+ description: 'PDS theme',
1410
+ defaultValue: 'light',
1411
+ toolbar: {
1412
+ icon: 'circlehollow',
1413
+ items: [
1414
+ { value: 'light', title: 'Light', icon: 'sun' },
1415
+ { value: 'dark', title: 'Dark', icon: 'moon' },
1416
+ { value: 'system', title: 'System', icon: 'browser' }
1417
+ ],
1418
+ dynamicTitle: true
1419
+ }
1420
+ },
1421
+ preset: {
1422
+ name: 'Preset',
1423
+ description: 'Design preset',
1424
+ defaultValue: initialPreset, // Use the preset loaded from storage
1425
+ toolbar: {
1426
+ icon: 'paintbrush',
1427
+ items: Object.keys(presets)
1428
+ .sort((a, b) => {
1429
+ const aPreset = presets[a];
1430
+ const bPreset = presets[b];
1431
+ const aTags = aPreset.tags || [];
1432
+ const bTags = bPreset.tags || [];
1433
+
1434
+ // Check if featured
1435
+ const aFeatured = aTags.includes('featured');
1436
+ const bFeatured = bTags.includes('featured');
1437
+
1438
+ if (aFeatured && !bFeatured) return -1;
1439
+ if (!aFeatured && bFeatured) return 1;
1440
+
1441
+ // Check if has any tags
1442
+ const aHasTags = aTags.length > 0;
1443
+ const bHasTags = bTags.length > 0;
1444
+
1445
+ if (aHasTags && !bHasTags) return -1;
1446
+ if (!aHasTags && bHasTags) return 1;
1447
+
1448
+ // Alphabetical by name
1449
+ return (aPreset.name || a).localeCompare(bPreset.name || b);
1450
+ })
1451
+ .map(key => ({
1452
+ value: key,
1453
+ title: presets[key].name || key
1454
+ })),
1455
+ dynamicTitle: true
1456
+ }
1457
+ }
1458
+ }
1459
+ };
1460
+
1461
+ // Listen to theme and preset changes from toolbar
1462
+ if (typeof window !== 'undefined') {
1463
+ console.log('👂 Setting up message listener for toolbar changes...');
1464
+
1465
+ window.addEventListener('message', async (event) => {
1466
+ if(event.data?.type == null) return;
1467
+
1468
+ console.log('📨 Message received:', event.data?.type, event.data);
1469
+
1470
+ if (event.data?.type === 'SET_GLOBALS') {
1471
+ console.log('✅ SET_GLOBALS detected, globals:', event.data.globals);
1472
+ const { globals } = event.data;
1473
+
1474
+ if (globals?.theme) {
1475
+ console.log('🌙 Theme change requested:', globals.theme);
1476
+
1477
+ // Set data-theme attribute on body for PDS theme system
1478
+ document.body.setAttribute('data-theme', globals.theme);
1479
+
1480
+ // Also use PDS.theme property
1481
+ PDS.theme = globals.theme;
1482
+
1483
+ console.log('✅ Theme applied:', globals.theme);
1484
+ }
1485
+
1486
+ if (globals?.preset) {
1487
+ console.log('🔔 SET_GLOBALS message received with preset:', globals.preset);
1488
+ console.log('📦 Current stored preset:', window.__pdsCurrentPreset);
1489
+
1490
+ // Skip if already on this preset
1491
+ if (globals.preset === window.__pdsCurrentPreset) {
1492
+ console.log('⏭️ Preset unchanged, skipping');
1493
+ return;
1494
+ }
1495
+
1496
+ try {
1497
+ console.log('📦 Preset change requested:', globals.preset);
1498
+
1499
+ // Store preset selection for persistence
1500
+ try {
1501
+ sessionStorage.setItem('storybook-pds-preset', globals.preset);
1502
+ localStorage.setItem('storybook-pds-preset', globals.preset);
1503
+ console.log('💾 Preset stored in storage');
1504
+ } catch (e) {
1505
+ console.warn('⚠️ Failed to store preset:', e);
1506
+ }
1507
+
1508
+ // Load preset from PDS presets and create new designer
1509
+ console.log('📥 Importing pds-config...');
1510
+ const configModule = await import('../../../src/js/pds-core/pds-config.js');
1511
+ console.log('✅ Config module loaded:', configModule);
1512
+
1513
+ const { presets } = configModule;
1514
+ const presetId = globals.preset;
1515
+ const presetConfig = presets[presetId];
1516
+
1517
+ console.log('📋 Available presets:', Object.keys(presets));
1518
+ console.log('🔍 Looking for preset:', presetId);
1519
+ console.log('✅ Found preset config:', presetConfig ? 'yes' : 'no');
1520
+
1521
+ if (presetConfig) {
1522
+ console.log(`🎨 Applying preset: ${presetConfig.name || presetId}`);
1523
+ console.log('📝 Preset config:', presetConfig);
1524
+
1525
+ // Create new designer with preset config (same as pds-config-form does)
1526
+ const generatorOptions = {
1527
+ design: structuredClone(presetConfig),
1528
+ log: (...args) => console.log('🟦 [Generator]', ...args)
1529
+ };
1530
+ const storedTheme = PDS.theme || null;
1531
+ if (storedTheme) {
1532
+ generatorOptions.theme = storedTheme;
1533
+ console.log('🌙 Applying stored theme:', storedTheme);
1534
+ }
1535
+
1536
+ console.log('🏗️ Creating new Generator...');
1537
+ const newDesigner = new PDS.Generator(generatorOptions);
1538
+ console.log('✅ Generator created');
1539
+
1540
+ console.log('🎨 Applying styles to document...');
1541
+ await PDS.Generator.applyStyles(newDesigner);
1542
+ console.log('✅ Styles applied to document');
1543
+
1544
+ // Update BOTH registry designer AND global reference
1545
+ PDS.registry._designer = newDesigner;
1546
+ window.__pdsDesigner = newDesigner;
1547
+ window.__pdsCurrentPreset = presetId;
1548
+
1549
+ console.log(`✅✅✅ Preset applied successfully: ${presetConfig.name || presetId}`);
1550
+ } else {
1551
+ console.error(`❌ Preset not found: ${presetId}`);
1552
+ console.error('❌ Available presets:', Object.keys(presets));
1553
+ }
1554
+ } catch (err) {
1555
+ console.error('❌❌❌ Failed to apply preset:', err);
1556
+ console.error('❌ Stack trace:', err.stack);
1557
+ }
1558
+ }
1559
+ }
1560
+ });
1561
+ }
1562
+
1563
+ export default preview;