@fragments-sdk/cli 0.9.0 → 0.10.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/dist/bin.d.ts +1 -0
- package/dist/bin.js +502 -84
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CJEGT3WD.js → chunk-566BNPQZ.js} +21 -6
- package/dist/chunk-566BNPQZ.js.map +1 -0
- package/dist/{chunk-WI6SLMSO.js → chunk-CAMXG5HJ.js} +5 -5
- package/dist/chunk-D2CDBRNU.js +2 -0
- package/dist/{chunk-YMPGYEWK.js → chunk-D5PYOXEI.js} +2 -2
- package/dist/{chunk-NGIMCIK2.js → chunk-OQO55NKV.js} +405 -34
- package/dist/chunk-OQO55NKV.js.map +1 -0
- package/dist/{chunk-TOIE7VXF.js → chunk-PW7QTQA6.js} +2 -2
- package/dist/{chunk-AWYCDRPG.js → chunk-WXSR2II7.js} +2 -2
- package/dist/chunk-WXSR2II7.js.map +1 -0
- package/dist/{chunk-2JIKCJX3.js → chunk-ZDA3PLQ6.js} +17 -14
- package/dist/chunk-ZDA3PLQ6.js.map +1 -0
- package/dist/core/index.d.ts +1 -2092
- package/dist/core/index.js +26 -21
- package/dist/{discovery-Z4RDDFVR.js → discovery-NEOY4MPN.js} +3 -3
- package/dist/generate-BGKTKO6E.js +459 -0
- package/dist/generate-BGKTKO6E.js.map +1 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/{init-KSAAS7X3.js → init-Q53R5Q2T.js} +66 -76
- package/dist/init-Q53R5Q2T.js.map +1 -0
- package/dist/mcp-bin.js +5 -7
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-OQU7M4GH.js +14 -0
- package/dist/scan-generate-T5QNUG7N.js +691 -0
- package/dist/scan-generate-T5QNUG7N.js.map +1 -0
- package/dist/{service-A5GIGGGK.js → service-TQYWY65E.js} +4 -5
- package/dist/{static-viewer-NSODM5VX.js → static-viewer-NUBFPKWH.js} +4 -5
- package/dist/static-viewer-NUBFPKWH.js.map +1 -0
- package/dist/{test-RPWZAYSJ.js → test-2CSOSS3B.js} +4 -5
- package/dist/{test-RPWZAYSJ.js.map → test-2CSOSS3B.js.map} +1 -1
- package/dist/{tokens-NIXSZRX7.js → tokens-DXEGYTOJ.js} +6 -7
- package/dist/{tokens-NIXSZRX7.js.map → tokens-DXEGYTOJ.js.map} +1 -1
- package/dist/{viewer-SBTJDMP7.js → viewer-DBEPYM3G.js} +245 -23
- package/dist/viewer-DBEPYM3G.js.map +1 -0
- package/package.json +2 -1
- package/src/bin.ts +33 -1
- package/src/build.ts +13 -3
- package/src/commands/__tests__/scan-generate.test.ts +308 -0
- package/src/commands/build.ts +16 -2
- package/src/commands/generate.ts +383 -68
- package/src/commands/init.ts +81 -56
- package/src/commands/perf.ts +1 -1
- package/src/commands/scan-generate.ts +1013 -0
- package/src/commands/setup.ts +499 -0
- package/src/core/auto-props.ts +1 -1
- package/src/core/bundle-measurer.ts +2 -2
- package/src/core/config.ts +16 -4
- package/src/core/discovery.ts +2 -2
- package/src/core/generators/context.ts +1 -1
- package/src/core/generators/registry.ts +3 -3
- package/src/core/generators/typescript-extractor.ts +11 -1
- package/src/core/graph-extractor.ts +1 -1
- package/src/core/index.ts +3 -190
- package/src/core/loader.ts +2 -2
- package/src/core/parser.ts +1 -1
- package/src/core/previewLoader.ts +1 -1
- package/src/index.ts +2 -2
- package/src/migrate/converter.ts +9 -1
- package/src/migrate/parser.ts +2 -0
- package/src/migrate/types.ts +2 -0
- package/src/service/snippet-validation.test.ts +1 -1
- package/src/service/snippet-validation.ts +2 -2
- package/src/setup.ts +69 -24
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -10
- package/src/viewer/components/AccessibilityPanel.tsx +305 -312
- package/src/viewer/components/ActionsPanel.tsx +31 -29
- package/src/viewer/components/AllVariantsPreview.tsx +78 -0
- package/src/viewer/components/App.tsx +187 -740
- package/src/viewer/components/BottomPanel.tsx +228 -132
- package/src/viewer/components/CodePanel.tsx +1 -1
- package/src/viewer/components/CommandPalette.tsx +7 -10
- package/src/viewer/components/ComponentDocView.tsx +164 -0
- package/src/viewer/components/ComponentGraph.tsx +111 -142
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
- package/src/viewer/components/FigmaEmbed.tsx +20 -18
- package/src/viewer/components/FragmentEditor.tsx +92 -115
- package/src/viewer/components/HeaderSearch.tsx +24 -0
- package/src/viewer/components/HealthDashboard.tsx +16 -2
- package/src/viewer/components/Icons.tsx +9 -0
- package/src/viewer/components/InteractionsPanel.tsx +101 -117
- package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
- package/src/viewer/components/LandingPage.tsx +3 -3
- package/src/viewer/components/LeftSidebar.tsx +141 -63
- package/src/viewer/components/LoadErrorMessage.tsx +102 -0
- package/src/viewer/components/MultiViewportPreview.tsx +61 -142
- package/src/viewer/components/NoVariantsMessage.tsx +59 -0
- package/src/viewer/components/PanelShell.tsx +161 -0
- package/src/viewer/components/PerformancePanel.tsx +31 -28
- package/src/viewer/components/PreviewArea.tsx +1 -1
- package/src/viewer/components/PreviewAside.tsx +168 -0
- package/src/viewer/components/PreviewFrameHost.tsx +3 -3
- package/src/viewer/components/PropsEditor.tsx +70 -156
- package/src/viewer/components/ResizablePanel.tsx +103 -263
- package/src/viewer/components/RightSidebar.tsx +3 -9
- package/src/viewer/components/SkeletonLoader.tsx +13 -13
- package/src/viewer/components/TokenStylePanel.tsx +182 -209
- package/src/viewer/components/TopToolbar.tsx +159 -0
- package/src/viewer/components/VariantMatrix.tsx +42 -86
- package/src/viewer/components/VariantTabs.tsx +3 -3
- package/src/viewer/components/ViewerHeader.tsx +69 -0
- package/src/viewer/components/WebMCPDevTools.tsx +17 -23
- package/src/viewer/components/viewer-utils.ts +16 -0
- package/src/viewer/entry.tsx +5 -0
- package/src/viewer/hooks/useAppState.ts +27 -4
- package/src/viewer/hooks/usePreviewBridge.ts +2 -2
- package/src/viewer/preview-frame.html +6 -12
- package/src/viewer/server.ts +169 -2
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
- package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
- package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
- package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +114 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
- package/src/viewer/vendor/shared/src/index.ts +8 -0
- package/src/viewer/vendor/shared/src/types.ts +12 -0
- package/src/viewer/vite-plugin.ts +109 -4
- package/dist/chunk-2JIKCJX3.js.map +0 -1
- package/dist/chunk-AWYCDRPG.js.map +0 -1
- package/dist/chunk-CJEGT3WD.js.map +0 -1
- package/dist/chunk-EKLMXTWU.js +0 -80
- package/dist/chunk-EKLMXTWU.js.map +0 -1
- package/dist/chunk-GOVI6COW.js +0 -195
- package/dist/chunk-GOVI6COW.js.map +0 -1
- package/dist/chunk-NGIMCIK2.js.map +0 -1
- package/dist/defineFragment-D0UTve-I.d.ts +0 -665
- package/dist/generate-35OIMW4Y.js +0 -252
- package/dist/generate-35OIMW4Y.js.map +0 -1
- package/dist/init-KSAAS7X3.js.map +0 -1
- package/dist/scan-65RH3QMM.js +0 -15
- package/dist/viewer-SBTJDMP7.js.map +0 -1
- package/src/core/__tests__/preview-runtime.test.tsx +0 -111
- package/src/core/composition.test.ts +0 -262
- package/src/core/composition.ts +0 -318
- package/src/core/constants.ts +0 -114
- package/src/core/context.ts +0 -2
- package/src/core/defineFragment.ts +0 -141
- package/src/core/figma.ts +0 -263
- package/src/core/fragment-types.ts +0 -214
- package/src/core/performance-presets.ts +0 -142
- package/src/core/preview-runtime.tsx +0 -144
- package/src/core/schema.ts +0 -221
- package/src/core/storyAdapter.test.ts +0 -571
- package/src/core/storyAdapter.ts +0 -761
- package/src/core/storybook-csf.ts +0 -11
- package/src/core/token-parser.ts +0 -321
- package/src/core/token-types.ts +0 -287
- package/src/core/types.ts +0 -762
- /package/dist/{chunk-WI6SLMSO.js.map → chunk-CAMXG5HJ.js.map} +0 -0
- /package/dist/{discovery-Z4RDDFVR.js.map → chunk-D2CDBRNU.js.map} +0 -0
- /package/dist/{chunk-YMPGYEWK.js.map → chunk-D5PYOXEI.js.map} +0 -0
- /package/dist/{chunk-TOIE7VXF.js.map → chunk-PW7QTQA6.js.map} +0 -0
- /package/dist/{scan-65RH3QMM.js.map → discovery-NEOY4MPN.js.map} +0 -0
- /package/dist/{service-A5GIGGGK.js.map → scan-OQU7M4GH.js.map} +0 -0
- /package/dist/{static-viewer-NSODM5VX.js.map → service-TQYWY65E.js.map} +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
margin: 1.5rem 0;
|
|
3
|
+
overflow-x: auto;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.empty {
|
|
7
|
+
color: var(--fui-text-tertiary);
|
|
8
|
+
font-style: italic;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.table {
|
|
12
|
+
width: 100%;
|
|
13
|
+
border-collapse: collapse;
|
|
14
|
+
font-size: 0.875rem;
|
|
15
|
+
|
|
16
|
+
th, td {
|
|
17
|
+
text-align: left;
|
|
18
|
+
padding: 0.75rem;
|
|
19
|
+
border-bottom: 1px solid var(--fui-border);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
th {
|
|
23
|
+
font-weight: var(--fui-font-weight-semibold);
|
|
24
|
+
color: var(--fui-text-secondary);
|
|
25
|
+
background-color: var(--fui-bg-secondary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
td {
|
|
29
|
+
vertical-align: top;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.propName {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 0.5rem;
|
|
37
|
+
|
|
38
|
+
code {
|
|
39
|
+
font-weight: var(--fui-font-weight-semibold);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.type {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 0.25rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.values {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-wrap: wrap;
|
|
52
|
+
gap: 0.25rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.value {
|
|
56
|
+
font-size: 0.75rem;
|
|
57
|
+
background-color: var(--fui-bg-tertiary);
|
|
58
|
+
padding: 0.125rem 0.375rem;
|
|
59
|
+
border-radius: var(--fui-radius-sm);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.default {
|
|
63
|
+
color: var(--fui-color-accent);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.noDefault {
|
|
67
|
+
color: var(--fui-text-tertiary);
|
|
68
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Badge } from '@fragments-sdk/ui';
|
|
4
|
+
import type { DocProp } from './types';
|
|
5
|
+
import styles from './PropsTable.module.scss';
|
|
6
|
+
|
|
7
|
+
/** Detect auto-generated descriptions like "variant prop" */
|
|
8
|
+
function isGenericPropDescription(name: string, description: string): boolean {
|
|
9
|
+
const lower = description.toLowerCase().trim();
|
|
10
|
+
return lower === `${name.toLowerCase()} prop` || lower === `${name.toLowerCase()} property`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface PropsTableProps {
|
|
14
|
+
props: Record<string, DocProp>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function PropsTable({ props }: PropsTableProps) {
|
|
18
|
+
const propEntries = Object.entries(props);
|
|
19
|
+
|
|
20
|
+
if (propEntries.length === 0) {
|
|
21
|
+
return <p className={styles.empty}>No props documented</p>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className={styles.container}>
|
|
26
|
+
<table className={styles.table}>
|
|
27
|
+
<thead>
|
|
28
|
+
<tr>
|
|
29
|
+
<th>Prop</th>
|
|
30
|
+
<th>Type</th>
|
|
31
|
+
<th>Default</th>
|
|
32
|
+
<th>Description</th>
|
|
33
|
+
</tr>
|
|
34
|
+
</thead>
|
|
35
|
+
<tbody>
|
|
36
|
+
{propEntries.map(([name, prop]) => (
|
|
37
|
+
<tr key={name}>
|
|
38
|
+
<td>
|
|
39
|
+
<span className={styles.propName}>
|
|
40
|
+
<code>{name}</code>
|
|
41
|
+
{prop.required && (
|
|
42
|
+
<Badge size="sm" variant="error">
|
|
43
|
+
Required
|
|
44
|
+
</Badge>
|
|
45
|
+
)}
|
|
46
|
+
</span>
|
|
47
|
+
</td>
|
|
48
|
+
<td>
|
|
49
|
+
<span className={styles.type}>
|
|
50
|
+
<code>{prop.type}</code>
|
|
51
|
+
{prop.values && (
|
|
52
|
+
<span className={styles.values}>
|
|
53
|
+
{prop.values.map((v) => (
|
|
54
|
+
<code key={v} className={styles.value}>
|
|
55
|
+
{v}
|
|
56
|
+
</code>
|
|
57
|
+
))}
|
|
58
|
+
</span>
|
|
59
|
+
)}
|
|
60
|
+
</span>
|
|
61
|
+
</td>
|
|
62
|
+
<td>
|
|
63
|
+
{prop.default !== undefined ? (
|
|
64
|
+
<code className={styles.default}>{String(prop.default)}</code>
|
|
65
|
+
) : (
|
|
66
|
+
<span className={styles.noDefault}>Not set</span>
|
|
67
|
+
)}
|
|
68
|
+
</td>
|
|
69
|
+
<td>{prop.description && !isGenericPropDescription(name, prop.description) ? prop.description : '—'}</td>
|
|
70
|
+
</tr>
|
|
71
|
+
))}
|
|
72
|
+
</tbody>
|
|
73
|
+
</table>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Shared variant preview card — used by docs and CLI viewer
|
|
2
|
+
.card {
|
|
3
|
+
border: 1px solid var(--fui-border);
|
|
4
|
+
border-radius: var(--fui-radius-lg);
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
background-color: var(--fui-bg-primary);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.highlighted {
|
|
10
|
+
border-color: var(--fui-color-accent);
|
|
11
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--fui-color-accent) 40%, transparent);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.header {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: var(--fui-space-4);
|
|
19
|
+
padding: var(--fui-space-2) var(--fui-space-4);
|
|
20
|
+
background-color: var(--fui-bg-primary);
|
|
21
|
+
border-bottom: 1px solid var(--fui-border);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.headerContent {
|
|
25
|
+
flex: 1;
|
|
26
|
+
min-width: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.title {
|
|
30
|
+
font-size: var(--fui-font-size-sm);
|
|
31
|
+
font-weight: var(--fui-font-weight-semibold);
|
|
32
|
+
color: var(--fui-text-primary);
|
|
33
|
+
margin: 0;
|
|
34
|
+
line-height: 1.4;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.description {
|
|
38
|
+
font-size: var(--fui-font-size-xs);
|
|
39
|
+
color: var(--fui-text-tertiary);
|
|
40
|
+
margin: var(--fui-space-0-5) 0 0 0;
|
|
41
|
+
line-height: 1.4;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Preview area with subtle dotted grid pattern
|
|
45
|
+
.previewPanel {
|
|
46
|
+
position: relative;
|
|
47
|
+
padding: var(--fui-space-4);
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
background-color: var(--fui-bg-primary);
|
|
53
|
+
|
|
54
|
+
// Subtle dotted grid pattern
|
|
55
|
+
background-image: radial-gradient(circle, var(--fui-border) 1px, transparent 1px);
|
|
56
|
+
background-size: 24px 24px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Code panel
|
|
60
|
+
.codePanel {
|
|
61
|
+
background-color: var(--fui-bg-tertiary);
|
|
62
|
+
padding: 0 !important; // Override Tabs panel padding
|
|
63
|
+
|
|
64
|
+
// Override CodeBlock styles for embedded use
|
|
65
|
+
:global([class*="CodeBlock_container"]) {
|
|
66
|
+
border: none;
|
|
67
|
+
border-radius: 0;
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:global([class*="CodeBlock_wrapper"]) {
|
|
72
|
+
border-top-left-radius: 0;
|
|
73
|
+
border-top-right-radius: 0;
|
|
74
|
+
border: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
:global([class*="CodeBlock_title"]) {
|
|
78
|
+
display: none;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.placeholder {
|
|
83
|
+
color: var(--fui-text-tertiary);
|
|
84
|
+
font-style: italic;
|
|
85
|
+
font-size: var(--fui-font-size-sm);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.headerActions {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: var(--fui-space-2);
|
|
92
|
+
flex-shrink: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Override Tabs styling for preview cards
|
|
96
|
+
.card {
|
|
97
|
+
:global([class*="Tabs_root"]) {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Tabs list sits inline with header
|
|
103
|
+
:global([class*="Tabs_list"]) {
|
|
104
|
+
padding: 0;
|
|
105
|
+
border-bottom: none;
|
|
106
|
+
background-color: transparent;
|
|
107
|
+
flex-shrink: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:global([class*="Tabs_listPills"]) {
|
|
111
|
+
margin: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { CodeBlock, Tabs } from '@fragments-sdk/ui';
|
|
5
|
+
import styles from './VariantPreviewCard.module.scss';
|
|
6
|
+
|
|
7
|
+
/** Detect auto-generated descriptions like "Alternative Layout variant" or "Default ActionMenu" */
|
|
8
|
+
function isGenericVariantDescription(name: string, description: string): boolean {
|
|
9
|
+
const lower = description.toLowerCase().trim();
|
|
10
|
+
const nameLower = name.toLowerCase().trim();
|
|
11
|
+
return (
|
|
12
|
+
lower === `${nameLower} variant` ||
|
|
13
|
+
lower === `default ${nameLower}` ||
|
|
14
|
+
lower === nameLower ||
|
|
15
|
+
// "Default SomeComponent" — "Default" followed by a PascalCase component name
|
|
16
|
+
/^default [a-z][a-z0-9]*$/i.test(description.trim())
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface VariantPreviewCardProps {
|
|
21
|
+
name: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
code?: string;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
isLoading?: boolean;
|
|
26
|
+
error?: string | null;
|
|
27
|
+
defaultTab?: 'preview' | 'code';
|
|
28
|
+
highlight?: boolean;
|
|
29
|
+
id?: string;
|
|
30
|
+
headerActions?: ReactNode;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function VariantPreviewCard({
|
|
35
|
+
name,
|
|
36
|
+
description,
|
|
37
|
+
code,
|
|
38
|
+
children,
|
|
39
|
+
isLoading,
|
|
40
|
+
error,
|
|
41
|
+
defaultTab = 'preview',
|
|
42
|
+
highlight,
|
|
43
|
+
id,
|
|
44
|
+
headerActions,
|
|
45
|
+
className,
|
|
46
|
+
}: VariantPreviewCardProps) {
|
|
47
|
+
const cardClassName = [
|
|
48
|
+
styles.card,
|
|
49
|
+
highlight && styles.highlighted,
|
|
50
|
+
className,
|
|
51
|
+
]
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.join(' ');
|
|
54
|
+
|
|
55
|
+
const hasPreview = !!children;
|
|
56
|
+
const hasCode = typeof code === 'string' && code.length > 0;
|
|
57
|
+
|
|
58
|
+
// No preview, code only — render without tabs
|
|
59
|
+
if (!hasPreview && hasCode) {
|
|
60
|
+
return (
|
|
61
|
+
<div className={cardClassName} id={id}>
|
|
62
|
+
<div className={styles.header}>
|
|
63
|
+
<div className={styles.headerContent}>
|
|
64
|
+
<h3 className={styles.title}>{name}</h3>
|
|
65
|
+
{description && !isGenericVariantDescription(name, description) && <p className={styles.description}>{description}</p>}
|
|
66
|
+
</div>
|
|
67
|
+
{headerActions && <div className={styles.headerActions}>{headerActions}</div>}
|
|
68
|
+
</div>
|
|
69
|
+
<div className={styles.codePanel}>
|
|
70
|
+
<CodeBlock code={code} language="tsx" />
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// No code — render preview only, no tabs
|
|
77
|
+
if (!hasCode) {
|
|
78
|
+
return (
|
|
79
|
+
<div className={cardClassName} id={id}>
|
|
80
|
+
<div className={styles.header}>
|
|
81
|
+
<div className={styles.headerContent}>
|
|
82
|
+
<h3 className={styles.title}>{name}</h3>
|
|
83
|
+
{description && !isGenericVariantDescription(name, description) && <p className={styles.description}>{description}</p>}
|
|
84
|
+
</div>
|
|
85
|
+
{headerActions && <div className={styles.headerActions}>{headerActions}</div>}
|
|
86
|
+
</div>
|
|
87
|
+
<div className={styles.previewPanel}>
|
|
88
|
+
{isLoading ? (
|
|
89
|
+
<div className={styles.placeholder}>Loading preview...</div>
|
|
90
|
+
) : error ? (
|
|
91
|
+
<div className={styles.placeholder}>Preview error: {error}</div>
|
|
92
|
+
) : (
|
|
93
|
+
children
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Both preview and code — render with tabs
|
|
101
|
+
return (
|
|
102
|
+
<div className={cardClassName} id={id}>
|
|
103
|
+
<Tabs defaultValue={defaultTab}>
|
|
104
|
+
<div className={styles.header}>
|
|
105
|
+
<div className={styles.headerContent}>
|
|
106
|
+
<h3 className={styles.title}>{name}</h3>
|
|
107
|
+
{description && !isGenericVariantDescription(name, description) && <p className={styles.description}>{description}</p>}
|
|
108
|
+
</div>
|
|
109
|
+
<div className={styles.headerActions}>
|
|
110
|
+
{headerActions}
|
|
111
|
+
<Tabs.List variant="pills">
|
|
112
|
+
<Tabs.Tab value="preview">Preview</Tabs.Tab>
|
|
113
|
+
<Tabs.Tab value="code">Code</Tabs.Tab>
|
|
114
|
+
</Tabs.List>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<Tabs.Panel value="preview" className={styles.previewPanel}>
|
|
119
|
+
{isLoading ? (
|
|
120
|
+
<div className={styles.placeholder}>Loading preview...</div>
|
|
121
|
+
) : error ? (
|
|
122
|
+
<div className={styles.placeholder}>Preview error: {error}</div>
|
|
123
|
+
) : (
|
|
124
|
+
children
|
|
125
|
+
)}
|
|
126
|
+
</Tabs.Panel>
|
|
127
|
+
|
|
128
|
+
<Tabs.Panel value="code" className={styles.codePanel}>
|
|
129
|
+
<CodeBlock code={code} language="tsx" />
|
|
130
|
+
</Tabs.Panel>
|
|
131
|
+
</Tabs>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -24,3 +24,11 @@ export type {
|
|
|
24
24
|
HeaderNavDropdown,
|
|
25
25
|
} from './types';
|
|
26
26
|
export { isDropdown } from './types';
|
|
27
|
+
|
|
28
|
+
export { VariantPreviewCard } from './VariantPreviewCard';
|
|
29
|
+
export type { VariantPreviewCardProps } from './VariantPreviewCard';
|
|
30
|
+
|
|
31
|
+
export { PropsTable } from './PropsTable';
|
|
32
|
+
export { ComponentDocContent } from './ComponentDocContent';
|
|
33
|
+
export type { ComponentDocContentProps } from './ComponentDocContent';
|
|
34
|
+
export type { DocProp } from './types';
|
|
@@ -39,3 +39,15 @@ export type HeaderNavEntry = HeaderNavLink | HeaderNavDropdown;
|
|
|
39
39
|
export function isDropdown(entry: HeaderNavEntry): entry is HeaderNavDropdown {
|
|
40
40
|
return 'items' in entry;
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Prop documentation type for shared PropsTable / ComponentDocContent.
|
|
45
|
+
* Intentionally simple — consumers map their richer types into this shape.
|
|
46
|
+
*/
|
|
47
|
+
export interface DocProp {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
required?: boolean;
|
|
51
|
+
default?: unknown;
|
|
52
|
+
values?: string[];
|
|
53
|
+
}
|
|
@@ -1506,7 +1506,7 @@ export function fragmentsPlugin(options: FragmentsPluginOptions): Plugin[] {
|
|
|
1506
1506
|
// Load virtual modules
|
|
1507
1507
|
load(id) {
|
|
1508
1508
|
if (id === VIRTUAL_FRAGMENTS_RESOLVED) {
|
|
1509
|
-
return generateFragmentsModule(fragmentFiles, config, previewConfigPath);
|
|
1509
|
+
return generateFragmentsModule(fragmentFiles, config, previewConfigPath, projectRoot);
|
|
1510
1510
|
}
|
|
1511
1511
|
if (id === VIRTUAL_VIEWER_ENTRY_RESOLVED) {
|
|
1512
1512
|
return generateViewerEntry();
|
|
@@ -1636,6 +1636,19 @@ function extractComponentName(filePath: string): string {
|
|
|
1636
1636
|
* Reads the file and looks for `dependencies: [{ name: '...' }, ...]` patterns.
|
|
1637
1637
|
* Returns an array of package names, or empty array if extraction fails.
|
|
1638
1638
|
*/
|
|
1639
|
+
/**
|
|
1640
|
+
* Read the consumer project's package.json name field.
|
|
1641
|
+
* Used to show the correct import path in the docs view.
|
|
1642
|
+
*/
|
|
1643
|
+
async function readProjectPackageName(root: string): Promise<string | null> {
|
|
1644
|
+
try {
|
|
1645
|
+
const pkgJson = JSON.parse(await readFile(resolve(root, "package.json"), "utf-8"));
|
|
1646
|
+
return pkgJson.name || null;
|
|
1647
|
+
} catch {
|
|
1648
|
+
return null;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1639
1652
|
async function extractDependenciesFromSource(absolutePath: string): Promise<string[]> {
|
|
1640
1653
|
try {
|
|
1641
1654
|
const source = await readFile(absolutePath, "utf-8");
|
|
@@ -1670,7 +1683,8 @@ async function extractDependenciesFromSource(absolutePath: string): Promise<stri
|
|
|
1670
1683
|
async function generateFragmentsModule(
|
|
1671
1684
|
fragmentFiles: Array<{ absolutePath: string; relativePath: string }>,
|
|
1672
1685
|
config: FragmentsConfig,
|
|
1673
|
-
previewConfigPath: string | null
|
|
1686
|
+
previewConfigPath: string | null,
|
|
1687
|
+
projectRoot: string
|
|
1674
1688
|
): Promise<string> {
|
|
1675
1689
|
const authoredVariantCodeCache = new Map<string, Record<string, string>>();
|
|
1676
1690
|
|
|
@@ -1699,6 +1713,11 @@ async function generateFragmentsModule(
|
|
|
1699
1713
|
}
|
|
1700
1714
|
}
|
|
1701
1715
|
|
|
1716
|
+
// Compute sub-component map from story file paths (build-time)
|
|
1717
|
+
const storyOnlyFiles = fragmentFiles.filter(f => isStoryFile(f.relativePath));
|
|
1718
|
+
const { detectSubComponentPaths: _detectSubs } = await import("../core/index.js");
|
|
1719
|
+
const subComponentMap = _detectSubs(storyOnlyFiles);
|
|
1720
|
+
|
|
1702
1721
|
// Group files by base component path to identify pairs
|
|
1703
1722
|
const filesByBasePath = new Map<string, {
|
|
1704
1723
|
storyFile?: { absolutePath: string; relativePath: string };
|
|
@@ -1750,11 +1769,16 @@ async function generateFragmentsModule(
|
|
|
1750
1769
|
const fragmentSource = files.fragmentFile || primaryFile;
|
|
1751
1770
|
const dependencies = await extractDependenciesFromSource(fragmentSource.absolutePath);
|
|
1752
1771
|
|
|
1772
|
+
// Sub-component detection (only applies to story files)
|
|
1773
|
+
const parentComponent = isStory ? subComponentMap.get(primaryFile.relativePath) : undefined;
|
|
1774
|
+
|
|
1753
1775
|
return ` {
|
|
1754
1776
|
path: "${primaryFile.relativePath}",
|
|
1755
1777
|
isStory: ${isStory},
|
|
1756
1778
|
componentName: ${JSON.stringify(componentName)},
|
|
1757
1779
|
dependencies: ${JSON.stringify(dependencies)},
|
|
1780
|
+
isSubComponent: ${!!parentComponent},
|
|
1781
|
+
parentComponent: ${parentComponent ? JSON.stringify(parentComponent) : 'null'},
|
|
1758
1782
|
loader: () => import("${primaryFile.absolutePath}"),
|
|
1759
1783
|
metadataLoader: ${metadataPath ? `() => import("${metadataPath}")` : 'null'},
|
|
1760
1784
|
authoredVariantCode: ${JSON.stringify(authoredVariantCode)}
|
|
@@ -1782,10 +1806,16 @@ setPreviewConfig({
|
|
|
1782
1806
|
`
|
|
1783
1807
|
: "";
|
|
1784
1808
|
|
|
1809
|
+
// Serialize storybook filter config for the virtual module
|
|
1810
|
+
const storybookFilterConfig = JSON.stringify(config.storybook ?? {});
|
|
1811
|
+
|
|
1785
1812
|
return `
|
|
1786
|
-
import { storyModuleToFragment, setPreviewConfig } from "@fragments-sdk/cli/core";
|
|
1813
|
+
import { storyModuleToFragment, setPreviewConfig, checkStoryExclusion, isForceIncluded, isConfigExcluded } from "@fragments-sdk/cli/core";
|
|
1787
1814
|
${previewImport}
|
|
1788
1815
|
${previewSetup}
|
|
1816
|
+
// Storybook filter config (deep-merged with defaults at build time)
|
|
1817
|
+
const storybookFilterConfig = ${storybookFilterConfig};
|
|
1818
|
+
|
|
1789
1819
|
// Lazy fragment loaders (supports both .fragment.tsx and .stories.tsx)
|
|
1790
1820
|
const fragmentLoaders = [
|
|
1791
1821
|
${loaders}
|
|
@@ -1848,8 +1878,24 @@ function mergeAuthoredVariantCode(fragment, authoredVariantCode) {
|
|
|
1848
1878
|
return fragment;
|
|
1849
1879
|
}
|
|
1850
1880
|
|
|
1881
|
+
// Diagnostics: track exclusions for FRAGMENTS_DEBUG
|
|
1882
|
+
const exclusionLog = [];
|
|
1883
|
+
|
|
1884
|
+
/**
|
|
1885
|
+
* Try to load a paired .fragment.tsx file as fallback when a story is excluded.
|
|
1886
|
+
* Returns the fragment if available, null otherwise.
|
|
1887
|
+
*/
|
|
1888
|
+
async function tryFallbackFragment(loader) {
|
|
1889
|
+
if (!loader.metadataLoader) return null;
|
|
1890
|
+
try {
|
|
1891
|
+
const mod = await loader.metadataLoader();
|
|
1892
|
+
return mod?.default ? { path: loader.path, fragment: mod.default } : null;
|
|
1893
|
+
} catch { return null; }
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1851
1896
|
// Load all fragments (for initial render)
|
|
1852
1897
|
// Gracefully handles individual failures - one bad story won't break all fragments
|
|
1898
|
+
// Applies smart filtering for Storybook stories (SVG icons, deprecated, test stories, sub-components)
|
|
1853
1899
|
export async function loadAllFragments() {
|
|
1854
1900
|
const results = await Promise.all(
|
|
1855
1901
|
fragmentLoaders.map(async (loader) => {
|
|
@@ -1859,6 +1905,30 @@ export async function loadAllFragments() {
|
|
|
1859
1905
|
return cached ? { path: loader.path, fragment: cached } : null;
|
|
1860
1906
|
}
|
|
1861
1907
|
|
|
1908
|
+
// --- Pre-load filtering (config-level and sub-component checks) ---
|
|
1909
|
+
if (loader.isStory) {
|
|
1910
|
+
// Force-include bypasses all filters
|
|
1911
|
+
if (!isForceIncluded(loader.componentName, storybookFilterConfig)) {
|
|
1912
|
+
// Config explicit exclude
|
|
1913
|
+
if (isConfigExcluded(loader.componentName, storybookFilterConfig)) {
|
|
1914
|
+
exclusionLog.push({ component: loader.componentName, reason: 'config-excluded', detail: 'Matches storybook.exclude pattern', path: loader.path });
|
|
1915
|
+
const fallback = await tryFallbackFragment(loader);
|
|
1916
|
+
if (fallback) return fallback;
|
|
1917
|
+
loadedFragments.set(loader.path, null);
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// Sub-component check (directory-based, computed at build time)
|
|
1922
|
+
if (loader.isSubComponent && storybookFilterConfig.excludeSubComponents !== false) {
|
|
1923
|
+
exclusionLog.push({ component: loader.componentName, reason: 'sub-component', detail: 'Sub-component of ' + loader.parentComponent, path: loader.path });
|
|
1924
|
+
const fallback = await tryFallbackFragment(loader);
|
|
1925
|
+
if (fallback) return fallback;
|
|
1926
|
+
loadedFragments.set(loader.path, null);
|
|
1927
|
+
return null;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1862
1932
|
const module = await loader.loader();
|
|
1863
1933
|
|
|
1864
1934
|
// Convert story modules to fragments at runtime
|
|
@@ -1870,6 +1940,29 @@ export async function loadAllFragments() {
|
|
|
1870
1940
|
loadedFragments.set(loader.path, null);
|
|
1871
1941
|
return null;
|
|
1872
1942
|
}
|
|
1943
|
+
|
|
1944
|
+
// --- Post-load filtering (needs loaded module data) ---
|
|
1945
|
+
if (!isForceIncluded(loader.componentName, storybookFilterConfig)) {
|
|
1946
|
+
const meta = module.default || {};
|
|
1947
|
+
const exclusion = checkStoryExclusion({
|
|
1948
|
+
storybookTitle: meta.title,
|
|
1949
|
+
componentName: fragment.meta?.name || loader.componentName,
|
|
1950
|
+
componentDisplayName: meta.component?.displayName,
|
|
1951
|
+
componentFunctionName: meta.component?.name,
|
|
1952
|
+
tags: meta.tags,
|
|
1953
|
+
variantCount: fragment.variants?.length || 0,
|
|
1954
|
+
filePath: loader.path,
|
|
1955
|
+
config: storybookFilterConfig,
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
if (exclusion.excluded) {
|
|
1959
|
+
exclusionLog.push({ component: fragment.meta?.name || loader.componentName, reason: exclusion.reason, detail: exclusion.detail, path: loader.path });
|
|
1960
|
+
const fallback = await tryFallbackFragment(loader);
|
|
1961
|
+
if (fallback) return fallback;
|
|
1962
|
+
loadedFragments.set(loader.path, null);
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1873
1966
|
} else {
|
|
1874
1967
|
fragment = module.default;
|
|
1875
1968
|
}
|
|
@@ -1911,6 +2004,15 @@ export async function loadAllFragments() {
|
|
|
1911
2004
|
}
|
|
1912
2005
|
})
|
|
1913
2006
|
);
|
|
2007
|
+
|
|
2008
|
+
// Log exclusions in debug mode
|
|
2009
|
+
if (exclusionLog.length > 0) {
|
|
2010
|
+
console.log("[Fragments] Filtered " + exclusionLog.length + " component(s)");
|
|
2011
|
+
if (typeof process !== 'undefined' && process.env?.FRAGMENTS_DEBUG) {
|
|
2012
|
+
console.table(exclusionLog);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
1914
2016
|
// Filter out nulls (fragments that had no component)
|
|
1915
2017
|
return results.filter(r => r !== null);
|
|
1916
2018
|
}
|
|
@@ -1959,9 +2061,12 @@ export async function loadFragment(path) {
|
|
|
1959
2061
|
let fragments = [];
|
|
1960
2062
|
const fragmentsPromise = loadAllFragments().then(s => { fragments = s; return s; });
|
|
1961
2063
|
|
|
1962
|
-
export { fragments, fragmentsPromise };
|
|
2064
|
+
export { fragments, fragmentsPromise, exclusionLog };
|
|
1963
2065
|
export const config = ${JSON.stringify(config)};
|
|
1964
2066
|
|
|
2067
|
+
// Auto-detect consumer package name from package.json
|
|
2068
|
+
export const projectPackageName = ${JSON.stringify(await readProjectPackageName(projectRoot))};
|
|
2069
|
+
|
|
1965
2070
|
// HMR support
|
|
1966
2071
|
if (import.meta.hot) {
|
|
1967
2072
|
import.meta.hot.accept();
|