refactor(storybook): consolidate property panel comparison layout
Keep sw-header--property-panel-compare as the single property panel state page: the four existing SWHeaderPropertyPanel states in 2 columns x 2 rows at their original size, inside a scrolling Inner. Remove the duplicate Property Panel Variants and the four single-state Stories (SW 24 -> 19). Storybook-only change; SW runtime and public API untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c705a1fc59
commit
c8e5fbeebc
82 files changed
+12318
-49
No files matched your search
@@ -1038,7 +1038,7 @@ function usePropertyMenus(record: (line: string) => void, setProperties: (update
|
||||
}
|
||||
}
|
||||
|
||||
function PropertyPanelDemo({ standalone = false }: { standalone?: boolean }) {
|
||||
function PropertyPanelDemo() {
|
||||
const [example, setExample] = useState<PropertyExample>("none")
|
||||
const [selection, setSelection] = useState(propertySelection("none"))
|
||||
const [properties, setProperties] = useState(propertyFixture("none"))
|
||||
@@ -1087,12 +1087,11 @@ function PropertyPanelDemo({ standalone = false }: { standalone?: boolean }) {
|
||||
</div>
|
||||
<p className="sw-story-log" data-property-log="" aria-live="polite">{log.join(" → ") || "Callback 없음"}</p>
|
||||
<pre className="sw-story-layer-state" data-property-state="">{JSON.stringify({ selection: selection.id, values: properties.map(item => [item.id, item.value]), expanded, open })}</pre>
|
||||
{standalone ? <SWTheme><SWHeaderPropertyPanel {...panel} onClose={() => record("close")} /></SWTheme>
|
||||
: <div className="sw-story-property-room"><Frame width={width}><SWHeader {...figmaArgs} propertyPanel={panel} propertyPanelOpen={controlled ? open : undefined}
|
||||
onPropertyPanelOpenChange={next => { record("open(" + next + ")"); if (controlled) setOpen(next) }}
|
||||
onPropertyPanelToggle={() => record("legacyToggle")}
|
||||
layers={SAMPLE_LAYERS} activeLayerId="layer-0" colors={FIGMA_COLORS} lineTypes={FIGMA_LINE_TYPES} textStyles={FIGMA_TEXT_STYLES} dimensionStyles={FIGMA_DIMENSION_STYLES}
|
||||
/></Frame></div>}
|
||||
<div className="sw-story-property-room"><Frame width={width}><SWHeader {...figmaArgs} propertyPanel={panel} propertyPanelOpen={controlled ? open : undefined}
|
||||
onPropertyPanelOpenChange={next => { record("open(" + next + ")"); if (controlled) setOpen(next) }}
|
||||
onPropertyPanelToggle={() => record("legacyToggle")}
|
||||
layers={SAMPLE_LAYERS} activeLayerId="layer-0" colors={FIGMA_COLORS} lineTypes={FIGMA_LINE_TYPES} textStyles={FIGMA_TEXT_STYLES} dimensionStyles={FIGMA_DIMENSION_STYLES}
|
||||
/></Frame></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1102,11 +1101,6 @@ export const PropertyPanel: Story = {
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="속성창" description="Figma 4개 상태를 외부 데이터·그룹 펼침으로 구성합니다. 속성창 버튼(좁은 화면: 더보기)으로 열고 닫으세요. Enter/Blur 확정, Escape 취소, 다음 Escape 닫기." /><PropertyPanelDemo /></StoryPage>,
|
||||
}
|
||||
export const PropertyPanelVariants: Story = {
|
||||
name: "Property Panel Variants",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="속성창 4개 상태" description="동일 컴포넌트와 호스트 데이터입니다. 측정용 독립 패널." /><PropertyPanelDemo standalone /></StoryPage>,
|
||||
}
|
||||
export const KGuide: Story = {
|
||||
name: "K Guide",
|
||||
parameters: { controls: { disable: true } },
|
||||
@@ -1293,22 +1287,11 @@ function PropertyState({ example, shapeContent, onLog }: { example: PropertyExam
|
||||
onPropertyChange={(property, value) => { onLog?.(`property(${property.id}, ${value})`); setProperties(items => items.map(item => item.id === property.id ? { ...item, value, mixed: false, ...(item.id === "line" ? { linePattern: linePatternOf(value) } : {}) } : item)) }}
|
||||
onClose={() => onLog?.("close")} /></SWTheme>
|
||||
}
|
||||
function PropertyStateStory({ example }: { example: PropertyExample }) {
|
||||
const state = PROPERTY_STATES.find(item => item.example === example)!
|
||||
const [log, setLog] = useState<string[]>([])
|
||||
return <StoryPage><StoryPageHeader kicker={KICKER} title={`속성창 · ${state.title}`} description={`Figma ${state.node} (${state.size}px). 기존 SWHeaderPropertyPanel 하나에 호스트 데이터만 바꿔 표시합니다. Enter/Blur 확정, Escape 취소.`} />
|
||||
<p className="sw-story-log" data-property-log="">{log.join(" → ") || "Callback 없음"}</p>
|
||||
<div className="sw-story-property-state" data-property-state={example}><PropertyState example={example} onLog={line => setLog(lines => [...lines.slice(-9), line])} /></div>
|
||||
</StoryPage>
|
||||
}
|
||||
export const PropertyPanelNone: Story = { name: "Property Panel · 선택 없음", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="none" /> }
|
||||
export const PropertyPanelAll: Story = { name: "Property Panel · 전체 선택", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="all" /> }
|
||||
export const PropertyPanelCircle: Story = { name: "Property Panel · 원 1개", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="circle" /> }
|
||||
export const PropertyPanelExpanded: Story = { name: "Property Panel · 모두 펼침", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="expanded" /> }
|
||||
|
||||
/** Design check only: a real header shows one panel for the current selection. */
|
||||
/** Design check only: a real header shows one panel for the current selection.
|
||||
2 columns × 2 rows at the panels' own size; the Inner scrolls instead of shrinking or wrapping them. */
|
||||
function PropertyCompare() {
|
||||
const [sizes, setSizes] = useState<Record<string, string>>({})
|
||||
const [log, setLog] = useState<string[]>([])
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
const measure = () => setSizes(Object.fromEntries(Array.from(ref.current?.querySelectorAll<HTMLElement>("[data-compare]") ?? []).map(cell => {
|
||||
@@ -1320,17 +1303,22 @@ function PropertyCompare() {
|
||||
ref.current?.querySelectorAll(".hm-sw-property").forEach(panel => observer.observe(panel))
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
return <div ref={ref} className="sw-story-compare">
|
||||
{PROPERTY_STATES.map(state => <figure key={state.example} className="sw-story-compare__cell" data-compare={state.example}>
|
||||
<figcaption><strong>{state.title}</strong><span>Figma {state.node} · {state.size}</span><span data-measured="">측정 {sizes[state.example] ?? "-"}</span></figcaption>
|
||||
<PropertyState example={state.example} />
|
||||
</figure>)}
|
||||
</div>
|
||||
return <>
|
||||
<p className="sw-story-log" data-property-log="" aria-live="polite">{log.join(" → ") || "Callback 없음"}</p>
|
||||
<div className="sw-story-compare-scroll" data-compare-scroll="">
|
||||
<div ref={ref} className="sw-story-compare sw-story-compare--grid">
|
||||
{PROPERTY_STATES.map(state => <figure key={state.example} className="sw-story-compare__cell" data-compare={state.example}>
|
||||
<figcaption><strong>{state.title}</strong><span>Figma {state.node} · {state.size}</span><span data-measured="">측정 {sizes[state.example] ?? "-"}</span></figcaption>
|
||||
<PropertyState example={state.example} onLog={line => setLog(lines => [...lines.slice(-7), `${state.example}: ${line}`])} />
|
||||
</figure>)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
export const PropertyPanelCompare: Story = {
|
||||
name: "Property Panel · 4개 비교",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => <StoryPage className="sw-story-followup"><StoryPageHeader kicker={KICKER} title="속성창 4개 상태 비교" description="디자인 검증용 비교 화면입니다. 네 패널은 같은 SWHeaderPropertyPanel이며, 실제 Header에서는 현재 선택 객체의 패널 하나만 표시합니다. 폭이 부족하면 줄바꿈합니다." /><PropertyCompare /></StoryPage>,
|
||||
render: () => <StoryPage className="sw-story-compare-page"><StoryPageHeader kicker={KICKER} title="속성창 4개 상태 비교" description="네 패널은 같은 SWHeaderPropertyPanel입니다. 실제 Header에서는 현재 선택 객체의 패널 하나만 표시합니다." /><PropertyCompare /></StoryPage>,
|
||||
}
|
||||
|
||||
/* Shape slot: replaceable area; the values stay in the property rows. */
|
||||
|
||||
@@ -45,6 +45,17 @@
|
||||
.sw-story-compare { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 24px; }
|
||||
.sw-story-compare__cell { margin: 0; }
|
||||
.sw-story-compare__cell figcaption { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px; font-size: 12px; color: #3d4a45; }
|
||||
/* Property panel 4-state comparison (2026-10-19): 2 columns × 2 rows at the panels' own size inside a
|
||||
viewport-high page. Only the Inner scroller scrolls (both axes); panels are never shrunk or wrapped. */
|
||||
.sw-story-compare-page.sb-page { height: 100dvh; min-height: 0; }
|
||||
.sw-story-compare-page > .sb-page-inner { height: 100%; display: flex; flex-direction: column; }
|
||||
.sw-story-compare-page .sb-page-header { flex: 0 0 auto; }
|
||||
.sw-story-compare-page .sw-story-log { flex: 0 0 auto; margin-top: 12px; }
|
||||
.sw-story-compare-scroll { flex: 1 1 0; min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
||||
.sw-story-compare.sw-story-compare--grid { display: grid; grid-template-columns: repeat(2, max-content); align-items: start; gap: 24px; width: max-content; }
|
||||
/* Placement-only exception, same as the Overview Stage: the product clamps a panel to the viewport
|
||||
height; here the scroller provides the room so the 754px panel keeps its size. */
|
||||
.sw-story-compare--grid .hm-sw-property { max-height: none; }
|
||||
.sw-story-detail-comparison { overflow-x: auto; padding: 16px; background: var(--hm-sw-header-layer-menu-background); }
|
||||
.sw-story-detail-row { display: grid; grid-template-columns: 140px repeat(4, 180px); align-items: center; gap: 16px; min-width: 924px; padding: 12px 0; }
|
||||
.sw-story-detail-row > div { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
Reference in new issue
Block a user