@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,15 @@
1
+ import { addons } from '@storybook/preview-api';
2
+
3
+ export const withDescription = (StoryFn, context) => {
4
+ const channel = addons.getChannel();
5
+ const description = context.parameters?.docs?.description?.component || '';
6
+ const title = context.title || '';
7
+
8
+ // Send description to the panel
9
+ channel.emit('pds-description/update', {
10
+ title,
11
+ description
12
+ });
13
+
14
+ return StoryFn();
15
+ };
@@ -0,0 +1,60 @@
1
+ import { addons, types } from '@storybook/manager-api';
2
+ import { AddonPanel } from '@storybook/components';
3
+ import React, { useEffect, useState } from 'react';
4
+ import { Markdown } from '@storybook/blocks';
5
+
6
+ const ADDON_ID = 'pds-description';
7
+ const PANEL_ID = `${ADDON_ID}/panel`;
8
+
9
+ const DescriptionPanel = () => {
10
+ const [description, setDescription] = useState('');
11
+ const [title, setTitle] = useState('');
12
+
13
+ useEffect(() => {
14
+ const channel = addons.getChannel();
15
+
16
+ const updateDescription = (data) => {
17
+ setTitle(data.title || '');
18
+ setDescription(data.description || 'No description available.');
19
+ };
20
+
21
+ channel.on('pds-description/update', updateDescription);
22
+
23
+ return () => {
24
+ channel.removeListener('pds-description/update', updateDescription);
25
+ };
26
+ }, []);
27
+
28
+ return React.createElement('div', {
29
+ style: {
30
+ padding: '1rem',
31
+ height: '100%',
32
+ overflow: 'auto'
33
+ }
34
+ },
35
+ React.createElement('div', {
36
+ style: {
37
+ marginBottom: '0.5rem'
38
+ }
39
+ },
40
+ React.createElement('strong', {
41
+ style: {
42
+ fontSize: '1rem'
43
+ }
44
+ }, title)
45
+ ),
46
+ React.createElement(Markdown, null, description)
47
+ );
48
+ };
49
+
50
+ addons.register(ADDON_ID, () => {
51
+ addons.add(PANEL_ID, {
52
+ type: types.PANEL,
53
+ title: 'Description',
54
+ render: ({ active }) => {
55
+ return React.createElement(AddonPanel, { active },
56
+ React.createElement(DescriptionPanel)
57
+ );
58
+ }
59
+ });
60
+ });
@@ -0,0 +1,327 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { useChannel } from '@storybook/manager-api';
3
+ import { IconButton } from '@storybook/components';
4
+ import { EVENTS } from './constants.js';
5
+ import { styled } from '@storybook/theming';
6
+
7
+ const Container = styled.div`
8
+ position: relative;
9
+ height: 100%;
10
+ overflow: auto;
11
+ background: ${props => props.theme.background.content};
12
+ `;
13
+
14
+ const CodeBlock = styled.pre`
15
+ margin: 0;
16
+ padding: 16px;
17
+ padding-bottom: ${props => (props.$compact ? '24px' : '60px')}; /* Space for fixed button */
18
+ font-family: ${props => props.theme.typography.fonts.mono};
19
+ font-size: 13px;
20
+ line-height: 1.6;
21
+ color: ${props => props.theme.color.defaultText};
22
+ background: transparent;
23
+ tab-size: 2;
24
+
25
+ .html-token-tag {
26
+ color: #e06c75;
27
+ }
28
+
29
+ .html-token-attr {
30
+ color: #d19a66;
31
+ }
32
+
33
+ .html-token-value {
34
+ color: #98c379;
35
+ }
36
+
37
+ .html-token-comment {
38
+ color: #5c6370;
39
+ font-style: italic;
40
+ }
41
+ `;
42
+
43
+ const SectionWrapper = styled.div`
44
+ border-bottom: 1px solid ${props => props.theme.appBorderColor};
45
+
46
+ &:last-of-type {
47
+ border-bottom: none;
48
+ }
49
+ `;
50
+
51
+ const SectionHeading = styled.h3`
52
+ margin: 0;
53
+ padding: 16px 16px 0;
54
+ font-size: 12px;
55
+ font-weight: 600;
56
+ text-transform: uppercase;
57
+ letter-spacing: 0.08em;
58
+ color: ${props => props.theme.color.mediumdark};
59
+ `;
60
+
61
+ const Subheading = styled.h4`
62
+ margin: 0;
63
+ padding: 12px 16px 0;
64
+ font-size: 11px;
65
+ font-weight: 600;
66
+ text-transform: uppercase;
67
+ letter-spacing: 0.08em;
68
+ color: ${props => props.theme.color.mediumdark};
69
+ `;
70
+
71
+ const EmptyState = styled.div`
72
+ display: flex;
73
+ flex-direction: column;
74
+ align-items: center;
75
+ justify-content: center;
76
+ height: 100%;
77
+ color: ${props => props.theme.color.mediumdark};
78
+ text-align: center;
79
+ padding: 20px;
80
+ `;
81
+
82
+ const CopyButton = styled(IconButton)`
83
+ position: fixed;
84
+ bottom: 16px;
85
+ right: 16px;
86
+ z-index: 10;
87
+ background: ${props => props.theme.background.app};
88
+ border: 1px solid ${props => props.theme.appBorderColor};
89
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
90
+
91
+ &:hover {
92
+ background: ${props => props.theme.background.hoverable};
93
+ }
94
+
95
+ &.copied {
96
+ background: ${props => props.theme.color.positive};
97
+ color: white;
98
+ border-color: ${props => props.theme.color.positive};
99
+ }
100
+ `;
101
+
102
+ const CopyIcon = () => (
103
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
104
+ <rect x="3" y="3" width="8" height="9" rx="1" stroke="currentColor" strokeWidth="1.5" fill="none"/>
105
+ <path d="M5 3V2C5 1.44772 5.44772 1 6 1H12C12.5523 1 13 1.44772 13 2V10C13 10.5523 12.5523 11 12 11H11" stroke="currentColor" strokeWidth="1.5" fill="none"/>
106
+ </svg>
107
+ );
108
+
109
+ const CheckIcon = () => (
110
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
111
+ <path d="M2 7L5.5 10.5L12 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
112
+ </svg>
113
+ );
114
+
115
+ export const Panel = ({ active }) => {
116
+ const [source, setSource] = useState({ markup: '', jsonForms: [] });
117
+ const [copied, setCopied] = useState(false);
118
+
119
+ useChannel({
120
+ [EVENTS.UPDATE_HTML]: (payload) => {
121
+ if (typeof payload === 'string') {
122
+ setSource({ markup: payload || '', jsonForms: [] });
123
+ return;
124
+ }
125
+
126
+ if (payload && typeof payload === 'object') {
127
+ setSource({
128
+ markup: payload.markup || '',
129
+ jsonForms: Array.isArray(payload.jsonForms) ? payload.jsonForms : []
130
+ });
131
+ return;
132
+ }
133
+
134
+ setSource({ markup: '', jsonForms: [] });
135
+ }
136
+ });
137
+
138
+ // Request HTML update when panel becomes active
139
+ React.useEffect(() => {
140
+ if (active && !source.markup && source.jsonForms.length === 0) {
141
+ // Trigger a re-extraction by emitting a request event
142
+ // The decorator will pick this up on the next render cycle
143
+ const container = document.querySelector('#storybook-root');
144
+ if (container && container.innerHTML) {
145
+ // Panel just became active, HTML might already be there
146
+ // Give it a moment to process
147
+ setTimeout(() => {
148
+ // This will be caught by subsequent decorator runs
149
+ }, 100);
150
+ }
151
+ }
152
+ }, [active, source.markup, source.jsonForms.length]);
153
+
154
+ const copyToClipboard = useCallback(async () => {
155
+ try {
156
+ if (!source.markup) return;
157
+ await navigator.clipboard.writeText(source.markup);
158
+ setCopied(true);
159
+ setTimeout(() => setCopied(false), 2000);
160
+ } catch (err) {
161
+ console.error('Failed to copy:', err);
162
+ }
163
+ }, [source.markup]);
164
+
165
+ const highlightHTML = useCallback((code) => {
166
+ if (!code) return '';
167
+
168
+ let result = '';
169
+ let i = 0;
170
+
171
+ const escapeHtml = (text) => {
172
+ return text
173
+ .replace(/&/g, '&amp;')
174
+ .replace(/</g, '&lt;')
175
+ .replace(/>/g, '&gt;');
176
+ };
177
+
178
+ while (i < code.length) {
179
+ // Handle HTML comments
180
+ if (code.substr(i, 4) === '<!--') {
181
+ const end = code.indexOf('-->', i);
182
+ if (end !== -1) {
183
+ const comment = code.substring(i, end + 3);
184
+ result += `<span class="html-token-comment">${escapeHtml(comment)}</span>`;
185
+ i = end + 3;
186
+ continue;
187
+ }
188
+ }
189
+
190
+ // Handle tags
191
+ if (code[i] === '<') {
192
+ const tagEnd = code.indexOf('>', i);
193
+ if (tagEnd !== -1) {
194
+ const tagContent = code.substring(i + 1, tagEnd);
195
+ result += '&lt;';
196
+
197
+ // Check if it's a closing tag
198
+ if (tagContent[0] === '/') {
199
+ result += '/';
200
+ const tagName = tagContent.substring(1).split(/[\s>]/)[0];
201
+ result += `<span class="html-token-tag">${escapeHtml(tagName)}</span>`;
202
+ } else {
203
+ // Parse tag name and attributes
204
+ const parts = tagContent.match(/^([\w-]+)([\s\S]*?)(\/?)?$/);
205
+ if (parts) {
206
+ const [, tagName, attrsStr, slash] = parts;
207
+ result += `<span class="html-token-tag">${escapeHtml(tagName)}</span>`;
208
+
209
+ // Parse attributes
210
+ if (attrsStr.trim()) {
211
+ const attrRegex = /([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
212
+ let match;
213
+ let lastIndex = 0;
214
+
215
+ while ((match = attrRegex.exec(attrsStr)) !== null) {
216
+ result += escapeHtml(attrsStr.substring(lastIndex, match.index));
217
+
218
+ const [fullMatch, attrName, doubleQuoted, singleQuoted, unquoted] = match;
219
+ result += `<span class="html-token-attr">${escapeHtml(attrName)}</span>`;
220
+
221
+ if (doubleQuoted !== undefined) {
222
+ result += `=<span class="html-token-value">"${escapeHtml(doubleQuoted)}"</span>`;
223
+ } else if (singleQuoted !== undefined) {
224
+ result += `=<span class="html-token-value">'${escapeHtml(singleQuoted)}'</span>`;
225
+ } else if (unquoted !== undefined) {
226
+ result += `=<span class="html-token-value">${escapeHtml(unquoted)}</span>`;
227
+ }
228
+
229
+ lastIndex = match.index + fullMatch.length;
230
+ }
231
+
232
+ result += escapeHtml(attrsStr.substring(lastIndex));
233
+ }
234
+
235
+ if (slash) result += '/';
236
+ }
237
+ }
238
+
239
+ result += '&gt;';
240
+ i = tagEnd + 1;
241
+ continue;
242
+ }
243
+ }
244
+
245
+ // Regular text
246
+ result += escapeHtml(code[i]);
247
+ i++;
248
+ }
249
+
250
+ return result;
251
+ }, []);
252
+
253
+ const hasMarkup = Boolean(source.markup);
254
+ const hasJsonForms = source.jsonForms.length > 0;
255
+
256
+ if (!hasMarkup && !hasJsonForms) {
257
+ return (
258
+ <Container>
259
+ <EmptyState>
260
+ <p>No code to display</p>
261
+ <p style={{ fontSize: '0.875rem', opacity: 0.7 }}>Select a story to inspect its output</p>
262
+ </EmptyState>
263
+ </Container>
264
+ );
265
+ }
266
+
267
+ return (
268
+ <Container>
269
+ {hasMarkup && (
270
+ <SectionWrapper>
271
+ <SectionHeading>Markup</SectionHeading>
272
+ <CodeBlock>
273
+ <code dangerouslySetInnerHTML={{ __html: highlightHTML(source.markup) }} />
274
+ </CodeBlock>
275
+ </SectionWrapper>
276
+ )}
277
+
278
+ {hasJsonForms && source.jsonForms.map((form, index) => {
279
+ const key = form.id ?? index;
280
+ const heading = source.jsonForms.length > 1 ? (form.label || `Form ${index + 1}`) : 'pds-jsonform';
281
+
282
+ return (
283
+ <SectionWrapper key={key}>
284
+ <SectionHeading>{heading}</SectionHeading>
285
+
286
+ {form.jsonSchema && (
287
+ <>
288
+ <Subheading>jsonSchema</Subheading>
289
+ <CodeBlock $compact>
290
+ <code>{form.jsonSchema}</code>
291
+ </CodeBlock>
292
+ </>
293
+ )}
294
+
295
+ {form.uiSchema && (
296
+ <>
297
+ <Subheading>uiSchema</Subheading>
298
+ <CodeBlock $compact>
299
+ <code>{form.uiSchema}</code>
300
+ </CodeBlock>
301
+ </>
302
+ )}
303
+
304
+ {form.options && (
305
+ <>
306
+ <Subheading>options</Subheading>
307
+ <CodeBlock $compact>
308
+ <code>{form.options}</code>
309
+ </CodeBlock>
310
+ </>
311
+ )}
312
+ </SectionWrapper>
313
+ );
314
+ })}
315
+
316
+ {hasMarkup && (
317
+ <CopyButton
318
+ onClick={copyToClipboard}
319
+ title={copied ? 'Copied!' : 'Copy markup'}
320
+ className={copied ? 'copied' : ''}
321
+ >
322
+ {copied ? <CheckIcon /> : <CopyIcon />}
323
+ </CopyButton>
324
+ )}
325
+ </Container>
326
+ );
327
+ };
@@ -0,0 +1,6 @@
1
+ export const ADDON_ID = 'html-preview';
2
+ export const PANEL_ID = `${ADDON_ID}/panel`;
3
+
4
+ export const EVENTS = {
5
+ UPDATE_HTML: `${ADDON_ID}/updateHtml`
6
+ };
@@ -0,0 +1,178 @@
1
+ import { useEffect } from '@storybook/preview-api';
2
+ import { addons } from '@storybook/preview-api';
3
+ import { EVENTS } from './constants.js';
4
+ import { render as litRender } from 'lit';
5
+
6
+ /**
7
+ * Format HTML string with proper indentation
8
+ */
9
+ function formatHTML(html) {
10
+ if (!html) return '';
11
+
12
+ let formatted = '';
13
+ let indent = 0;
14
+ const tab = ' ';
15
+
16
+ const tokens = html.split(/(<[^>]+>)/g).filter(Boolean);
17
+
18
+ tokens.forEach((token) => {
19
+ if (token.startsWith('</')) {
20
+ // Closing tag
21
+ indent = Math.max(0, indent - 1);
22
+ formatted += '\n' + tab.repeat(indent) + token;
23
+ } else if (token.startsWith('<')) {
24
+ // Opening tag
25
+ const isSelfClosing = token.endsWith('/>') || token.match(/<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)/);
26
+ formatted += '\n' + tab.repeat(indent) + token;
27
+ if (!isSelfClosing) {
28
+ indent++;
29
+ }
30
+ } else if (token.trim()) {
31
+ // Text content
32
+ formatted += token.trim();
33
+ }
34
+ });
35
+
36
+ return formatted.trim();
37
+ }
38
+
39
+ /**
40
+ * Extract HTML from a rendered story
41
+ */
42
+ function extractHTML(element) {
43
+ if (!element) return '';
44
+
45
+ const clone = element.cloneNode(true);
46
+
47
+ // Clean up Storybook-specific attributes
48
+ const cleanElement = (el) => {
49
+ if (el.removeAttribute) {
50
+ el.removeAttribute('data-story-id');
51
+ el.removeAttribute('data-view-mode');
52
+ }
53
+ if (el.children) {
54
+ Array.from(el.children).forEach(cleanElement);
55
+ }
56
+ };
57
+
58
+ cleanElement(clone);
59
+ return clone.innerHTML || '';
60
+ }
61
+
62
+ /**
63
+ * Transform Lit template result to HTML string
64
+ */
65
+ async function litToHTML(templateResult) {
66
+ if (!templateResult || !templateResult._$litType$) {
67
+ return '';
68
+ }
69
+
70
+ const temp = document.createElement('div');
71
+ litRender(templateResult, temp);
72
+
73
+ await new Promise(resolve => setTimeout(resolve, 50));
74
+
75
+ return formatHTML(temp.innerHTML);
76
+ }
77
+
78
+ function serializeForDisplay(value) {
79
+ if (value === undefined) return '';
80
+ if (typeof value === 'bigint') return value.toString();
81
+ if (typeof value === 'symbol') return value.toString();
82
+
83
+ const seen = new WeakSet();
84
+
85
+ const replacer = (key, currentValue) => {
86
+ if (typeof currentValue === 'function') {
87
+ return `[Function${currentValue.name ? `: ${currentValue.name}` : ''}]`;
88
+ }
89
+ if (typeof currentValue === 'bigint') {
90
+ return currentValue.toString();
91
+ }
92
+ if (typeof currentValue === 'symbol') {
93
+ return currentValue.toString();
94
+ }
95
+ if (currentValue instanceof Map) {
96
+ return Object.fromEntries(currentValue);
97
+ }
98
+ if (currentValue instanceof Set) {
99
+ return Array.from(currentValue);
100
+ }
101
+ if (currentValue instanceof Date) {
102
+ return currentValue.toISOString();
103
+ }
104
+ if (typeof currentValue === 'object' && currentValue !== null) {
105
+ if (seen.has(currentValue)) {
106
+ return '[Circular]';
107
+ }
108
+ seen.add(currentValue);
109
+ }
110
+ return currentValue;
111
+ };
112
+
113
+ try {
114
+ if (value === null) {
115
+ return 'null';
116
+ }
117
+ return JSON.stringify(value, replacer, 2);
118
+ } catch (error) {
119
+ return `/* Unable to serialize value: ${error.message} */`;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Global decorator that extracts and sends HTML to the panel
125
+ */
126
+ export const withHTMLExtractor = (storyFn, context) => {
127
+ const story = storyFn();
128
+ const channel = addons.getChannel();
129
+
130
+ // Function to extract and send HTML
131
+ const extractAndSendHTML = async () => {
132
+ let html = '';
133
+
134
+ // Try to get HTML from the story container
135
+ const container = document.querySelector('#storybook-root');
136
+ if (container) {
137
+ if (story && story._$litType$) {
138
+ html = await litToHTML(story);
139
+ } else {
140
+ html = formatHTML(extractHTML(container));
141
+ }
142
+
143
+ const forms = Array.from(container.querySelectorAll('pds-jsonform'));
144
+ const jsonForms = forms
145
+ .map((form, index) => {
146
+ const label =
147
+ form.getAttribute?.('id') ||
148
+ form.getAttribute?.('name') ||
149
+ (forms.length > 1 ? `Form ${index + 1}` : 'Form');
150
+
151
+ const jsonSchema = serializeForDisplay(form.jsonSchema);
152
+ const uiSchema = serializeForDisplay(form.uiSchema);
153
+ const options = serializeForDisplay(form.options);
154
+
155
+ return {
156
+ id: index,
157
+ label,
158
+ jsonSchema,
159
+ uiSchema,
160
+ options
161
+ };
162
+ })
163
+ .filter((entry) => entry.jsonSchema || entry.uiSchema || entry.options);
164
+
165
+ channel.emit(EVENTS.UPDATE_HTML, {
166
+ markup: html || '',
167
+ jsonForms
168
+ });
169
+ }
170
+ };
171
+
172
+ // Extract and send HTML after render (multiple times to catch async renders)
173
+ setTimeout(extractAndSendHTML, 50);
174
+ setTimeout(extractAndSendHTML, 150);
175
+ setTimeout(extractAndSendHTML, 300);
176
+
177
+ return story;
178
+ };
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { addons, types } from '@storybook/manager-api';
3
+ import { ADDON_ID, PANEL_ID } from './constants.js';
4
+ import { Panel } from './Panel.jsx';
5
+
6
+ addons.register(ADDON_ID, () => {
7
+ addons.add(PANEL_ID, {
8
+ type: types.PANEL,
9
+ title: 'Code',
10
+ render: ({ active, key }) => (
11
+ <div style={{ display: active ? 'block' : 'none', height: '100%' }}>
12
+ <Panel key={key} active={active} />
13
+ </div>
14
+ )
15
+ });
16
+ });
@@ -0,0 +1,44 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { useChannel } from '@storybook/manager-api';
3
+ import { IconButton } from '@storybook/components';
4
+ import { EVENTS } from './constants.js';
5
+
6
+ export const SearchTool = () => {
7
+ const [isOpen, setIsOpen] = useState(false);
8
+
9
+ const channel = useChannel({
10
+ [EVENTS.QUERY_EXECUTED + '_RESPONSE']: (data) => {
11
+ console.log('Query response received:', data);
12
+ }
13
+ });
14
+
15
+ const toggleSearch = useCallback(() => {
16
+ const newState = !isOpen;
17
+ console.log('Toggle search - current isOpen:', isOpen, 'newState:', newState);
18
+
19
+ if (newState) {
20
+ // Emit event to open search in preview
21
+ const searchQuery = prompt('Enter search query (e.g., "primary color", "spacing", "button"):');
22
+ if (searchQuery) {
23
+ console.log('Executing search query:', searchQuery);
24
+ channel.emit(EVENTS.QUERY_EXECUTED, { query: searchQuery });
25
+ }
26
+ }
27
+
28
+ setIsOpen(newState);
29
+ }, [isOpen, channel]);
30
+
31
+ return (
32
+ <IconButton
33
+ key="search-tool"
34
+ active={isOpen}
35
+ title="Quick Search PDS"
36
+ onClick={toggleSearch}
37
+ >
38
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
39
+ <circle cx="11" cy="11" r="8" />
40
+ <path d="m21 21-4.35-4.35" />
41
+ </svg>
42
+ </IconButton>
43
+ );
44
+ };
@@ -0,0 +1,30 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { useChannel } from '@storybook/manager-api';
3
+ import { IconButton } from '@storybook/components';
4
+ import { EVENTS } from './constants.js';
5
+
6
+ export const Tool = () => {
7
+ const emit = useChannel({
8
+ [EVENTS.DESIGN_UPDATED]: (data) => {
9
+ console.log('Design updated in addon:', data);
10
+ }
11
+ });
12
+
13
+ const openConfigurator = useCallback(() => {
14
+ console.log('Opening PDS Configurator');
15
+ emit(EVENTS.OPEN_CONFIGURATOR);
16
+ }, [emit]);
17
+
18
+ return (
19
+ <IconButton
20
+ key="configurator-toggle"
21
+ title="Open PDS Configurator"
22
+ onClick={openConfigurator}
23
+ >
24
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
25
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
26
+ <circle cx="12" cy="12" r="3"/>
27
+ </svg>
28
+ </IconButton>
29
+ );
30
+ };
@@ -0,0 +1,9 @@
1
+ export const ADDON_ID = 'pds-configurator';
2
+ export const TOOL_ID = `${ADDON_ID}/tool`;
3
+ export const PANEL_ID = `${ADDON_ID}/panel`;
4
+
5
+ export const EVENTS = {
6
+ OPEN_CONFIGURATOR: `${ADDON_ID}/openConfigurator`,
7
+ DESIGN_UPDATED: `${ADDON_ID}/designUpdated`,
8
+ QUERY_EXECUTED: `${ADDON_ID}/queryExecuted`,
9
+ };