vibe-design-system 2.8.64 → 2.8.66
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/package.json
CHANGED
|
@@ -2525,7 +2525,7 @@ function writeFoundationsStories(foundations) {
|
|
|
2525
2525
|
"",
|
|
2526
2526
|
"export const Default: Story = {",
|
|
2527
2527
|
" render: () => (",
|
|
2528
|
-
" <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight:
|
|
2528
|
+
" <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
|
|
2529
2529
|
" <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\", color: \"#111\" }}>Colors</h2>",
|
|
2530
2530
|
" <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 40px\" }}>Design tokens defined in CSS variables — grouped by role</p>",
|
|
2531
2531
|
"",
|
|
@@ -3815,11 +3815,193 @@ function writeComponentSuggestionsStory(componentSuggestions) {
|
|
|
3815
3815
|
console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentSuggestions.stories.tsx")));
|
|
3816
3816
|
}
|
|
3817
3817
|
|
|
3818
|
+
/**
|
|
3819
|
+
* Determines the visual preview shape and colors for a component in the inventory.
|
|
3820
|
+
* Returns an HTML string rendered at generation time using the project's resolved colors.
|
|
3821
|
+
*/
|
|
3822
|
+
function buildInventoryPreviewHtml(compName, category, colorSwatches, tokens) {
|
|
3823
|
+
const n = compName.toLowerCase().replace(/[\s-]+/g, "");
|
|
3824
|
+
|
|
3825
|
+
// Extract colors from resolved swatches
|
|
3826
|
+
const bgSwatch = colorSwatches.find(s => /^bg-(?!gradient)/.test(s.token));
|
|
3827
|
+
const textSwatch = colorSwatches.find(s => /^text-/.test(s.token));
|
|
3828
|
+
const brdSwatch = colorSwatches.find(s => /^border-/.test(s.token));
|
|
3829
|
+
const accSwatch = colorSwatches.find(s => s.token === "bg-primary" || s.token === "border-primary" || s.token === "ring-primary");
|
|
3830
|
+
|
|
3831
|
+
// Fallback neutral palette (works on white canvas)
|
|
3832
|
+
const bg = bgSwatch?.hex || "#f3f4f6";
|
|
3833
|
+
const txt = textSwatch?.hex || "#374151";
|
|
3834
|
+
const brd = brdSwatch?.hex || "#e5e7eb";
|
|
3835
|
+
const acc = accSwatch?.hex || "#6366f1";
|
|
3836
|
+
// Lighten/darken helpers (inline, no deps)
|
|
3837
|
+
const hex2 = (h) => { // slightly lighter bg for header strips
|
|
3838
|
+
const n = parseInt(h.slice(1), 16);
|
|
3839
|
+
const r = Math.min(255, (n >> 16) + 30), g = Math.min(255, ((n >> 8) & 0xff) + 30), b = Math.min(255, (n & 0xff) + 30);
|
|
3840
|
+
return "#" + [r,g,b].map(x => x.toString(16).padStart(2,"0")).join("");
|
|
3841
|
+
};
|
|
3842
|
+
|
|
3843
|
+
// Border-radius from tokens
|
|
3844
|
+
const radTok = tokens.find(t => /^rounded/.test(t));
|
|
3845
|
+
const rad = radTok === "rounded-none" ? 0 : radTok === "rounded-sm" ? 2 : radTok === "rounded" ? 4
|
|
3846
|
+
: radTok === "rounded-md" ? 6 : radTok === "rounded-lg" ? 8 : radTok === "rounded-xl" ? 12
|
|
3847
|
+
: radTok === "rounded-2xl" ? 16 : radTok === "rounded-3xl" ? 24 : radTok === "rounded-full" ? 9999 : 4;
|
|
3848
|
+
|
|
3849
|
+
const wrap = (inner) => `<div style="width:100%;height:56px;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:0 10px;box-sizing:border-box">${inner}</div>`;
|
|
3850
|
+
|
|
3851
|
+
// ── Specific component shapes ────────────────────────────────────────────
|
|
3852
|
+
if (/^input$/.test(n) || (category === "Forms and Input" && /input/.test(n))) {
|
|
3853
|
+
return wrap(`<div style="width:100%;max-width:200px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;display:flex;align-items:center;gap:6px;box-sizing:border-box"><div style="flex:1;height:8px;background:${txt};opacity:0.3;border-radius:2px"></div></div>`);
|
|
3854
|
+
}
|
|
3855
|
+
if (/^textarea$/.test(n)) {
|
|
3856
|
+
return wrap(`<div style="width:100%;max-width:200px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;height:38px;box-sizing:border-box;display:flex;flex-direction:column;gap:4px"><div style="height:6px;background:${txt};opacity:0.3;border-radius:2px;width:80%"></div><div style="height:6px;background:${txt};opacity:0.2;border-radius:2px;width:50%"></div></div>`);
|
|
3857
|
+
}
|
|
3858
|
+
if (/^(button|submitbutton)$/.test(n) || (category === "Forms and Input" && /^button$/.test(n))) {
|
|
3859
|
+
return wrap(`<div style="display:inline-flex;align-items:center;background:${acc};color:#fff;border:1px solid ${acc};border-radius:${rad}px;padding:6px 18px;font-size:11px;font-weight:600;white-space:nowrap">${compName}</div>`);
|
|
3860
|
+
}
|
|
3861
|
+
if (/^toggle$/.test(n)) {
|
|
3862
|
+
return wrap(`<div style="display:flex;align-items:center;gap:6px"><div style="display:inline-flex;align-items:center;background:${acc};color:#fff;border-radius:${rad}px;padding:5px 12px;font-size:11px;font-weight:600">ON</div><div style="display:inline-flex;align-items:center;background:${bg};color:${txt};border:1px solid ${brd};border-radius:${rad}px;padding:5px 12px;font-size:11px">OFF</div></div>`);
|
|
3863
|
+
}
|
|
3864
|
+
if (/^(badge|chip|tag|lozenge|pill)$/.test(n) || category === "Status Indicators") {
|
|
3865
|
+
const bBg = acc + "20"; const bTxt = acc;
|
|
3866
|
+
return wrap(`<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center"><span style="background:${bBg};color:${bTxt};border:1px solid ${brd};border-radius:${Math.max(rad,20)}px;padding:3px 10px;font-size:11px;font-weight:600">${compName}</span><span style="background:${brd}40;color:${txt};border:1px solid ${brd};border-radius:${Math.max(rad,20)}px;padding:3px 10px;font-size:11px">Default</span></div>`);
|
|
3867
|
+
}
|
|
3868
|
+
if (/^(checkbox|radio|radiogroup)$/.test(n)) {
|
|
3869
|
+
return wrap(`<div style="display:flex;flex-direction:column;gap:5px"><div style="display:flex;align-items:center;gap:6px"><div style="width:14px;height:14px;border-radius:3px;background:${acc};flex-shrink:0"></div><div style="height:6px;background:${txt};opacity:0.35;border-radius:2px;width:50px"></div></div><div style="display:flex;align-items:center;gap:6px"><div style="width:14px;height:14px;border-radius:3px;background:${bg};border:2px solid ${brd};flex-shrink:0"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:40px"></div></div></div>`);
|
|
3870
|
+
}
|
|
3871
|
+
if (/^switch$/.test(n)) {
|
|
3872
|
+
return wrap(`<div style="display:flex;align-items:center;gap:12px"><div style="width:36px;height:20px;background:${acc};border-radius:9999px;position:relative;flex-shrink:0"><div style="position:absolute;right:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:9999px"></div></div><div style="width:36px;height:20px;background:${brd};border-radius:9999px;position:relative;flex-shrink:0"><div style="position:absolute;left:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:9999px"></div></div></div>`);
|
|
3873
|
+
}
|
|
3874
|
+
if (/^(select|combobox)$/.test(n)) {
|
|
3875
|
+
return wrap(`<div style="width:100%;max-width:190px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;display:flex;align-items:center;gap:4px;box-sizing:border-box"><div style="flex:1;height:8px;background:${txt};opacity:0.3;border-radius:2px"></div><div style="font-size:8px;color:${txt};opacity:0.5;flex-shrink:0">▾</div></div>`);
|
|
3876
|
+
}
|
|
3877
|
+
if (/^slider$/.test(n)) {
|
|
3878
|
+
return wrap(`<div style="width:80%;position:relative;padding:8px 0"><div style="height:4px;background:${brd};border-radius:9999px;position:relative"><div style="position:absolute;left:0;top:0;height:100%;width:60%;background:${acc};border-radius:9999px"></div><div style="position:absolute;left:calc(60% - 7px);top:50%;transform:translateY(-50%);width:14px;height:14px;background:${bg};border:2px solid ${acc};border-radius:9999px"></div></div></div>`);
|
|
3879
|
+
}
|
|
3880
|
+
if (/^skeleton$/.test(n) || category === "Loading") {
|
|
3881
|
+
return wrap(`<div style="display:flex;flex-direction:column;gap:5px;width:80%"><div style="height:10px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:100%"></div><div style="height:8px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:80%;opacity:0.7"></div><div style="height:8px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:60%;opacity:0.5"></div></div>`);
|
|
3882
|
+
}
|
|
3883
|
+
if (/^(spinner|loader)$/.test(n)) {
|
|
3884
|
+
return wrap(`<div style="width:24px;height:24px;border-radius:9999px;border:3px solid ${brd};border-top-color:${acc};"></div>`);
|
|
3885
|
+
}
|
|
3886
|
+
if (/^(alert|banner|notification)$/.test(n) || category === "Messaging") {
|
|
3887
|
+
return wrap(`<div style="display:flex;align-items:center;gap:8px;background:${acc}15;border:1px solid ${acc}40;border-left:3px solid ${acc};border-radius:${rad}px;padding:6px 10px;width:100%;max-width:220px;box-sizing:border-box"><div style="flex:1;height:7px;background:${txt};opacity:0.3;border-radius:2px"></div></div>`);
|
|
3888
|
+
}
|
|
3889
|
+
if (/^toast$/.test(n)) {
|
|
3890
|
+
return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:8px 12px;width:100%;max-width:210px;box-sizing:border-box;display:flex;justify-content:space-between;align-items:center"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:60%"></div><div style="font-size:9px;color:${txt};opacity:0.4">✕</div></div>`);
|
|
3891
|
+
}
|
|
3892
|
+
if (/^avatar$/.test(n) || /^(images and icons)$/i.test(category)) {
|
|
3893
|
+
return wrap(`<div style="display:flex;gap:8px;align-items:center"><div style="width:36px;height:36px;border-radius:9999px;background:${acc};display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:#fff;flex-shrink:0">${compName.slice(0,1).toUpperCase()}</div><div style="width:28px;height:28px;border-radius:9999px;background:${bg};border:1px solid ${brd};flex-shrink:0"></div></div>`);
|
|
3894
|
+
}
|
|
3895
|
+
if (/^(card|tile|panel)$/.test(n) || (category === "Layout and Structure" && /^card$/.test(n))) {
|
|
3896
|
+
return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:10px 12px;width:85%;box-sizing:border-box"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:50%;margin-bottom:5px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%;margin-bottom:4px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:65%"></div></div>`);
|
|
3897
|
+
}
|
|
3898
|
+
if (/^(accordion|collapsible)$/.test(n)) {
|
|
3899
|
+
return wrap(`<div style="width:85%;box-sizing:border-box"><div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:7px 12px;display:flex;justify-content:space-between;align-items:center"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:50%"></div><div style="font-size:10px;color:${txt};opacity:0.5">▾</div></div></div>`);
|
|
3900
|
+
}
|
|
3901
|
+
if (/^(dialog|modal|sheet|alertdialog)$/.test(n) || category === "Overlays and Layering") {
|
|
3902
|
+
return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:85%;box-sizing:border-box"><div style="background:${hex2(bg)};border-bottom:1px solid ${brd};padding:5px 10px;display:flex;justify-content:space-between;align-items:center"><div style="height:6px;background:${txt};opacity:0.5;border-radius:2px;width:50px"></div><div style="font-size:9px;color:${txt};opacity:0.3">✕</div></div><div style="padding:8px 10px;display:flex;flex-direction:column;gap:3px"><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%"></div><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:55%"></div></div></div>`);
|
|
3903
|
+
}
|
|
3904
|
+
if (/^drawer$/.test(n)) {
|
|
3905
|
+
return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px ${rad}px 0 0;width:85%;box-sizing:border-box"><div style="padding:8px 12px;border-bottom:1px solid ${brd};display:flex;align-items:center;gap:6px"><div style="height:6px;background:${txt};opacity:0.4;border-radius:2px;width:60%"></div></div><div style="padding:8px 12px;display:flex;flex-direction:column;gap:3px"><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:100%"></div></div></div>`);
|
|
3906
|
+
}
|
|
3907
|
+
if (/^(tabs|tab)$/.test(n)) {
|
|
3908
|
+
return wrap(`<div style="width:85%;box-sizing:border-box"><div style="display:flex;background:${bg};border-radius:${rad}px;padding:3px;gap:2px"><div style="flex:1;background:${bg === "#f3f4f6" ? "#fff" : hex2(bg)};border-radius:${Math.max(rad-2,2)}px;padding:4px 0;font-size:10px;font-weight:600;color:${txt};text-align:center">Tab 1</div><div style="flex:1;padding:4px 0;font-size:10px;color:${txt};opacity:0.5;text-align:center">Tab 2</div><div style="flex:1;padding:4px 0;font-size:10px;color:${txt};opacity:0.5;text-align:center">Tab 3</div></div></div>`);
|
|
3909
|
+
}
|
|
3910
|
+
if (/^(table|datatable)$/.test(n) || category === "Text and Data Display") {
|
|
3911
|
+
return wrap(`<div style="border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:85%;box-sizing:border-box"><div style="display:flex;background:${hex2(bg)};border-bottom:1px solid ${brd};padding:4px 8px;gap:8px">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.3;border-radius:1px"></div>`).join("")}</div><div style="display:flex;background:${bg};padding:4px 8px;gap:8px">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.15;border-radius:1px"></div>`).join("")}</div><div style="display:flex;background:${bg};padding:4px 8px;gap:8px;opacity:0.7">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.15;border-radius:1px"></div>`).join("")}</div></div>`);
|
|
3912
|
+
}
|
|
3913
|
+
if (/^(sidebar|appsidebar)$/.test(n)) {
|
|
3914
|
+
return wrap(`<div style="display:flex;gap:6px;align-items:flex-start"><div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;width:50px;display:flex;flex-direction:column;gap:4px;padding:6px 4px"><div style="height:4px;background:${acc};border-radius:2px;width:80%;margin:0 auto"></div><div style="height:4px;background:${txt};opacity:0.25;border-radius:2px;width:70%;margin:0 auto"></div><div style="height:4px;background:${txt};opacity:0.25;border-radius:2px;width:60%;margin:0 auto"></div></div><div style="flex:1;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px"><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:90%"></div></div></div>`);
|
|
3915
|
+
}
|
|
3916
|
+
if (/^(navbar|nav|navigation|breadcrumb|pagination|navigationmenu)$/.test(n) || /^Navigation$/.test(category)) {
|
|
3917
|
+
return wrap(`<div style="display:flex;align-items:center;gap:8px;background:${bg};border-bottom:1px solid ${brd};padding:6px 10px;width:100%;max-width:220px;box-sizing:border-box"><div style="height:6px;background:${acc};border-radius:2px;width:35px"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:28px"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:32px"></div></div>`);
|
|
3918
|
+
}
|
|
3919
|
+
if (/^(progress)$/.test(n)) {
|
|
3920
|
+
return wrap(`<div style="width:80%;box-sizing:border-box"><div style="height:6px;background:${brd};border-radius:9999px;overflow:hidden"><div style="height:100%;width:65%;background:${acc};border-radius:9999px"></div></div></div>`);
|
|
3921
|
+
}
|
|
3922
|
+
if (/^(dropdownmenu|contextmenu|menubar|menu)$/.test(n)) {
|
|
3923
|
+
return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:70%;box-sizing:border-box">${[["60%","#a0a"],["40%",""],["55%",""]].map(([w,a])=>`<div style="padding:4px 10px;display:flex;align-items:center;gap:6px;${a?`background:${acc}20`:``}"><div style="flex:0 0 ${w};height:5px;background:${txt};opacity:${a?0.5:0.25};border-radius:1px"></div></div>`).join("")}</div>`);
|
|
3924
|
+
}
|
|
3925
|
+
if (/^(popover|tooltip|hovercard)$/.test(n)) {
|
|
3926
|
+
return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:8px 12px;width:75%;box-sizing:border-box;box-shadow:0 4px 12px rgba(0,0,0,0.1)"><div style="height:6px;background:${txt};opacity:0.3;border-radius:2px;width:60%;margin-bottom:4px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%"></div></div>`);
|
|
3927
|
+
}
|
|
3928
|
+
if (/^(resizable|splitpane)$/.test(n)) {
|
|
3929
|
+
return wrap(`<div style="display:flex;align-items:stretch;gap:0;width:85%;height:40px"><div style="flex:1;background:${bg};border:1px solid ${brd};border-radius:${rad}px 0 0 ${rad}px"></div><div style="width:5px;background:${brd};cursor:col-resize"></div><div style="flex:1;background:${bg};border:1px solid ${brd};border-left:none;border-radius:0 ${rad}px ${rad}px 0"></div></div>`);
|
|
3930
|
+
}
|
|
3931
|
+
if (/^(chart|pricechart|sparkline)$/.test(n)) {
|
|
3932
|
+
const pts = [22,18,28,15,24,12,20,16,26,10,22,18].map((y,x)=>`${x*14},${y}`).join(" ");
|
|
3933
|
+
return wrap(`<div style="width:85%;height:40px;position:relative"><svg viewBox="0 0 168 32" style="width:100%;height:100%"><polyline points="${pts}" fill="none" stroke="${acc}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>`);
|
|
3934
|
+
}
|
|
3935
|
+
if (/^(separator)$/.test(n)) {
|
|
3936
|
+
return wrap(`<div style="width:85%;height:1px;background:${brd}"></div>`);
|
|
3937
|
+
}
|
|
3938
|
+
// Generic page/section components → show token color stripe
|
|
3939
|
+
if (colorSwatches.length > 0) {
|
|
3940
|
+
const stripeW = Math.floor(100 / colorSwatches.length);
|
|
3941
|
+
const stripes = colorSwatches.map(s => `<div style="flex:1;background:${s.hex};height:20px"></div>`).join("");
|
|
3942
|
+
return wrap(`<div style="width:85%;display:flex;flex-direction:column;gap:5px"><div style="display:flex;border-radius:4px;overflow:hidden;height:12px">${stripes}</div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:60%"></div><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:40%"></div></div>`);
|
|
3943
|
+
}
|
|
3944
|
+
// Absolute fallback: neutral box
|
|
3945
|
+
return wrap(`<div style="width:85%;height:28px;background:${bg};border:1px solid ${brd};border-radius:${rad}px"></div>`);
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3818
3948
|
/**
|
|
3819
3949
|
* Generates Foundations/Component Inventory story.
|
|
3820
3950
|
* Shows all project components classified by Atlassian-style functional category.
|
|
3821
3951
|
* Every value is derived from vds-output.json — no placeholder content.
|
|
3822
3952
|
*/
|
|
3953
|
+
/**
|
|
3954
|
+
* Single-pass JSX usage scan: returns a Set of component names (PascalCase-normalised)
|
|
3955
|
+
* that actually appear as <Tag in the project's source files (excluding ui/ definitions
|
|
3956
|
+
* and story files). This is used to distinguish "used in project" from "installed only".
|
|
3957
|
+
*/
|
|
3958
|
+
function buildComponentUsageSet(componentNames, projectRoot) {
|
|
3959
|
+
// Pre-compute pascal names for each component
|
|
3960
|
+
const patterns = componentNames.map(name => {
|
|
3961
|
+
const pascal = name
|
|
3962
|
+
.replace(/[-\s]+(.)/g, (_, c) => c.toUpperCase())
|
|
3963
|
+
.replace(/^(.)/, c => c.toUpperCase());
|
|
3964
|
+
return { name, pascal };
|
|
3965
|
+
});
|
|
3966
|
+
|
|
3967
|
+
const usedNames = new Set();
|
|
3968
|
+
|
|
3969
|
+
// Resolve the shadcn ui directory path to exclude component definition files
|
|
3970
|
+
// (e.g. dialog.tsx contains <DialogClose> which would falsely register Dialog as "used")
|
|
3971
|
+
const uiDefDir = path.resolve(path.join(projectRoot, COMPONENTS_REL_DIR));
|
|
3972
|
+
|
|
3973
|
+
function walkDir(dir) {
|
|
3974
|
+
let entries;
|
|
3975
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
3976
|
+
for (const entry of entries) {
|
|
3977
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
3978
|
+
const full = path.join(dir, entry.name);
|
|
3979
|
+
if (entry.isDirectory()) {
|
|
3980
|
+
// Skip: story dirs, storybook config, AND the shadcn/ui component definitions dir
|
|
3981
|
+
if (["stories", ".storybook"].includes(entry.name)) continue;
|
|
3982
|
+
if (path.resolve(full) === uiDefDir) continue; // shadcn ui/ — skip definitions
|
|
3983
|
+
walkDir(full);
|
|
3984
|
+
} else if (/\.(tsx|jsx)$/.test(entry.name) && !/\.stories\./.test(entry.name)) {
|
|
3985
|
+
let src;
|
|
3986
|
+
try { src = fs.readFileSync(full, "utf-8"); } catch { continue; }
|
|
3987
|
+
for (const { name, pascal } of patterns) {
|
|
3988
|
+
if (!usedNames.has(name) && src.includes(`<${pascal}`)) {
|
|
3989
|
+
usedNames.add(name);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
// Walk common source roots (excluding the component definition directories themselves)
|
|
3997
|
+
const srcRoots = ["src", "client/src", "app", "pages"]
|
|
3998
|
+
.map(d => path.join(projectRoot, d))
|
|
3999
|
+
.filter(d => fs.existsSync(d));
|
|
4000
|
+
for (const root of srcRoots) walkDir(root);
|
|
4001
|
+
|
|
4002
|
+
return usedNames;
|
|
4003
|
+
}
|
|
4004
|
+
|
|
3823
4005
|
function writeComponentInventoryStory(components, foundations) {
|
|
3824
4006
|
if (!Array.isArray(components) || components.length === 0) return;
|
|
3825
4007
|
const foundationsDir = path.join(STORIES_DIR, "foundations");
|
|
@@ -3833,6 +4015,10 @@ function writeComponentInventoryStory(components, foundations) {
|
|
|
3833
4015
|
const categorized = {}; // category → [{ name, group, tokenCount, propCount, colorSwatches }]
|
|
3834
4016
|
const foundColors = (foundations && foundations.colors) ? foundations.colors : {};
|
|
3835
4017
|
|
|
4018
|
+
// Determine which components are actually used as JSX in the project source
|
|
4019
|
+
const componentNames = components.map(c => c.name);
|
|
4020
|
+
const usedSet = buildComponentUsageSet(componentNames, PROJECT_ROOT);
|
|
4021
|
+
|
|
3836
4022
|
for (const comp of components) {
|
|
3837
4023
|
const g = comp.group || "Components";
|
|
3838
4024
|
const gLower = g.toLowerCase();
|
|
@@ -3860,19 +4046,21 @@ function writeComponentInventoryStory(components, foundations) {
|
|
|
3860
4046
|
})
|
|
3861
4047
|
.filter(s => s.hex); // only swatches with resolved hex
|
|
3862
4048
|
|
|
3863
|
-
//
|
|
3864
|
-
const
|
|
3865
|
-
|
|
3866
|
-
|
|
4049
|
+
// Visual preview HTML (computed at generation time using project's resolved colors)
|
|
4050
|
+
const previewHtml = buildInventoryPreviewHtml(comp.name, category, colorSwatches, tokens.filter(t => !/:/.test(t)));
|
|
4051
|
+
|
|
4052
|
+
// Whether this component is actually used as a JSX tag anywhere in the project source
|
|
4053
|
+
const active = usedSet.has(comp.name);
|
|
3867
4054
|
|
|
3868
4055
|
if (!categorized[category]) categorized[category] = [];
|
|
3869
4056
|
categorized[category].push({
|
|
3870
|
-
name:
|
|
3871
|
-
group:
|
|
3872
|
-
tokenCount:
|
|
3873
|
-
propCount:
|
|
4057
|
+
name: comp.name,
|
|
4058
|
+
group: g,
|
|
4059
|
+
tokenCount: tokens.filter(t => !/:/.test(t)).length,
|
|
4060
|
+
propCount: props.length,
|
|
3874
4061
|
colorSwatches,
|
|
3875
|
-
|
|
4062
|
+
previewHtml,
|
|
4063
|
+
active,
|
|
3876
4064
|
});
|
|
3877
4065
|
}
|
|
3878
4066
|
|
|
@@ -3887,10 +4075,15 @@ function writeComponentInventoryStory(components, foundations) {
|
|
|
3887
4075
|
|
|
3888
4076
|
const inventoryData = sortedCategories.map(cat => ({
|
|
3889
4077
|
category: cat,
|
|
3890
|
-
|
|
4078
|
+
// Sort: active first, then alphabetical within each group
|
|
4079
|
+
components: categorized[cat].sort((a, b) => {
|
|
4080
|
+
if (a.active !== b.active) return a.active ? -1 : 1;
|
|
4081
|
+
return a.name.localeCompare(b.name);
|
|
4082
|
+
}),
|
|
3891
4083
|
}));
|
|
3892
4084
|
|
|
3893
4085
|
const totalComponents = components.length;
|
|
4086
|
+
const activeComponents = [...usedSet].length;
|
|
3894
4087
|
const uniqueTokens = [...new Set(
|
|
3895
4088
|
components.flatMap(c => Array.isArray(c.tokens) ? c.tokens.filter(t => !/:/.test(t)) : [])
|
|
3896
4089
|
)].length;
|
|
@@ -3902,90 +4095,85 @@ function writeComponentInventoryStory(components, foundations) {
|
|
|
3902
4095
|
`export default meta;`,
|
|
3903
4096
|
`type Story = StoryObj;`,
|
|
3904
4097
|
``,
|
|
3905
|
-
`const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[];
|
|
4098
|
+
`const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; previewHtml: string; active: boolean }[] }[] = ${JSON.stringify(inventoryData)};`,
|
|
3906
4099
|
`const totalComponents = ${totalComponents};`,
|
|
4100
|
+
`const activeComponents = ${activeComponents};`,
|
|
3907
4101
|
`const uniqueTokens = ${uniqueTokens};`,
|
|
3908
4102
|
``,
|
|
3909
4103
|
`export const Default: Story = {`,
|
|
3910
|
-
` render: () =>
|
|
3911
|
-
`
|
|
4104
|
+
` render: () => {`,
|
|
4105
|
+
` const [showUnused, setShowUnused] = (window as any).__vdsShowUnused !== undefined`,
|
|
4106
|
+
` ? [(window as any).__vdsShowUnused, (v: boolean) => { (window as any).__vdsShowUnused = v; }]`,
|
|
4107
|
+
` : [false, () => {}];`,
|
|
4108
|
+
` const [expanded, setExpanded] = React.useState(false);`,
|
|
4109
|
+
` const Card = ({ comp }: { comp: any }) => (`,
|
|
4110
|
+
` <div style={{ border: \`1px solid \${comp.active ? "#e5e7eb" : "#f0f0f0"}\`, borderRadius: 10, overflow: "hidden", background: comp.active ? "#fff" : "#fafafa", display: "flex", flexDirection: "column" as any, boxShadow: comp.active ? "0 1px 3px rgba(0,0,0,0.05)" : "none", opacity: comp.active ? 1 : 0.55 }}>`,
|
|
4111
|
+
` <div style={{ background: comp.active ? "#f8f9fa" : "#f5f5f5", borderBottom: "1px solid #f0f0f0", minHeight: 64, display: "flex", alignItems: "center", justifyContent: "center" }}`,
|
|
4112
|
+
` dangerouslySetInnerHTML={{ __html: comp.previewHtml }}`,
|
|
4113
|
+
` />`,
|
|
4114
|
+
` <div style={{ padding: "10px 12px" }}>`,
|
|
4115
|
+
` <div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 5 }}>`,
|
|
4116
|
+
` <span style={{ fontWeight: 700, fontSize: 13, color: comp.active ? "#111" : "#9ca3af" }}>{comp.name}</span>`,
|
|
4117
|
+
` {comp.active && <span style={{ fontSize: 9, background: "#dcfce7", color: "#15803d", padding: "1px 5px", borderRadius: 4, fontWeight: 700, letterSpacing: "0.05em" }}>USED</span>}`,
|
|
4118
|
+
` </div>`,
|
|
4119
|
+
` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any }}>`,
|
|
4120
|
+
` {comp.tokenCount > 0 && (`,
|
|
4121
|
+
` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>{comp.tokenCount} tokens</span>`,
|
|
4122
|
+
` )}`,
|
|
4123
|
+
` {comp.colorSwatches.length > 0 && (`,
|
|
4124
|
+
` <span style={{ fontSize: 10, background: "#fef9c3", color: "#713f12", padding: "2px 7px", borderRadius: 10, border: "1px solid #fde68a" }}>{comp.colorSwatches.length} colors</span>`,
|
|
4125
|
+
` )}`,
|
|
4126
|
+
` {!comp.active && (`,
|
|
4127
|
+
` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#9ca3af", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>installed</span>`,
|
|
4128
|
+
` )}`,
|
|
4129
|
+
` </div>`,
|
|
4130
|
+
` </div>`,
|
|
4131
|
+
` </div>`,
|
|
4132
|
+
` );`,
|
|
4133
|
+
` return (`,
|
|
4134
|
+
` <div style={{ padding: 32, background: "#f8f9fa", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`,
|
|
3912
4135
|
` {/* Header */}`,
|
|
3913
4136
|
` <div style={{ marginBottom: 40 }}>`,
|
|
3914
4137
|
` <h2 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 8px" }}>Component Inventory</h2>`,
|
|
3915
|
-
` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>
|
|
3916
|
-
` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any }}>`,
|
|
4138
|
+
` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>Components actually used in this project. <strong style={{ color: "#111" }}>USED</strong> = appears as a JSX tag in source files.</p>`,
|
|
4139
|
+
` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any, marginBottom: 20 }}>`,
|
|
3917
4140
|
` {[`,
|
|
3918
|
-
` { value:
|
|
3919
|
-
` { value:
|
|
3920
|
-
` { value: uniqueTokens.toString(), label: "Unique Tokens" },`,
|
|
4141
|
+
` { value: activeComponents.toString(), label: "Used in Project", bg: "#f0fdf4", fg: "#15803d", border: "#bbf7d0" },`,
|
|
4142
|
+
` { value: (totalComponents - activeComponents).toString(), label: "Installed Only", bg: "#f9fafb", fg: "#6b7280", border: "#e5e7eb" },`,
|
|
4143
|
+
` { value: uniqueTokens.toString(), label: "Unique Tokens", bg: "#eff6ff", fg: "#1d4ed8", border: "#dbeafe" },`,
|
|
3921
4144
|
` ].map(stat => (`,
|
|
3922
|
-
` <div key={stat.label} style={{ padding: "12px 20px", background:
|
|
3923
|
-
` <div style={{ fontSize: 26, fontWeight: 800, color:
|
|
3924
|
-
` <div style={{ fontSize: 12, color:
|
|
4145
|
+
` <div key={stat.label} style={{ padding: "12px 20px", background: stat.bg, borderRadius: 10, textAlign: "center" as any, minWidth: 110, border: \`1px solid \${stat.border}\` }}>`,
|
|
4146
|
+
` <div style={{ fontSize: 26, fontWeight: 800, color: stat.fg, lineHeight: 1 }}>{stat.value}</div>`,
|
|
4147
|
+
` <div style={{ fontSize: 12, color: stat.fg, marginTop: 4, opacity: 0.8 }}>{stat.label}</div>`,
|
|
3925
4148
|
` </div>`,
|
|
3926
4149
|
` ))}`,
|
|
3927
4150
|
` </div>`,
|
|
4151
|
+
` <button onClick={() => setExpanded(!expanded)} style={{ fontSize: 12, color: "#6b7280", background: "transparent", border: "1px solid #e5e7eb", borderRadius: 6, padding: "5px 12px", cursor: "pointer" }}>`,
|
|
4152
|
+
` {expanded ? "Hide" : "Show"} installed-only components`,
|
|
4153
|
+
` </button>`,
|
|
3928
4154
|
` </div>`,
|
|
3929
|
-
` {/* Category groups */}`,
|
|
3930
|
-
` {inventoryData.map(group =>
|
|
3931
|
-
`
|
|
3932
|
-
`
|
|
4155
|
+
` {/* Category groups — active components first */}`,
|
|
4156
|
+
` {inventoryData.map(group => {`,
|
|
4157
|
+
` const active = group.components.filter((c: any) => c.active);`,
|
|
4158
|
+
` const inactive = group.components.filter((c: any) => !c.active);`,
|
|
4159
|
+
` if (active.length === 0 && !expanded) return null;`,
|
|
4160
|
+
` return (`,
|
|
4161
|
+
` <div key={group.category} style={{ marginBottom: 44 }}>`,
|
|
4162
|
+
` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #e5e7eb" }}>`,
|
|
3933
4163
|
` <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: "#111" }}>{group.category}</h3>`,
|
|
3934
|
-
` <span style={{ fontSize: 12, background: "#
|
|
4164
|
+
` <span style={{ fontSize: 12, background: "#dcfce7", color: "#15803d", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{active.length} used</span>`,
|
|
4165
|
+
` {inactive.length > 0 && <span style={{ fontSize: 12, background: "#f3f4f6", color: "#9ca3af", padding: "2px 9px", borderRadius: 12 }}>{inactive.length} installed</span>}`,
|
|
3935
4166
|
` </div>`,
|
|
3936
|
-
` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(
|
|
3937
|
-
` {
|
|
3938
|
-
`
|
|
3939
|
-
` {/* Color swatch strip — project-resolved colors from foundations */}`,
|
|
3940
|
-
` {comp.colorSwatches.length > 0 ? (`,
|
|
3941
|
-
` <div style={{ display: "flex", height: 8 }}>`,
|
|
3942
|
-
` {comp.colorSwatches.map((s: any) => (`,
|
|
3943
|
-
` <div key={s.token} title={s.token} style={{ flex: 1, background: s.hex }} />`,
|
|
3944
|
-
` ))}`,
|
|
3945
|
-
` </div>`,
|
|
3946
|
-
` ) : (`,
|
|
3947
|
-
` <div style={{ height: 4, background: "#f3f4f6" }} />`,
|
|
3948
|
-
` )}`,
|
|
3949
|
-
` <div style={{ padding: "12px 14px", flexGrow: 1 }}>`,
|
|
3950
|
-
` <div style={{ fontWeight: 700, fontSize: 13, color: "#111", marginBottom: 6 }}>{comp.name}</div>`,
|
|
3951
|
-
` {/* Token fingerprint pills */}`,
|
|
3952
|
-
` {comp.tokenFingerprint.length > 0 && (`,
|
|
3953
|
-
` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any, marginBottom: 8 }}>`,
|
|
3954
|
-
` {comp.tokenFingerprint.map((t: string) => (`,
|
|
3955
|
-
` <code key={t} style={{ fontSize: 9, background: "#f3f4f6", color: "#6b7280", padding: "1px 5px", borderRadius: 4, border: "1px solid #e5e7eb" }}>{t}</code>`,
|
|
3956
|
-
` ))}`,
|
|
3957
|
-
` </div>`,
|
|
3958
|
-
` )}`,
|
|
3959
|
-
` <div style={{ display: "flex", gap: 5, flexWrap: "wrap" as any }}>`,
|
|
3960
|
-
` {comp.propCount > 0 && (`,
|
|
3961
|
-
` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#6b7280", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>`,
|
|
3962
|
-
` {comp.propCount} props`,
|
|
3963
|
-
` </span>`,
|
|
3964
|
-
` )}`,
|
|
3965
|
-
` {comp.tokenCount > 0 && (`,
|
|
3966
|
-
` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>`,
|
|
3967
|
-
` {comp.tokenCount} tokens`,
|
|
3968
|
-
` </span>`,
|
|
3969
|
-
` )}`,
|
|
3970
|
-
` {comp.colorSwatches.length > 0 && (`,
|
|
3971
|
-
` <span style={{ fontSize: 10, background: "#fef9c3", color: "#713f12", padding: "2px 7px", borderRadius: 10, border: "1px solid #fde68a" }}>`,
|
|
3972
|
-
` {comp.colorSwatches.length} colors`,
|
|
3973
|
-
` </span>`,
|
|
3974
|
-
` )}`,
|
|
3975
|
-
` {comp.group && comp.group !== "shadcn" && comp.group !== "UI" && comp.group !== "Components" && (`,
|
|
3976
|
-
` <span style={{ fontSize: 10, background: "#f0fdf4", color: "#166534", padding: "2px 7px", borderRadius: 10, border: "1px solid #bbf7d0" }}>`,
|
|
3977
|
-
` {comp.group}`,
|
|
3978
|
-
` </span>`,
|
|
3979
|
-
` )}`,
|
|
3980
|
-
` </div>`,
|
|
3981
|
-
` </div>`,
|
|
3982
|
-
` </div>`,
|
|
3983
|
-
` ))}`,
|
|
4167
|
+
` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 12 }}>`,
|
|
4168
|
+
` {active.map((comp: any) => <Card key={comp.name} comp={comp} />)}`,
|
|
4169
|
+
` {expanded && inactive.map((comp: any) => <Card key={comp.name} comp={comp} />)}`,
|
|
3984
4170
|
` </div>`,
|
|
3985
4171
|
` </div>`,
|
|
3986
|
-
`
|
|
4172
|
+
` );`,
|
|
4173
|
+
` })}`,
|
|
3987
4174
|
` </div>`,
|
|
3988
|
-
`
|
|
4175
|
+
` );`,
|
|
4176
|
+
` },`,
|
|
3989
4177
|
`};`,
|
|
3990
4178
|
].join("\n");
|
|
3991
4179
|
|
|
@@ -4358,21 +4546,32 @@ function main() {
|
|
|
4358
4546
|
}
|
|
4359
4547
|
}
|
|
4360
4548
|
|
|
4549
|
+
// Auto-detect which shadcn/ui components are actually used as JSX in the project source.
|
|
4550
|
+
// This replaces the manual `includeComponents` list — VDS now self-discovers which
|
|
4551
|
+
// shadcn primitives need stories instead of requiring users to maintain a whitelist.
|
|
4552
|
+
const allCompNames = components.map(c => c.name);
|
|
4553
|
+
const autoUsedSet = buildComponentUsageSet(allCompNames, PROJECT_ROOT);
|
|
4554
|
+
console.log(`[VDS] Auto-detected ${autoUsedSet.size} components with active JSX usage`);
|
|
4555
|
+
|
|
4361
4556
|
let writtenCount = 0;
|
|
4362
4557
|
for (const comp of components) {
|
|
4363
4558
|
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
4364
|
-
// Skip unclassified and shadcn/ui primitives (UI group) — they're documented at ui.shadcn.com
|
|
4365
|
-
// Only project-specific module groups (Circles, Finance, Projects, Time, …) get stories
|
|
4366
4559
|
const g = comp.group || "Components";
|
|
4367
|
-
|
|
4368
|
-
|
|
4560
|
+
const gLower = g.toLowerCase();
|
|
4561
|
+
|
|
4562
|
+
// For shadcn/ui primitives: only generate a story if the component is actually
|
|
4563
|
+
// used as a JSX tag (<Button />, <Badge />, etc.) somewhere in the project source.
|
|
4564
|
+
// Manual overrides: includeGroups (all from group) and includeComponents (named list).
|
|
4369
4565
|
const includeGroups = VDS_CONFIG.includeGroups || [];
|
|
4370
4566
|
const includeComponents = VDS_CONFIG.includeComponents || [];
|
|
4371
|
-
|
|
4567
|
+
|
|
4372
4568
|
if (g === "Uncategorized" || gLower === "ui" || gLower === "shadcn") {
|
|
4373
4569
|
const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === gLower);
|
|
4374
4570
|
const compIncluded = includeComponents.includes(componentName);
|
|
4375
|
-
|
|
4571
|
+
// Auto-detection: is the component used as a JSX tag in the project?
|
|
4572
|
+
const autoUsed = autoUsedSet.has(comp.name) || autoUsedSet.has(componentName);
|
|
4573
|
+
|
|
4574
|
+
if (!groupIncluded && !compIncluded && !autoUsed) {
|
|
4376
4575
|
// Clean up leftover story files for components that are now in the skip group
|
|
4377
4576
|
const oldStory = path.join(STORIES_DIR, `${componentName}.stories.tsx`);
|
|
4378
4577
|
if (fs.existsSync(oldStory)) {
|
|
@@ -4380,12 +4579,15 @@ function main() {
|
|
|
4380
4579
|
const firstLine = fs.readFileSync(oldStory, "utf-8").split("\n")[0] || "";
|
|
4381
4580
|
if (firstLine.includes("@vds-regenerate")) {
|
|
4382
4581
|
fs.unlinkSync(oldStory);
|
|
4383
|
-
console.log(`[VDS] Removed shadcn
|
|
4582
|
+
console.log(`[VDS] Removed unused shadcn story: ${componentName}.stories.tsx`);
|
|
4384
4583
|
}
|
|
4385
4584
|
} catch { /* ignore */ }
|
|
4386
4585
|
}
|
|
4387
4586
|
continue;
|
|
4388
4587
|
}
|
|
4588
|
+
if (autoUsed && !compIncluded) {
|
|
4589
|
+
console.log(`[VDS] Auto-including ${componentName} (active JSX usage detected)`);
|
|
4590
|
+
}
|
|
4389
4591
|
}
|
|
4390
4592
|
if (onlyName && componentName !== onlyName) continue;
|
|
4391
4593
|
|