@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview-side addon logic for PDS Configurator
|
|
3
|
+
* Runs in the iframe where stories are rendered
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { addons } from '@storybook/preview-api';
|
|
7
|
+
import { EVENTS } from './constants.js';
|
|
8
|
+
|
|
9
|
+
let drawerElement = null;
|
|
10
|
+
let configFormLoaded = false;
|
|
11
|
+
|
|
12
|
+
// Initialize configurator UI in preview
|
|
13
|
+
async function initializeConfigurator() {
|
|
14
|
+
if (drawerElement) return;
|
|
15
|
+
|
|
16
|
+
// Force-load pds-drawer by triggering AutoDefiner
|
|
17
|
+
// Create a temporary element to force the component to load
|
|
18
|
+
if (!customElements.get('pds-drawer')) {
|
|
19
|
+
const temp = document.createElement('pds-drawer');
|
|
20
|
+
temp.style.display = 'none';
|
|
21
|
+
document.body.appendChild(temp);
|
|
22
|
+
try {
|
|
23
|
+
await customElements.whenDefined('pds-drawer');
|
|
24
|
+
} finally {
|
|
25
|
+
temp.remove();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create drawer for configurator
|
|
30
|
+
drawerElement = document.createElement('pds-drawer');
|
|
31
|
+
drawerElement.id = 'storybook-pds-configurator';
|
|
32
|
+
drawerElement.setAttribute('position', 'bottom');
|
|
33
|
+
drawerElement.setAttribute('show-close', '');
|
|
34
|
+
drawerElement.style.zIndex = '999999';
|
|
35
|
+
|
|
36
|
+
// Create header
|
|
37
|
+
//const header = document.createElement('div');
|
|
38
|
+
//header.slot = 'drawer-header';
|
|
39
|
+
//header.innerHTML = `
|
|
40
|
+
//`;
|
|
41
|
+
//drawerElement.appendChild(header);
|
|
42
|
+
|
|
43
|
+
// Create configurator content container
|
|
44
|
+
const content = document.createElement('div');
|
|
45
|
+
content.slot = 'drawer-content';
|
|
46
|
+
content.id = 'configurator-content';
|
|
47
|
+
|
|
48
|
+
content.innerHTML = `<p style="padding: 1rem;">Loading configurator...</p>`;
|
|
49
|
+
drawerElement.appendChild(content);
|
|
50
|
+
|
|
51
|
+
document.body.appendChild(drawerElement);
|
|
52
|
+
|
|
53
|
+
// Load pds-config-form dynamically
|
|
54
|
+
loadConfigForm();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function loadConfigForm() {
|
|
58
|
+
if (configFormLoaded) return;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Import the config form component with timeout
|
|
62
|
+
const importTimeout = new Promise((_, reject) =>
|
|
63
|
+
setTimeout(() => reject(new Error('Config form import timeout')), 3000)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await Promise.race([
|
|
67
|
+
import('../../../../pds-configurator/src/pds-config-form.js'),
|
|
68
|
+
importTimeout
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Wait for component to be defined
|
|
72
|
+
await customElements.whenDefined('pds-config-form');
|
|
73
|
+
|
|
74
|
+
const content = document.getElementById('configurator-content');
|
|
75
|
+
if (content) {
|
|
76
|
+
content.innerHTML = '<pds-config-form id="storybook-config-form"></pds-config-form>';
|
|
77
|
+
configFormLoaded = true;
|
|
78
|
+
|
|
79
|
+
// Listen for design updates from the form
|
|
80
|
+
const PDS = (await import('../../../../../src/js/pds.js')).PDS;
|
|
81
|
+
PDS.addEventListener('pds:design:updated', async (e) => {
|
|
82
|
+
console.log('Design updated in configurator:', e.detail);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// The event detail contains both config and designer from pds-config-form
|
|
86
|
+
if (e.detail.designer) {
|
|
87
|
+
// Apply the styles from the designer that was already created by pds-config-form
|
|
88
|
+
await PDS.Generator.applyStyles(e.detail.designer);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Notify manager
|
|
92
|
+
const channel = addons.getChannel();
|
|
93
|
+
channel.emit(EVENTS.DESIGN_UPDATED, e.detail);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Failed to apply design update:', error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Failed to load config form:', error);
|
|
101
|
+
const content = document.getElementById('configurator-content');
|
|
102
|
+
if (content) {
|
|
103
|
+
content.innerHTML = `<p style="padding: 1rem; color: red;">Error loading configurator: ${error.message}</p>`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Listen for addon events from manager
|
|
109
|
+
if (typeof window !== 'undefined') {
|
|
110
|
+
const channel = addons.getChannel();
|
|
111
|
+
|
|
112
|
+
channel.on(EVENTS.OPEN_CONFIGURATOR, async () => {
|
|
113
|
+
console.log('🎯 OPEN_CONFIGURATOR event received in preview');
|
|
114
|
+
try {
|
|
115
|
+
// Add timeout to prevent hanging forever
|
|
116
|
+
const timeout = new Promise((_, reject) =>
|
|
117
|
+
setTimeout(() => reject(new Error('Configurator initialization timeout')), 5000)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await Promise.race([initializeConfigurator(), timeout]);
|
|
121
|
+
|
|
122
|
+
if (drawerElement) {
|
|
123
|
+
console.log('✅ Opening configurator drawer');
|
|
124
|
+
// Always open (no toggle)
|
|
125
|
+
drawerElement.open = true;
|
|
126
|
+
} else {
|
|
127
|
+
throw new Error('Drawer element failed to initialize');
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('❌ Failed to open configurator:', error);
|
|
131
|
+
// Show user-friendly error
|
|
132
|
+
alert(`Failed to open PDS Configurator: ${error.message}\n\nPlease refresh the page and try again.`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
channel.on(EVENTS.QUERY_EXECUTED, async ({ query }) => {
|
|
137
|
+
try {
|
|
138
|
+
const PDS = (await import('../../../../../src/js/pds.js')).PDS;
|
|
139
|
+
const results = await PDS.query(query);
|
|
140
|
+
|
|
141
|
+
// Send results back to manager
|
|
142
|
+
channel.emit(EVENTS.QUERY_EXECUTED + '_RESPONSE', {
|
|
143
|
+
query,
|
|
144
|
+
results: results.slice(0, 10).map(r => ({
|
|
145
|
+
id: r.text,
|
|
146
|
+
title: r.text,
|
|
147
|
+
value: r.value,
|
|
148
|
+
category: r.category,
|
|
149
|
+
onClick: () => {
|
|
150
|
+
console.log('Query result clicked:', r);
|
|
151
|
+
// Could copy to clipboard or navigate
|
|
152
|
+
}
|
|
153
|
+
}))
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Query execution failed:', error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { addons, types } from '@storybook/manager-api';
|
|
2
|
+
import { ADDON_ID, TOOL_ID } from './constants.js';
|
|
3
|
+
import { Tool } from './Tool.js';
|
|
4
|
+
import { SearchTool } from './SearchTool.js';
|
|
5
|
+
|
|
6
|
+
const SEARCH_TOOL_ID = `${ADDON_ID}/search`;
|
|
7
|
+
|
|
8
|
+
addons.register(ADDON_ID, () => {
|
|
9
|
+
// Register PDS Configurator button
|
|
10
|
+
addons.add(TOOL_ID, {
|
|
11
|
+
type: types.TOOL,
|
|
12
|
+
title: 'PDS Configurator',
|
|
13
|
+
match: ({ viewMode, tabId }) => !tabId && (viewMode === 'story' || viewMode === 'docs'),
|
|
14
|
+
render: Tool
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Register Quick Search button
|
|
18
|
+
addons.add(SEARCH_TOOL_ID, {
|
|
19
|
+
type: types.TOOL,
|
|
20
|
+
title: 'Quick Search',
|
|
21
|
+
match: ({ viewMode, tabId }) => !tabId && (viewMode === 'story' || viewMode === 'docs'),
|
|
22
|
+
render: SearchTool
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Custom Docs page styling - hide all examples, show only description */
|
|
2
|
+
|
|
3
|
+
/* Hide ALL story previews and interactive elements in Docs */
|
|
4
|
+
.docs-story,
|
|
5
|
+
.sbdocs-preview,
|
|
6
|
+
.sb-unstyled,
|
|
7
|
+
.docblock-canvas-wrapper,
|
|
8
|
+
.docblock-canvas,
|
|
9
|
+
.docblock-argstable-wrapper,
|
|
10
|
+
.docblock-argstable,
|
|
11
|
+
.docblock-source-wrapper,
|
|
12
|
+
.docblock-source,
|
|
13
|
+
.sbdocs-preview-wrapper,
|
|
14
|
+
.sb-story,
|
|
15
|
+
[id*="story--"],
|
|
16
|
+
[class*="story-wrapper"] {
|
|
17
|
+
display: none !important;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Show only the description block */
|
|
21
|
+
.sbdocs-wrapper {
|
|
22
|
+
background: var(--surface-bg);
|
|
23
|
+
color: var(--color-text-primary);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sbdocs .sbdocs-title,
|
|
27
|
+
.sbdocs .sbdocs-subtitle {
|
|
28
|
+
color: var(--color-text-primary);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.sbdocs-description,
|
|
32
|
+
.sbdocs .sbdocs-p {
|
|
33
|
+
color: var(--color-text-secondary);
|
|
34
|
+
line-height: 1.6;
|
|
35
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* HTML Source Preview Styles */
|
|
2
|
+
|
|
3
|
+
.html-source-section {
|
|
4
|
+
border-top: 1px solid var(--color-border);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.html-source-details {
|
|
8
|
+
margin: var(--spacing-4, 1rem);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.html-source-summary {
|
|
12
|
+
padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
user-select: none;
|
|
16
|
+
display: flex;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
align-items: center;
|
|
19
|
+
border-radius: var(--radius-md, 4px);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.html-source-title {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: var(--spacing-2, 0.5rem);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.html-source-icon {
|
|
29
|
+
font-size: 1.25rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.html-source-content {
|
|
33
|
+
padding: var(--spacing-4, 1rem);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.html-source-pre {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: var(--spacing-4, 1rem);
|
|
39
|
+
border-radius: var(--radius-md, 4px);
|
|
40
|
+
overflow-x: auto;
|
|
41
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
|
42
|
+
font-size: 0.875rem;
|
|
43
|
+
line-height: 1.6;
|
|
44
|
+
tab-size: 2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Syntax highlighting colors - work in both light and dark modes */
|
|
48
|
+
:root {
|
|
49
|
+
--code-bg: #282c34;
|
|
50
|
+
--code-text: #abb2bf;
|
|
51
|
+
--code-tag: #e06c75;
|
|
52
|
+
--code-attr: #d19a66;
|
|
53
|
+
--code-value: #98c379;
|
|
54
|
+
--code-comment: #5c6370;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[data-theme="dark"] {
|
|
58
|
+
--code-bg: #1e1e1e;
|
|
59
|
+
--code-text: #d4d4d4;
|
|
60
|
+
--code-tag: #569cd6;
|
|
61
|
+
--code-attr: #9cdcfe;
|
|
62
|
+
--code-value: #ce9178;
|
|
63
|
+
--code-comment: #6a9955;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.html-source-pre {
|
|
67
|
+
background: var(--code-bg);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.html-source-code {
|
|
71
|
+
color: var(--code-text);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.html-token-tag {
|
|
75
|
+
color: var(--code-tag);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.html-token-attr {
|
|
79
|
+
color: var(--code-attr);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.html-token-value {
|
|
83
|
+
color: var(--code-value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.html-token-comment {
|
|
87
|
+
color: var(--code-comment);
|
|
88
|
+
font-style: italic;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pds-config-form {
|
|
92
|
+
position: relative;
|
|
93
|
+
label:has(input[type="color"]){
|
|
94
|
+
display: inline-block;
|
|
95
|
+
min-width: 7rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.designer-toolbar {
|
|
99
|
+
position: absolute;
|
|
100
|
+
top: 0;
|
|
101
|
+
right: 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Preview and Copy Utility for Storybook
|
|
3
|
+
* Provides HTML source viewing and copy functionality for all stories
|
|
4
|
+
* Uses Storybook's built-in source code display in Docs mode
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { render as litRender } from 'lit';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format HTML string with proper indentation
|
|
11
|
+
*/
|
|
12
|
+
export function formatHTML(html) {
|
|
13
|
+
if (!html) return '';
|
|
14
|
+
|
|
15
|
+
let formatted = '';
|
|
16
|
+
let indent = 0;
|
|
17
|
+
const tab = ' ';
|
|
18
|
+
|
|
19
|
+
const tokens = html.split(/(<[^>]+>)/g).filter(Boolean);
|
|
20
|
+
|
|
21
|
+
tokens.forEach((token) => {
|
|
22
|
+
if (token.startsWith('</')) {
|
|
23
|
+
// Closing tag
|
|
24
|
+
indent = Math.max(0, indent - 1);
|
|
25
|
+
formatted += '\n' + tab.repeat(indent) + token;
|
|
26
|
+
} else if (token.startsWith('<')) {
|
|
27
|
+
// Opening tag
|
|
28
|
+
const isSelfClosing = token.endsWith('/>') || token.match(/<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)/);
|
|
29
|
+
formatted += '\n' + tab.repeat(indent) + token;
|
|
30
|
+
if (!isSelfClosing) {
|
|
31
|
+
indent++;
|
|
32
|
+
}
|
|
33
|
+
} else if (token.trim()) {
|
|
34
|
+
// Text content
|
|
35
|
+
formatted += token.trim();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return formatted.trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract HTML from a rendered story
|
|
44
|
+
*/
|
|
45
|
+
export function extractHTML(element) {
|
|
46
|
+
if (!element) return '';
|
|
47
|
+
|
|
48
|
+
// Clone the element to avoid modifying the original
|
|
49
|
+
const clone = element.cloneNode(true);
|
|
50
|
+
|
|
51
|
+
// Clean up any Storybook-specific attributes or classes
|
|
52
|
+
const cleanElement = (el) => {
|
|
53
|
+
if (el.removeAttribute) {
|
|
54
|
+
el.removeAttribute('data-story-id');
|
|
55
|
+
el.removeAttribute('data-view-mode');
|
|
56
|
+
}
|
|
57
|
+
if (el.children) {
|
|
58
|
+
Array.from(el.children).forEach(cleanElement);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
cleanElement(clone);
|
|
63
|
+
return clone.innerHTML || '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Transform Lit template result to HTML string
|
|
68
|
+
*/
|
|
69
|
+
export async function litToHTML(templateResult) {
|
|
70
|
+
if (!templateResult || !templateResult._$litType$) {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const temp = document.createElement('div');
|
|
75
|
+
litRender(templateResult, temp);
|
|
76
|
+
|
|
77
|
+
// Wait for any custom elements to upgrade
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
79
|
+
|
|
80
|
+
return formatHTML(temp.innerHTML);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Global decorator that provides HTML source code display
|
|
85
|
+
*/
|
|
86
|
+
export const withHTMLSource = (storyFn, context) => {
|
|
87
|
+
const story = storyFn();
|
|
88
|
+
|
|
89
|
+
// For docs mode, Storybook will automatically show source
|
|
90
|
+
// We just need to ensure the parameters are set
|
|
91
|
+
if (context.viewMode === 'docs') {
|
|
92
|
+
return story;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For canvas/story mode, we'll add a source viewer below the story
|
|
96
|
+
const container = document.createElement('div');
|
|
97
|
+
container.style.cssText = 'display: flex; flex-direction: column; gap: 1rem; width: 100%; height: 100%;';
|
|
98
|
+
|
|
99
|
+
// Story container
|
|
100
|
+
const storyContainer = document.createElement('div');
|
|
101
|
+
storyContainer.id = 'story-preview';
|
|
102
|
+
storyContainer.style.cssText = 'flex: 1; overflow: auto;';
|
|
103
|
+
|
|
104
|
+
// Source panel with copy button
|
|
105
|
+
const sourceSection = document.createElement('div');
|
|
106
|
+
sourceSection.className = 'html-source-section';
|
|
107
|
+
sourceSection.innerHTML = `
|
|
108
|
+
<details class="html-source-details surface-subtle">
|
|
109
|
+
<summary class="html-source-summary surface-elevated">
|
|
110
|
+
<span class="html-source-title">
|
|
111
|
+
<span class="html-source-icon">📝</span>
|
|
112
|
+
<span>View HTML Source</span>
|
|
113
|
+
</span>
|
|
114
|
+
<button id="copy-source-btn" class="btn-outline">
|
|
115
|
+
📋 Copy HTML
|
|
116
|
+
</button>
|
|
117
|
+
</summary>
|
|
118
|
+
<div class="html-source-content">
|
|
119
|
+
<pre class="html-source-pre"><code class="html-source-code"></code></pre>
|
|
120
|
+
</div>
|
|
121
|
+
</details>
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
container.appendChild(storyContainer);
|
|
125
|
+
container.appendChild(sourceSection);
|
|
126
|
+
|
|
127
|
+
// Render the story
|
|
128
|
+
setTimeout(async () => {
|
|
129
|
+
if (story && story._$litType$) {
|
|
130
|
+
litRender(story, storyContainer);
|
|
131
|
+
} else if (story instanceof HTMLElement) {
|
|
132
|
+
storyContainer.appendChild(story);
|
|
133
|
+
} else if (typeof story === 'string') {
|
|
134
|
+
storyContainer.innerHTML = story;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Extract and display HTML
|
|
138
|
+
setTimeout(async () => {
|
|
139
|
+
let html = '';
|
|
140
|
+
|
|
141
|
+
if (story && story._$litType$) {
|
|
142
|
+
html = await litToHTML(story);
|
|
143
|
+
} else {
|
|
144
|
+
html = formatHTML(extractHTML(storyContainer));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const codeEl = sourceSection.querySelector('.html-source-code');
|
|
148
|
+
if (codeEl && html) {
|
|
149
|
+
// Simple syntax highlighter that properly tokenizes HTML
|
|
150
|
+
const highlightHTML = (code) => {
|
|
151
|
+
let result = '';
|
|
152
|
+
let i = 0;
|
|
153
|
+
|
|
154
|
+
while (i < code.length) {
|
|
155
|
+
// Handle HTML comments
|
|
156
|
+
if (code.substr(i, 4) === '<!--') {
|
|
157
|
+
const end = code.indexOf('-->', i);
|
|
158
|
+
if (end !== -1) {
|
|
159
|
+
const comment = code.substring(i, end + 3);
|
|
160
|
+
result += `<span class="html-token-comment">${escapeHtml(comment)}</span>`;
|
|
161
|
+
i = end + 3;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Handle tags
|
|
167
|
+
if (code[i] === '<') {
|
|
168
|
+
const tagEnd = code.indexOf('>', i);
|
|
169
|
+
if (tagEnd !== -1) {
|
|
170
|
+
const tagContent = code.substring(i + 1, tagEnd);
|
|
171
|
+
result += '<';
|
|
172
|
+
|
|
173
|
+
// Check if it's a closing tag
|
|
174
|
+
if (tagContent[0] === '/') {
|
|
175
|
+
result += '/';
|
|
176
|
+
const tagName = tagContent.substring(1).split(/[\s>]/)[0];
|
|
177
|
+
result += `<span class="html-token-tag">${escapeHtml(tagName)}</span>`;
|
|
178
|
+
} else {
|
|
179
|
+
// Parse tag name and attributes
|
|
180
|
+
const parts = tagContent.match(/^([\w-]+)([\s\S]*?)(\/?)?$/);
|
|
181
|
+
if (parts) {
|
|
182
|
+
const [, tagName, attrsStr, slash] = parts;
|
|
183
|
+
result += `<span class="html-token-tag">${escapeHtml(tagName)}</span>`;
|
|
184
|
+
|
|
185
|
+
// Parse attributes
|
|
186
|
+
if (attrsStr.trim()) {
|
|
187
|
+
const attrRegex = /([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
|
|
188
|
+
let match;
|
|
189
|
+
let lastIndex = 0;
|
|
190
|
+
|
|
191
|
+
while ((match = attrRegex.exec(attrsStr)) !== null) {
|
|
192
|
+
// Add whitespace before attribute
|
|
193
|
+
result += escapeHtml(attrsStr.substring(lastIndex, match.index));
|
|
194
|
+
|
|
195
|
+
const [fullMatch, attrName, doubleQuoted, singleQuoted, unquoted] = match;
|
|
196
|
+
result += `<span class="html-token-attr">${escapeHtml(attrName)}</span>`;
|
|
197
|
+
|
|
198
|
+
if (doubleQuoted !== undefined) {
|
|
199
|
+
result += `=<span class="html-token-value">"${escapeHtml(doubleQuoted)}"</span>`;
|
|
200
|
+
} else if (singleQuoted !== undefined) {
|
|
201
|
+
result += `=<span class="html-token-value">'${escapeHtml(singleQuoted)}'</span>`;
|
|
202
|
+
} else if (unquoted !== undefined) {
|
|
203
|
+
result += `=<span class="html-token-value">${escapeHtml(unquoted)}</span>`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
lastIndex = match.index + fullMatch.length;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
result += escapeHtml(attrsStr.substring(lastIndex));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (slash) result += '/';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
result += '>';
|
|
217
|
+
i = tagEnd + 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Regular text
|
|
223
|
+
result += escapeHtml(code[i]);
|
|
224
|
+
i++;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const escapeHtml = (text) => {
|
|
231
|
+
return text
|
|
232
|
+
.replace(/&/g, '&')
|
|
233
|
+
.replace(/</g, '<')
|
|
234
|
+
.replace(/>/g, '>');
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
codeEl.innerHTML = highlightHTML(html);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Setup copy button
|
|
241
|
+
const copyBtn = sourceSection.querySelector('#copy-source-btn');
|
|
242
|
+
if (copyBtn && html) {
|
|
243
|
+
copyBtn.addEventListener('click', async (e) => {
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
e.stopPropagation();
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await navigator.clipboard.writeText(html);
|
|
249
|
+
const originalClass = copyBtn.className;
|
|
250
|
+
copyBtn.textContent = '✅ Copied!';
|
|
251
|
+
copyBtn.className = 'btn-primary';
|
|
252
|
+
|
|
253
|
+
setTimeout(() => {
|
|
254
|
+
copyBtn.textContent = '📋 Copy HTML';
|
|
255
|
+
copyBtn.className = originalClass;
|
|
256
|
+
}, 2000);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error('Copy failed:', err);
|
|
259
|
+
const originalText = copyBtn.textContent;
|
|
260
|
+
copyBtn.textContent = '❌ Failed';
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
copyBtn.textContent = originalText;
|
|
263
|
+
}, 2000);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}, 100);
|
|
268
|
+
}, 0);
|
|
269
|
+
|
|
270
|
+
return container;
|
|
271
|
+
};
|