@pure-ds/storybook 0.5.39 → 0.5.41

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 (31) hide show
  1. package/.storybook/addons/html-preview/Panel.jsx +70 -7
  2. package/.storybook/addons/html-preview/preview.js +78 -3
  3. package/.storybook/main.js +3 -3
  4. package/.storybook/preview-head.html +1 -1
  5. package/dist/pds-reference.json +25 -297
  6. package/package.json +2 -2
  7. package/public/assets/js/app.js +19 -14
  8. package/public/assets/js/lit.js +3 -3
  9. package/public/assets/pds/components/pds-fab.js +4 -6
  10. package/public/assets/pds/external/lit.js +131 -0
  11. package/src/js/lit.js +54 -0
  12. package/src/js/pds-configurator/pds-demo.js +8 -0
  13. package/stories/components/PdsFab.stories.js +164 -127
  14. package/stories/components/PdsForm.stories.js +1 -1
  15. package/stories/components/PdsOmnibox.stories.js +22 -32
  16. package/stories/components/PdsScrollrow.stories.js +1 -1
  17. package/stories/components/PdsTabstrip.stories.js +1 -1
  18. package/stories/enhancements/Clip.stories.js +1 -1
  19. package/stories/enhancements/Dropdowns.stories.js +1 -1
  20. package/stories/enhancements/Toggles.stories.js +1 -1
  21. package/stories/foundations/Dividers.stories.js +1 -1
  22. package/stories/foundations/MeshGradients.stories.js +1 -1
  23. package/stories/foundations/SmartSurfaces.stories.js +1 -1
  24. package/stories/foundations/Typography.stories.js +1 -1
  25. package/stories/getting-started.md +1 -1
  26. package/stories/layout/LayoutOverview.stories.js +1 -1
  27. package/stories/layout/LayoutSystem.stories.js +1 -1
  28. package/stories/patterns/InteractiveStates.stories.js +1 -1
  29. package/stories/patterns/Utilities.stories.js +1 -1
  30. package/stories/primitives/Cards.stories.js +1 -1
  31. package/stories/primitives/HtmlFormGroups.stories.js +1 -1
@@ -109,11 +109,12 @@ const CheckIcon = () => (
109
109
  );
110
110
 
111
111
  export const Panel = ({ active }) => {
112
- const [source, setSource] = useState({ markup: '', forms: [], omniboxes: [] });
112
+ const [source, setSource] = useState({ markup: '', forms: [], omniboxes: [], fabs: [] });
113
113
  const [copied, setCopied] = useState(false);
114
114
  const [highlightedMarkup, setHighlightedMarkup] = useState('');
115
115
  const [highlightedforms, setHighlightedforms] = useState([]);
116
116
  const [highlightedOmniboxes, setHighlightedOmniboxes] = useState([]);
117
+ const [highlightedFabs, setHighlightedFabs] = useState([]);
117
118
  const shikiRef = useRef(null);
118
119
 
119
120
  // Get Storybook theme to detect light/dark mode
@@ -232,10 +233,44 @@ export const Panel = ({ active }) => {
232
233
  processOmniboxes();
233
234
  }, [source.omniboxes, shikiTheme]);
234
235
 
236
+ // Highlight fab satellites when source or theme changes
237
+ useEffect(() => {
238
+ if (!source.fabs || source.fabs.length === 0) {
239
+ setHighlightedFabs([]);
240
+ return;
241
+ }
242
+
243
+ const highlightCode = async (code) => {
244
+ if (!code) return '';
245
+ const highlighter = shikiRef.current || await loadShiki();
246
+ if (highlighter) {
247
+ try {
248
+ return highlighter.codeToHtml(code, { lang: 'javascript', theme: shikiTheme });
249
+ } catch (err) {
250
+ return `<pre><code>${escapeHtml(code)}</code></pre>`;
251
+ }
252
+ }
253
+ return `<pre><code>${escapeHtml(code)}</code></pre>`;
254
+ };
255
+
256
+ const processFabs = async () => {
257
+ const highlighted = await Promise.all(
258
+ source.fabs.map(async (fab, index) => ({
259
+ id: fab.id ?? index,
260
+ label: fab.label,
261
+ satellites: await highlightCode(fab.satellites)
262
+ }))
263
+ );
264
+ setHighlightedFabs(highlighted);
265
+ };
266
+
267
+ processFabs();
268
+ }, [source.fabs, shikiTheme]);
269
+
235
270
  useChannel({
236
271
  [EVENTS.UPDATE_HTML]: (payload) => {
237
272
  if (typeof payload === 'string') {
238
- setSource({ markup: payload || '', forms: [], omniboxes: [] });
273
+ setSource({ markup: payload || '', forms: [], omniboxes: [], fabs: [] });
239
274
  return;
240
275
  }
241
276
 
@@ -243,18 +278,19 @@ export const Panel = ({ active }) => {
243
278
  setSource({
244
279
  markup: payload.markup || '',
245
280
  forms: Array.isArray(payload.forms) ? payload.forms : [],
246
- omniboxes: Array.isArray(payload.omniboxes) ? payload.omniboxes : []
281
+ omniboxes: Array.isArray(payload.omniboxes) ? payload.omniboxes : [],
282
+ fabs: Array.isArray(payload.fabs) ? payload.fabs : []
247
283
  });
248
284
  return;
249
285
  }
250
286
 
251
- setSource({ markup: '', forms: [], omniboxes: [] });
287
+ setSource({ markup: '', forms: [], omniboxes: [], fabs: [] });
252
288
  }
253
289
  });
254
290
 
255
291
  // Request HTML update when panel becomes active
256
292
  React.useEffect(() => {
257
- if (active && !source.markup && source.forms.length === 0 && source.omniboxes.length === 0) {
293
+ if (active && !source.markup && source.forms.length === 0 && source.omniboxes.length === 0 && source.fabs.length === 0) {
258
294
  // Trigger a re-extraction by emitting a request event
259
295
  // The decorator will pick this up on the next render cycle
260
296
  const container = document.querySelector('#storybook-root');
@@ -266,7 +302,7 @@ export const Panel = ({ active }) => {
266
302
  }, 100);
267
303
  }
268
304
  }
269
- }, [active, source.markup, source.forms.length, source.omniboxes.length]);
305
+ }, [active, source.markup, source.forms.length, source.omniboxes.length, source.fabs.length]);
270
306
 
271
307
  const copyToClipboard = useCallback(async () => {
272
308
  try {
@@ -282,8 +318,9 @@ export const Panel = ({ active }) => {
282
318
  const hasMarkup = Boolean(source.markup);
283
319
  const hasforms = source.forms.length > 0;
284
320
  const hasOmniboxes = source.omniboxes.length > 0;
321
+ const hasFabs = source.fabs.length > 0;
285
322
 
286
- if (!hasMarkup && !hasforms && !hasOmniboxes) {
323
+ if (!hasMarkup && !hasforms && !hasOmniboxes && !hasFabs) {
287
324
  return (
288
325
  <Container>
289
326
  <EmptyState>
@@ -303,6 +340,31 @@ export const Panel = ({ active }) => {
303
340
  </SectionWrapper>
304
341
  )}
305
342
 
343
+ {hasFabs && (
344
+ <SectionWrapper>
345
+ <SectionHeading>satellites</SectionHeading>
346
+ {source.fabs.map((sourceFab, index) => {
347
+ const key = sourceFab.id ?? index;
348
+ const highlightedFab = highlightedFabs[index];
349
+ const label = source.fabs.length > 1 ? (sourceFab.label || `Fab ${index + 1}`) : null;
350
+
351
+ return (
352
+ <div key={key}>
353
+ {label && <Subheading>{label}</Subheading>}
354
+ {sourceFab.satellites && (
355
+ <CodeBlock
356
+ $compact
357
+ dangerouslySetInnerHTML={{
358
+ __html: highlightedFab?.satellites || `<pre><code>${escapeHtml(sourceFab.satellites)}</code></pre>`
359
+ }}
360
+ />
361
+ )}
362
+ </div>
363
+ );
364
+ })}
365
+ </SectionWrapper>
366
+ )}
367
+
306
368
  {hasforms && source.forms.map((sourceForm, index) => {
307
369
  const key = sourceForm.id ?? index;
308
370
  const highlightedForm = highlightedforms[index];
@@ -375,6 +437,7 @@ export const Panel = ({ active }) => {
375
437
  );
376
438
  })}
377
439
 
440
+
378
441
  {hasMarkup && (
379
442
  <CopyButton
380
443
  onClick={copyToClipboard}
@@ -33,7 +33,8 @@ function formatHTML(html) {
33
33
  }
34
34
  });
35
35
 
36
- return formatted.trim();
36
+ const cleaned = formatted.trim();
37
+ return cleaned.replace(/\s([\w:-]+)=""/g, ' $1');
37
38
  }
38
39
 
39
40
  /**
@@ -45,10 +46,20 @@ function extractHTML(element) {
45
46
  const clone = element.cloneNode(true);
46
47
 
47
48
  // Clean up Storybook-specific attributes
49
+ const runtimeDataAttrs = new Set([
50
+ 'data-original-icon',
51
+ 'data-icon',
52
+ 'data-uid',
53
+ 'data-rendered',
54
+ 'data-runtime',
55
+ 'data-pds-runtime'
56
+ ]);
57
+
48
58
  const cleanElement = (el) => {
49
59
  if (el.removeAttribute) {
50
60
  el.removeAttribute('data-story-id');
51
61
  el.removeAttribute('data-view-mode');
62
+ runtimeDataAttrs.forEach((attr) => el.removeAttribute(attr));
52
63
  }
53
64
  if (el.children) {
54
65
  Array.from(el.children).forEach(cleanElement);
@@ -230,6 +241,39 @@ function generatePdsOmniboxMarkup(omniboxElement) {
230
241
  return `<pds-omnibox${formattedAttrs}></pds-omnibox>`;
231
242
  }
232
243
 
244
+ /**
245
+ * Generate realistic source code for pds-fab elements
246
+ */
247
+ function generatePdsFabMarkup(fabElement) {
248
+ const attrs = [];
249
+
250
+ const stringAttrs = ['id', 'radius', 'spread', 'start-angle'];
251
+ stringAttrs.forEach((attr) => {
252
+ const value = fabElement.getAttribute(attr);
253
+ if (value !== null && value !== undefined && value !== '') {
254
+ attrs.push(`${attr}="${value}"`);
255
+ }
256
+ });
257
+
258
+ if (Array.isArray(fabElement.satellites) && fabElement.satellites.length > 0) {
259
+ const satellitesSource =
260
+ fabElement.getAttribute?.('data-satellites-source') ||
261
+ fabElement.dataset?.satellitesSource ||
262
+ null;
263
+ attrs.push(satellitesSource || '.satellites=${satellites}');
264
+ }
265
+
266
+ const formattedAttrs = attrs.length > 0
267
+ ? '\n ' + attrs.join('\n ') + '\n'
268
+ : '';
269
+
270
+ const icon = fabElement.querySelector?.('pds-icon');
271
+ const iconMarkup = icon?.outerHTML || '<pds-icon icon="plus" size="lg"></pds-icon>';
272
+ const normalizedIcon = iconMarkup.replace(/\s([\w:-]+)=""/g, ' $1');
273
+
274
+ return `<pds-fab${formattedAttrs}>\n ${normalizedIcon}\n</pds-fab>`;
275
+ }
276
+
233
277
  /**
234
278
  * Global decorator that extracts and sends HTML to the panel
235
279
  */
@@ -247,7 +291,11 @@ export const withHTMLExtractor = (storyFn, context) => {
247
291
  // Check if this story has pds-form or pds-omnibox elements
248
292
  const pdsFormElements = Array.from(container.querySelectorAll('pds-form'));
249
293
  const pdsOmniboxElements = Array.from(container.querySelectorAll('pds-omnibox'));
250
- const hasSpecialElements = pdsFormElements.length > 0 || pdsOmniboxElements.length > 0;
294
+ const pdsFabElements = Array.from(container.querySelectorAll('pds-fab'));
295
+ const hasSpecialElements =
296
+ pdsFormElements.length > 0 ||
297
+ pdsOmniboxElements.length > 0 ||
298
+ pdsFabElements.length > 0;
251
299
 
252
300
  if (hasSpecialElements) {
253
301
  // Generate realistic markup for pds-form / pds-omnibox stories
@@ -273,6 +321,11 @@ export const withHTMLExtractor = (storyFn, context) => {
273
321
  markup += generatePdsOmniboxMarkup(omnibox);
274
322
  });
275
323
 
324
+ // Add pds-fab markup
325
+ pdsFabElements.forEach(fab => {
326
+ markup += generatePdsFabMarkup(fab);
327
+ });
328
+
276
329
  html = markup;
277
330
  } else {
278
331
  // No pds-form elements, use standard extraction
@@ -326,10 +379,32 @@ export const withHTMLExtractor = (storyFn, context) => {
326
379
  })
327
380
  .filter((entry) => entry.settings);
328
381
 
382
+ const fabs = pdsFabElements
383
+ .map((fab, index) => {
384
+ const label =
385
+ fab.getAttribute?.('id') ||
386
+ (pdsFabElements.length > 1 ? `Fab ${index + 1}` : 'Fab');
387
+
388
+ const satellitesSource =
389
+ fab.getAttribute?.('data-satellites-source') ||
390
+ fab.dataset?.satellitesSource ||
391
+ null;
392
+
393
+ const satellites = satellitesSource || serializeForDisplay(fab.satellites);
394
+
395
+ return {
396
+ id: index,
397
+ label,
398
+ satellites
399
+ };
400
+ })
401
+ .filter((entry) => entry.satellites);
402
+
329
403
  channel.emit(EVENTS.UPDATE_HTML, {
330
404
  markup: html || '',
331
405
  forms,
332
- omniboxes
406
+ omniboxes,
407
+ fabs
333
408
  });
334
409
  }
335
410
  };
@@ -68,10 +68,10 @@ const config = {
68
68
  process.cwd()
69
69
  ];
70
70
 
71
- // Ensure Lit import alias is resolved
71
+ // Ensure Lit import alias is resolved to PDS bundle
72
72
  const aliases = {
73
73
  ...config.resolve.alias,
74
- '#pds/lit': 'lit',
74
+ '#pds/lit': resolve(pdsSrcPath, 'js/lit.js'),
75
75
  };
76
76
 
77
77
  // In monorepo, pds-configurator is in a separate package, not in src/js
@@ -184,7 +184,7 @@ const config = {
184
184
  config.resolve.alias['@user/pds-config'] = resolve(currentDirname, '../default-pds.config.js');
185
185
  }
186
186
 
187
- // Support absolute path imports like: import { html } from '/assets/js/lit.js';
187
+ // Support absolute path imports like: import { html } from '/assets/pds/external/lit.js';
188
188
  // Vite blocks direct imports from public/, so we disable Vite's public directory
189
189
  // handling (Storybook uses staticDirs instead) and resolve these imports ourselves.
190
190
  const userPublicPath = resolve(process.cwd(), 'public');
@@ -9,7 +9,7 @@
9
9
  <script type="importmap">
10
10
  {
11
11
  "imports": {
12
- "#pds/lit": "/assets/js/lit.js",
12
+ "#pds/lit": "/assets/pds/external/lit.js",
13
13
  "#showdown": "https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.mjs"
14
14
  }
15
15
  }