@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.
- package/.storybook/addons/description/preview.js +15 -0
- package/.storybook/addons/description/register.js +60 -0
- package/.storybook/addons/html-preview/Panel.jsx +327 -0
- package/.storybook/addons/html-preview/constants.js +6 -0
- package/.storybook/addons/html-preview/preview.js +178 -0
- package/.storybook/addons/html-preview/register.js +16 -0
- package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
- package/.storybook/addons/pds-configurator/Tool.js +30 -0
- package/.storybook/addons/pds-configurator/constants.js +9 -0
- package/.storybook/addons/pds-configurator/preview.js +159 -0
- package/.storybook/addons/pds-configurator/register.js +24 -0
- package/.storybook/docs.css +35 -0
- package/.storybook/htmlPreview.css +103 -0
- package/.storybook/htmlPreview.js +271 -0
- package/.storybook/main.js +160 -0
- package/.storybook/preview-body.html +48 -0
- package/.storybook/preview-head.html +11 -0
- package/.storybook/preview.js +1563 -0
- package/README.md +266 -0
- package/bin/index.js +40 -0
- package/dist/pds-reference.json +2101 -0
- package/package.json +45 -0
- package/pds.config.js +6 -0
- package/public/assets/css/app.css +1216 -0
- package/public/assets/data/auto-design-advanced.json +704 -0
- package/public/assets/data/auto-design-simple.json +123 -0
- package/public/assets/img/icon-512x512.png +0 -0
- package/public/assets/img/logo-trans.png +0 -0
- package/public/assets/img/logo.png +0 -0
- package/public/assets/js/app.js +15088 -0
- package/public/assets/js/app.js.map +7 -0
- package/public/assets/js/lit.js +1176 -0
- package/public/assets/js/lit.js.map +7 -0
- package/public/assets/js/pds.js +9801 -0
- package/public/assets/js/pds.js.map +7 -0
- package/public/assets/pds/components/pds-calendar.js +837 -0
- package/public/assets/pds/components/pds-drawer.js +857 -0
- package/public/assets/pds/components/pds-icon.js +338 -0
- package/public/assets/pds/components/pds-jsonform.js +1775 -0
- package/public/assets/pds/components/pds-richtext.js +1035 -0
- package/public/assets/pds/components/pds-scrollrow.js +331 -0
- package/public/assets/pds/components/pds-splitpanel.js +401 -0
- package/public/assets/pds/components/pds-tabstrip.js +251 -0
- package/public/assets/pds/components/pds-toaster.js +446 -0
- package/public/assets/pds/components/pds-upload.js +657 -0
- package/public/assets/pds/custom-elements.json +2003 -0
- package/public/assets/pds/icons/pds-icons.svg +498 -0
- package/public/assets/pds/pds-css-complete.json +1861 -0
- package/public/assets/pds/pds-runtime-config.json +11 -0
- package/public/assets/pds/pds.css-data.json +2152 -0
- package/public/assets/pds/styles/pds-components.css +1944 -0
- package/public/assets/pds/styles/pds-components.css.js +3895 -0
- package/public/assets/pds/styles/pds-primitives.css +352 -0
- package/public/assets/pds/styles/pds-primitives.css.js +711 -0
- package/public/assets/pds/styles/pds-styles.css +3761 -0
- package/public/assets/pds/styles/pds-styles.css.js +7529 -0
- package/public/assets/pds/styles/pds-tokens.css +699 -0
- package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
- package/public/assets/pds/styles/pds-utilities.css +763 -0
- package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
- package/public/assets/pds/vscode-custom-data.json +824 -0
- package/scripts/build-pds-reference.mjs +807 -0
- package/scripts/generate-stories.js +542 -0
- package/scripts/package-build.js +86 -0
- package/src/js/app.js +17 -0
- package/src/js/common/ask.js +208 -0
- package/src/js/common/common.js +20 -0
- package/src/js/common/font-loader.js +200 -0
- package/src/js/common/msg.js +90 -0
- package/src/js/lit.js +40 -0
- package/src/js/pds-core/pds-config.js +1162 -0
- package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
- package/src/js/pds-core/pds-enhancers.js +357 -0
- package/src/js/pds-core/pds-enums.js +86 -0
- package/src/js/pds-core/pds-generator.js +5317 -0
- package/src/js/pds-core/pds-ontology.js +256 -0
- package/src/js/pds-core/pds-paths.js +109 -0
- package/src/js/pds-core/pds-query.js +571 -0
- package/src/js/pds-core/pds-registry.js +129 -0
- package/src/js/pds-core/pds.d.ts +129 -0
- package/src/js/pds.d.ts +408 -0
- package/src/js/pds.js +1579 -0
- package/src/pds-core/pds-api.js +105 -0
- package/stories/GettingStarted.md +96 -0
- package/stories/GettingStarted.stories.js +144 -0
- package/stories/WhatIsPDS.md +194 -0
- package/stories/WhatIsPDS.stories.js +144 -0
- package/stories/components/PdsCalendar.stories.js +263 -0
- package/stories/components/PdsDrawer.stories.js +623 -0
- package/stories/components/PdsIcon.stories.js +78 -0
- package/stories/components/PdsJsonform.stories.js +1444 -0
- package/stories/components/PdsRichtext.stories.js +367 -0
- package/stories/components/PdsScrollrow.stories.js +140 -0
- package/stories/components/PdsSplitpanel.stories.js +502 -0
- package/stories/components/PdsTabstrip.stories.js +442 -0
- package/stories/components/PdsToaster.stories.js +186 -0
- package/stories/components/PdsUpload.stories.js +66 -0
- package/stories/enhancements/Dropdowns.stories.js +185 -0
- package/stories/enhancements/InteractiveStates.stories.js +625 -0
- package/stories/enhancements/MeshGradients.stories.js +320 -0
- package/stories/enhancements/OpenGroups.stories.js +227 -0
- package/stories/enhancements/RangeSliders.stories.js +232 -0
- package/stories/enhancements/RequiredFields.stories.js +189 -0
- package/stories/enhancements/Toggles.stories.js +167 -0
- package/stories/foundations/Colors.stories.js +283 -0
- package/stories/foundations/Icons.stories.js +305 -0
- package/stories/foundations/SmartSurfaces.stories.js +367 -0
- package/stories/foundations/Spacing.stories.js +175 -0
- package/stories/foundations/Typography.stories.js +960 -0
- package/stories/foundations/ZIndex.stories.js +325 -0
- package/stories/patterns/BorderEffects.stories.js +72 -0
- package/stories/patterns/Layout.stories.js +99 -0
- package/stories/patterns/Utilities.stories.js +107 -0
- package/stories/primitives/Accordion.stories.js +359 -0
- package/stories/primitives/Alerts.stories.js +64 -0
- package/stories/primitives/Badges.stories.js +183 -0
- package/stories/primitives/Buttons.stories.js +229 -0
- package/stories/primitives/Cards.stories.js +353 -0
- package/stories/primitives/FormGroups.stories.js +569 -0
- package/stories/primitives/Forms.stories.js +131 -0
- package/stories/primitives/Media.stories.js +203 -0
- package/stories/primitives/Tables.stories.js +232 -0
- package/stories/reference/ReferenceCatalog.stories.js +28 -0
- package/stories/reference/reference-catalog.js +413 -0
- package/stories/reference/reference-docs.js +302 -0
- package/stories/reference/reference-helpers.js +310 -0
- package/stories/utilities/GridSystem.stories.js +208 -0
- package/stories/utils/PdsAsk.stories.js +420 -0
- 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, '&')
|
|
174
|
+
.replace(/</g, '<')
|
|
175
|
+
.replace(/>/g, '>');
|
|
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 += '<';
|
|
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 += '>';
|
|
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,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
|
+
};
|