import { useId, useLayoutEffect, useRef, useState, type CSSProperties } from "react" import { SWHeader, parseSWHeaderColor, type SWHeaderProps, type SWHeaderProperty, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyMenus, type SWHeaderColorMode, type SWHeaderLayer, type SWHeaderLineWeight, type SWHomeMenuSectionData, } from "@hm-design/sw" import { StoryPage, StoryPageHeader } from "@/components/story-page" import { showVerificationControls } from "./sw-story-flags" type Selection = "none" | "all" | "circle" | "expanded" interface OverviewProps { initial: SWHeaderProps fixtures: Required> propertyGroups: SWHeaderPropertyGroup[] propertyFixture: (selection: Selection) => SWHeaderProperty[] propertySelection: (selection: Selection) => SWHeaderPropertySelection barFixture: SWHeaderProperty[] /** Logo (home) menu of the SW normal mode: the logo opens and closes it (SWHeader homeMenu). */ homeMenuSections?: readonly SWHomeMenuSectionData[] } /** Storybook host only; dropdowns, validation, editing and focus use the public SW components. */ export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFixture, propertySelection, barFixture, homeMenuSections }: OverviewProps) { const controlId = useId() const slotRef = useRef(null) const headerRef = useRef(null) const [size, setSize] = useState({ width: 0, height: 0 }) const [panelLeft, setPanelLeft] = useState(4) const [example, setExample] = useState("circle") const getProperties = (selection: Selection) => propertyFixture(selection).map(property => ({ ...property, value: barFixture.find(item => item.id === property.id)?.value ?? property.value, })) const [properties, setProperties] = useState(() => getProperties("circle")) const [documents, setDocuments] = useState(fixtures.documents) const [documentId, setDocumentId] = useState(fixtures.documents[0]?.id) const [layers, setLayers] = useState(fixtures.layers) const [weights, setWeights] = useState(fixtures.lineWeights) const [weightId, setWeightId] = useState("w-default") const [colorMode, setColorMode] = useState(null) const [textStyle, setTextStyle] = useState(fixtures.textStyles[0]) const [dimensionStyle, setDimensionStyle] = useState(fixtures.dimensionStyles[0]) const [barOpen, setBarOpen] = useState(true) const [panelOpen, setPanelOpen] = useState(false) const [expanded, setExpanded] = useState([]) const [readOnly, setReadOnly] = useState(false) const [disabled, setDisabled] = useState(false) const [emptyShape, setEmptyShape] = useState(false) const [callbackLog, setCallbackLog] = useState([]) const counter = useRef(0) const record = (line: string) => { setCallbackLog(lines => [...lines.slice(-5), line]); console.info(`[SWHeader Overview] ${line}`) } const value = (id: string) => properties.find(property => property.id === id)?.value ?? "" const change = (id: string, next: string, event = "onPropertyChange") => { setProperties(items => items.map(property => property.id === id ? { ...property, value: next, mixed: false, ...(id === "color" ? { swatch: parseSWHeaderColor(next)?.hex } : {}), } : property)) if (id === "color") setColorMode(null) record(`${event}(${id}, ${next})`) } const onPropertyChange = (property: SWHeaderProperty, next: string) => change(property.id, next) const changeExample = (next: Selection) => { setExample(next); setProperties(getProperties(next)); setExpanded(next === "expanded" ? ["common"] : []) record(`selection(${next})`) } useLayoutEffect(() => { const slot = slotRef.current if (!slot) return const measure = () => { const bounds = slot.getBoundingClientRect() const width = Math.max(0, Math.min(bounds.width, bounds.height * 16 / 9)) setSize(previous => previous.width === width ? previous : { width, height: width * 9 / 16 }) } measure() const observer = new ResizeObserver(measure) observer.observe(slot) return () => observer.disconnect() }, []) useLayoutEffect(() => { const header = headerRef.current if (!header) return const measure = () => { const bounds = header.getBoundingClientRect() const trigger = header.querySelector("[data-property-panel-trigger]") const anchor = trigger?.getClientRects().length ? trigger : header.querySelector(".hm-sw-header__more-trigger") setPanelLeft(Math.max(4, Math.min((anchor?.getBoundingClientRect().left ?? bounds.left) - bounds.left, bounds.width - 318))) } measure() const observer = new ResizeObserver(measure) observer.observe(header) return () => observer.disconnect() }, []) const toggleLayer = (key: "visible" | "frozen" | "locked") => (layer: SWHeaderLayer, next: boolean) => { setLayers(items => items.map(item => item.id === layer.id ? { ...item, [key]: next } : item)) record(`layer.${key}(${layer.id}, ${next})`) } const chooseWeight = (weight: SWHeaderLineWeight) => { setWeightId(weight.id) // ByLayer/ByBlock select a mode, not a numeric property value (same policy as usePropertyMenus). if (weight.value !== undefined) change("weight", weight.value, "onLineWeightSelect") else record(`onLineWeightSelect(${weight.id})`) } const changeWeight = (weight: SWHeaderLineWeight, next: string) => { setWeights(items => items.map(item => item.id === weight.id ? { ...item, value: next } : item)) if (weightId === weight.id) change("weight", next, "onLineWeightValueChange") else record(`onLineWeightValueChange(${weight.id}, ${next})`) } const changeMode = (mode: SWHeaderColorMode) => { setColorMode(mode); record(`onColorModeChange(${mode})`) } const scale = value("scale") const menus: SWHeaderPropertyMenus = { layers, onLayerVisibilityChange: toggleLayer("visible"), onLayerFreezeChange: toggleLayer("frozen"), onLayerLockChange: toggleLayer("locked"), onLayerDetail: () => record("onLayerDetail"), lineTypes: fixtures.lineTypes, lineWeights: weights, activeLineWeightId: weightId, lineScale: scale, onLineWeightSelect: chooseWeight, onLineWeightValueChange: changeWeight, onLineScaleChange: next => change("scale", Number(next).toFixed(4), "onLineScaleChange"), onLineStyleDetail: () => record("onLineStyleDetail"), colors: fixtures.colors, userColors: fixtures.userColors, colorMode, onColorModeChange: changeMode, onColorPick: () => record("onColorPick"), onColorDetail: () => record("onColorDetail"), } const guardedProperties = properties.map(property => ({ ...property, disabled: disabled || property.disabled, readOnly: readOnly || property.readOnly })) const barProperties = guardedProperties.flatMap(property => { const bar = barFixture.find(item => item.id === property.id) return bar ? [{ ...bar, value: property.value, disabled: property.disabled, readOnly: property.readOnly }] : property.id.startsWith("preview-extra-") ? [property] : [] }) const layer = layers.find(item => item.id === value("layer")) const line = fixtures.lineTypes.find(item => item.id === value("line")) const color = parseSWHeaderColor(value("color")) const weight = weights.find(item => item.id === weightId) return {showVerificationControls() &&
시연 데이터
}
document.id === documentId)?.name ?? "열린 문서 없음"} onDocumentClick={() => record("onDocumentClick")} onDocumentMenuOpenChange={open => record(`documentOpen(${open})`)} onDocumentSelect={document => { setDocumentId(document.id); record(`onDocumentSelect(${document.id})`) }} onDocumentClose={document => { const next = documents.filter(item => item.id !== document.id); setDocuments(next) if (documentId === document.id) setDocumentId(next[0]?.id) record(`onDocumentClose(${document.id})`) }} onDocumentNew={() => { const id = `preview-document-${++counter.current}` setDocuments(items => [...items, { id, name: `새 문서 ${counter.current}.dwg` }]); setDocumentId(id); record("onDocumentNew") }} onDocumentOpen={() => record("onDocumentOpen")} layers={layers} layer={{ name: layer?.name ?? value("layer") }} activeLayerId={layer?.id} onLayerSelect={item => change("layer", item.id, "onLayerSelect")} onLayerVisibilityChange={menus.onLayerVisibilityChange} onLayerFreezeChange={menus.onLayerFreezeChange} onLayerLockChange={menus.onLayerLockChange} onLayerOpen={() => record("onLayerOpen")} onLayerDetail={menus.onLayerDetail} colors={fixtures.colors} userColors={fixtures.userColors} color={{ value: colorMode === "byLayer" ? "ByLayer" : colorMode === "byBlock" ? "ByBlock" : value("color"), swatch: color?.hex }} colorMode={colorMode} onColorSelect={item => change("color", item.value ?? parseSWHeaderColor(item.color)?.value ?? item.color, "onColorSelect")} onColorInput={next => change("color", next.value, "onColorInput")} onColorModeChange={changeMode} onColorOpen={() => record("onColorOpen")} onColorDetail={menus.onColorDetail} onColorPick={() => record("onColorPick")} lineTypes={fixtures.lineTypes} lineWeights={weights} activeLineTypeId={line?.id} activeLineWeightId={weightId} lineStyle={{ name: line?.name ?? value("line"), weight: weight && weight.value === undefined ? weight.name : value("weight"), scale }} onLineTypeSelect={item => change("line", item.id, "onLineTypeSelect")} onLineWeightSelect={chooseWeight} onLineWeightValueChange={changeWeight} onLineScaleChange={menus.onLineScaleChange} onLineStyleOpen={() => record("onLineStyleOpen")} onLineStyleDetail={menus.onLineStyleDetail} textStyles={fixtures.textStyles} activeTextStyleId={textStyle?.id} textStyle={textStyle} onTextStyleSelect={item => { setTextStyle(item); record(`onTextStyleSelect(${item.id})`) }} onTextStyleOpen={() => record("onTextStyleOpen")} onTextStyleDetail={() => record("onTextStyleDetail")} dimensionStyles={fixtures.dimensionStyles} activeDimensionStyleId={dimensionStyle?.id} dimensionStyle={dimensionStyle} onDimensionStyleSelect={item => { setDimensionStyle(item); record(`onDimensionStyleSelect(${item.id})`) }} onDimensionStyleOpen={() => record("onDimensionStyleOpen")} onDimensionStyleDetail={() => record("onDimensionStyleDetail")} propertyBarOpen={barOpen} onPropertyBarOpenChange={next => { setBarOpen(next); record(`propertyBarOpen(${next})`) }} propertyPanelOpen={panelOpen} onPropertyPanelOpenChange={next => { setPanelOpen(next); record(`propertyPanelOpen(${next})`) }} propertyBar={{ selection: propertySelection(example), properties: barProperties, onPropertyChange, readOnly, disabled }} propertyPanel={{ selection: propertySelection(example), properties: guardedProperties, groups: propertyGroups, expandedGroupIds: expanded, onExpandedGroupsChange: setExpanded, onPropertyChange, menus, shapeContent: emptyShape ? null : undefined, onOverviewClick: () => changeExample("all"), }} onHomeClick={() => record("onHomeClick")} homeMenu={homeMenuSections && { sections: homeMenuSections, onItemSelect: event => record(`onItemSelect(${event.item.id}, ${event.active})`) }} onHomeMenuOpenChange={(open, reason) => record(`homeMenuOpen(${open}, ${reason})`)} onPrint={() => record("onPrint")} onPopupGuide={() => record("onPopupGuide")} onMinimize={() => record("onMinimize")} onClose={() => record("onClose")} onWindowMoveStart={() => record("onWindowMoveStart")} />
{/* Callback record for the browser tests only; not shown on the page. */}
}