import { useId, useState } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" import { SWHeaderInput, SWHeaderPropertyPanel, SWPopup, SWPopupPropertyRow, SWPopupRow, SWPopupSection, SWSelect, SWSlider, SWTheme, type SWHeaderColorOption, type SWHeaderLayer, type SWHeaderProperty, type SWSliderAppearance, } from "@hm-design/sw" import "@hm-design/sw/styles.css" import { Button, Switch } from "@hm-design/ui" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import "./sw-stories.css" import "./sw-integrated-stories.css" // SWPopup (2026-10-19): the SW property window frame with free children. Public API only, like a consuming // project. HM UI components are placed inside to show that the content is not restricted. const KICKER = "HM DESIGN SYSTEM / SW" const LAYERS: SWHeaderLayer[] = [ { id: "0", name: "0", color: "#ffffff" }, { id: "wall", name: "WALL", color: "#ff4242" }, { id: "dim", name: "DIM", color: "#47af91" }, { id: "hatch", name: "HATCH", color: "#5aa0ff" }, ] const COLORS: SWHeaderColorOption[] = [ { id: "red", color: "#ff0000", label: "빨강" }, { id: "yellow", color: "#ffff00", label: "노랑" }, { id: "green", color: "#00ff00", label: "초록" }, { id: "blue", color: "#0000ff", label: "파랑" }, ] const UNITS = ["mm", "cm", "m"].map(value => ({ value })) const MENUS = { layers: LAYERS, colors: COLORS } const layerProperty = (value: string): SWHeaderProperty => ({ id: "layer", name: "레이어", value, type: "select", groupId: "popup", picker: "layer", options: LAYERS.map(({ id, name }) => ({ id, name })) }) const APPEARANCES = [{ value: "panel", label: "A · panel (기본)" }, { value: "property", label: "B · property" }] const meta = { title: "SW/Popup", parameters: { layout: "fullscreen", controls: { disable: true } }, } satisfies Meta export default meta type Story = StoryObj function useLog() { const [log, setLog] = useState([]) const view =

Callback: {log.length ? log.join(" → ") : "없음"}

return [view, (line: string) => setLog(lines => [...lines.slice(-9), line])] as const } function SettingsPopup({ record }: { record: (line: string) => void }) { const ids = { name: useId(), scale: useId(), unit: useId(), snap: useId() } const [name, setName] = useState("평면도-01") const [scale, setScale] = useState("100") const [layerId, setLayerId] = useState("wall") const [color, setColor] = useState("255,0,0") const [snap, setSnap] = useState(true) const [opacity, setOpacity] = useState(60) const [unit, setUnit] = useState("mm") return ( record("settings.onClose")} data-popup="settings"> { setName(next); record(`name=${next}`) }} /> { setScale(next); record(`scale=${next}`) }} /> { setUnit(next); record(`unit=${next}`) }} data-sw-select="unit" /> {/* 2026-10-20: popup layer / color controls are the property window pickers (SWPopupPropertyRow), not the SWHeader field dropdowns (header-only design). */} { setLayerId(value); record(`layer=${value}`) }} /> { setColor(value); record(`color=${value}`) }} /> {/* 2026-10-20: the gauge is the SW gauge (SWSlider). SW has no normal-mode switch, so 스냅 stays the HM UI Switch. */} { setSnap(next); record(`snap=${next}`) }} data-ui-switch="" /> record(`opacity=${next}`)} aria-label="투명도" data-popup-slider="" />
) } function ObjectInfoPopup({ record }: { record: (line: string) => void }) { const [x, setX] = useState("1200.5") return ( record("object.onClose")} data-popup="object"> { setX(next); record(`x=${next}`) }} /> record(`y=${next}`)} /> ) } function PanelWithChildren({ record }: { record: (line: string) => void }) { const [properties, setProperties] = useState([ { id: "layer", name: "레이어", value: "wall", type: "select", groupId: "common", picker: "layer", options: LAYERS.map(({ id, name }) => ({ id, name })) }, { id: "color", name: "색상", value: "255,66,66", type: "color", groupId: "common" }, { id: "width", name: "선가중치", value: "0.25", type: "number", groupId: "common" }, ]) return ( record("panel.onClose")} onPropertyChange={(property, value) => { setProperties(list => list.map(item => item.id === property.id ? { ...item, value } : item)); record(`${property.id}=${value}`) }}> record(`memo=${next}`)} /> ) } /** SWSlider: the same value and events in both looks; the host only picks `appearance`. */ function OpacityStylePopups({ record }: { record: (line: string) => void }) { const [opacity, setOpacity] = useState(40) const [appearance, setAppearance] = useState("panel") const slider = (look: SWSliderAppearance, label: string) => ( record(`opacity(${look})=${next}`)} /> ) return ( <> {slider("panel", "투명도 A")} {slider("property", "투명도 B")} { setOpacity(Math.min(100, Math.max(0, Number(next)))); record(`opacity=${next}`) }} /> { setAppearance(next as SWSliderAppearance); record(`appearance=${next}`) }} /> {slider(appearance, "투명도 (선택한 스타일)")} ) } function PopupPage() { const [log, record] = useLog() return ( {log} appearance="panel"(기본, A) = 지금까지 Popup에서 쓰던 게이지바(6px 트랙 · 18px 흰 Thumb · 녹색 Range, SWViewerSlider와 같은 모양). appearance="property"(B) = 속성창 4개 상태 비교의 게이지바(베이지 트랙 · 빨강 채움 · Figma Thumb). 값·onValueChange·onValueCommit·키보드는 같고 모양만 바뀝니다. 색은 SW 토큰(--hm-sw-slider-panel-*, --hm-sw-property-range-*)을 따릅니다.}> ) } export const CustomContent: Story = { name: "Custom Content", render: () => } function StackPage() { const [log, record] = useLog() const [open, setOpen] = useState(["a", "b", "c"]) const [layerB, setLayerB] = useState("wall") const close = (id: string) => { setOpen(list => list.filter(item => item !== id)); record(`close(${id})`) } const rows = Array.from({ length: 30 }, (_, index) => index + 1) return ( {log} {open.includes("a") && close("a")} data-popup="a">A} {open.includes("b") && close("b")} data-popup="b"> { setLayerB(value); record(`b.layer=${value}`) }} /> } {open.includes("c") && close("c")} data-popup="c" style={{ maxHeight: 360 }}> {rows.map(row => )} } ) } export const MultiplePopups: Story = { name: "Multiple Popups", render: () => }