Skip to content

Commit cc44c20

Browse files
committed
improve the props table and story creation in panel
1 parent 5a77853 commit cc44c20

2 files changed

Lines changed: 149 additions & 13 deletions

File tree

src/panel/panel.css

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,18 @@ html, body, #app {
485485
pointer-events: none;
486486
}
487487

488+
.hl-story-name-row {
489+
display: flex;
490+
align-items: center;
491+
gap: 8px;
492+
padding: 0 16px 12px;
493+
}
494+
495+
.hl-story-name-row .hl-prop-input {
496+
flex: 1;
497+
min-width: 0;
498+
}
499+
488500
/* Component list (shared by Missing and Covered sections) */
489501
.cov-list-wrap {
490502
flex: 1;
@@ -747,7 +759,7 @@ html, body, #app {
747759

748760
.hl-prop-row {
749761
display: flex;
750-
align-items: center;
762+
align-items: baseline;
751763
gap: 12px;
752764
padding: 4px 0;
753765
border-bottom: 1px solid var(--sb-bordercolor-muted);
@@ -821,6 +833,63 @@ html, body, #app {
821833
color: var(--sb-fgcolor-muted);
822834
background: var(--sb-bgcolor-muted);
823835
border-radius: 3px;
836+
cursor: pointer;
837+
}
838+
839+
.hl-prop-fn {
840+
display: inline-flex;
841+
align-items: center;
842+
height: 20px;
843+
padding: 0 6px;
844+
font-family: var(--sb-font-mono);
845+
font-size: 10px;
846+
font-weight: 600;
847+
color: var(--sb-color-secondary);
848+
background: var(--sb-bgcolor-muted);
849+
border-radius: 3px;
850+
}
851+
852+
.hl-prop-jsx-badge {
853+
display: inline-flex;
854+
align-items: center;
855+
height: 20px;
856+
padding: 0 6px;
857+
font-family: var(--sb-font-mono);
858+
font-size: 10px;
859+
font-weight: 600;
860+
color: var(--sb-color-secondary);
861+
background: var(--sb-bgcolor-muted);
862+
border-radius: 3px;
863+
cursor: pointer;
864+
}
865+
866+
.hl-prop-details {
867+
width: 100%;
868+
}
869+
870+
.hl-prop-details summary {
871+
list-style: none;
872+
cursor: pointer;
873+
}
874+
875+
.hl-prop-details summary::-webkit-details-marker {
876+
display: none;
877+
}
878+
879+
.hl-prop-code {
880+
margin: 4px 0 0;
881+
padding: 6px 8px;
882+
font-family: var(--sb-font-mono);
883+
font-size: 10px;
884+
line-height: 1.5;
885+
color: var(--sb-fgcolor-default);
886+
background: var(--sb-bgcolor-muted);
887+
border-radius: var(--sb-border-radius);
888+
overflow-x: auto;
889+
max-height: 120px;
890+
overflow-y: auto;
891+
white-space: pre-wrap;
892+
word-break: break-word;
824893
}
825894

826895
/* Stories section */
@@ -898,10 +967,10 @@ html, body, #app {
898967
cursor: not-allowed;
899968
}
900969

901-
/* Stories grid */
970+
/* Stories list (row layout) */
902971
.hl-stories-list {
903-
display: grid;
904-
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
972+
display: flex;
973+
flex-direction: column;
905974
gap: 12px;
906975
padding: 0 16px 16px;
907976
}
@@ -921,7 +990,7 @@ html, body, #app {
921990

922991
.hl-story-iframe {
923992
width: 100%;
924-
height: 120px;
993+
height: 200px;
925994
border: none;
926995
pointer-events: none;
927996
background: var(--sb-bgcolor-default);

src/panel/panel.ts

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,24 @@ function propsFingerprint(props: Record<string, unknown>): string {
748748
return JSON.stringify(meaningful, Object.keys(meaningful).sort())
749749
}
750750

751+
/** Suggest a story name based on meaningful prop values */
752+
function suggestStoryName(props: Record<string, unknown>): string {
753+
const meaningfulProps = ['variant', 'type', 'size', 'mode', 'status', 'kind', 'color', 'intent', 'appearance']
754+
for (const propName of meaningfulProps) {
755+
const value = props[propName]
756+
if (typeof value === 'string' && value.length > 0 && value.length < 30) {
757+
return value.charAt(0).toUpperCase() + value.slice(1)
758+
}
759+
}
760+
for (const [, value] of Object.entries(props)) {
761+
if (typeof value === 'boolean' && value) continue
762+
if (typeof value === 'string' && value.length > 0 && value.length < 30) {
763+
return value.charAt(0).toUpperCase() + value.slice(1)
764+
}
765+
}
766+
return 'Default'
767+
}
768+
751769
/** Fetch the registry snapshot from the server (RPC) */
752770
/** Read the registry from shared state (synced automatically from client) */
753771
function fetchRegistry(): RegistryInstance[] {
@@ -1294,7 +1312,10 @@ async function buildHighlighterPanel() {
12941312
root.appendChild(hdr)
12951313

12961314
// ── Properties section ──
1297-
const propsEntries = Object.entries(comp.props || {})
1315+
// Use serializedProps when available (React) for __isJSX / __isFunction markers;
1316+
// fall back to raw props (Vue / Svelte where props are already serialized).
1317+
const displayProps = comp.serializedProps || comp.props || {}
1318+
const propsEntries = Object.entries(displayProps)
12981319
if (propsEntries.length > 0) {
12991320
const propsSection = document.createElement('div')
13001321
propsSection.className = 'hl-section'
@@ -1318,9 +1339,29 @@ async function buildHighlighterPanel() {
13181339
const val = document.createElement('div')
13191340
val.className = 'hl-prop-val'
13201341

1321-
// Create an editable input for primitive values
13221342
const valType = typeof value
1323-
if (valType === 'string' || valType === 'number' || valType === 'boolean') {
1343+
const isObj = value && typeof value === 'object'
1344+
const isFunction = isObj && (value as Record<string, unknown>).__isFunction
1345+
const isJSX = isObj && (value as Record<string, unknown>).__isJSX
1346+
1347+
if (isFunction) {
1348+
// Handler / function prop
1349+
const fn = value as { __isFunction: true; name: string }
1350+
val.innerHTML = `<span class="hl-prop-fn">${fn.name ? fn.name : '() => {}'}</span>`
1351+
} else if (isJSX) {
1352+
// JSX prop — show source in a collapsible code block
1353+
const jsx = value as { __isJSX: true; source: string }
1354+
const wrapper = document.createElement('details')
1355+
wrapper.className = 'hl-prop-details'
1356+
const summary = document.createElement('summary')
1357+
summary.innerHTML = `<span class="hl-prop-jsx-badge">JSX</span>`
1358+
wrapper.appendChild(summary)
1359+
const code = document.createElement('pre')
1360+
code.className = 'hl-prop-code'
1361+
code.textContent = jsx.source
1362+
wrapper.appendChild(code)
1363+
val.appendChild(wrapper)
1364+
} else if (valType === 'string' || valType === 'number' || valType === 'boolean') {
13241365
const input = document.createElement('input')
13251366
input.className = 'hl-prop-input'
13261367
input.type = valType === 'boolean' ? 'checkbox' : valType === 'number' ? 'number' : 'text'
@@ -1330,7 +1371,6 @@ async function buildHighlighterPanel() {
13301371
input.value = String(value)
13311372
}
13321373
input.addEventListener('change', () => {
1333-
// Update the local props for story creation
13341374
if (valType === 'boolean') {
13351375
comp.props[key] = input.checked
13361376
} else if (valType === 'number') {
@@ -1343,8 +1383,21 @@ async function buildHighlighterPanel() {
13431383
} else if (value === null || value === undefined) {
13441384
val.innerHTML = `<span class="hl-prop-null">${String(value)}</span>`
13451385
} else {
1346-
// Complex objects — show a collapsed badge
1347-
val.innerHTML = `<span class="hl-prop-obj">${Array.isArray(value) ? 'Array' : 'Object'}</span>`
1386+
// Complex objects/arrays — show collapsible JSON
1387+
const wrapper = document.createElement('details')
1388+
wrapper.className = 'hl-prop-details'
1389+
const summary = document.createElement('summary')
1390+
summary.innerHTML = `<span class="hl-prop-obj">${Array.isArray(value) ? `Array(${(value as unknown[]).length})` : 'Object'}</span>`
1391+
wrapper.appendChild(summary)
1392+
const code = document.createElement('pre')
1393+
code.className = 'hl-prop-code'
1394+
try {
1395+
code.textContent = JSON.stringify(value, null, 2)
1396+
} catch {
1397+
code.textContent = String(value)
1398+
}
1399+
wrapper.appendChild(code)
1400+
val.appendChild(wrapper)
13481401
}
13491402

13501403
row.appendChild(label)
@@ -1364,6 +1417,19 @@ async function buildHighlighterPanel() {
13641417
createHdr.className = 'hl-section-hdr'
13651418
createHdr.innerHTML = `<span class="hl-section-title">Create Story</span>`
13661419

1420+
createSection.appendChild(createHdr)
1421+
1422+
// Story name input
1423+
const storyNameRow = document.createElement('div')
1424+
storyNameRow.className = 'hl-story-name-row'
1425+
const storyNameInput = document.createElement('input')
1426+
storyNameInput.className = 'hl-prop-input'
1427+
storyNameInput.type = 'text'
1428+
storyNameInput.placeholder = 'Story name\u2026'
1429+
storyNameInput.value = suggestStoryName(comp.props)
1430+
storyNameInput.addEventListener('focus', () => storyNameInput.select())
1431+
storyNameRow.appendChild(storyNameInput)
1432+
13671433
const addBtn = document.createElement('button')
13681434
addBtn.className = 'create-all-btn'
13691435
addBtn.textContent = 'Add'
@@ -1375,6 +1441,7 @@ async function buildHighlighterPanel() {
13751441
meta: comp.meta,
13761442
props: comp.props,
13771443
serializedProps: comp.serializedProps,
1444+
storyName: storyNameInput.value.trim() || undefined,
13781445
})
13791446
// Bust the storybook index cache and retry until the new story appears
13801447
const refreshAfterCreate = async () => {
@@ -1401,9 +1468,9 @@ async function buildHighlighterPanel() {
14011468
addBtn.textContent = 'Add'
14021469
}
14031470
})
1404-
createHdr.appendChild(addBtn)
1471+
storyNameRow.appendChild(addBtn)
14051472

1406-
createSection.appendChild(createHdr)
1473+
createSection.appendChild(storyNameRow)
14071474
root.appendChild(createSection)
14081475

14091476
// ── Stories section ──

0 commit comments

Comments
 (0)