feat(sw): integrate responsive home menu into header

- Merge SW / Home Menu Stories into SW / Header (sidebar: Home Menu; others verification-tagged)
- Home On: logo hover ring and header top/bottom #5F4B0F lines while the menu is open
- Measured More in SWHeader; home menu narrow widths use a More (...) section with a grouped
  text menu instead of horizontal scroll, placed right after the last visible section
- Action tiles no longer stay on after a click; 최근파일 stays Active only while its list is open
- More dropdown and recent files list never open together; list top joins the accent line
- Recent files preview falls back left / over the list at narrow widths (390px)
- Update Storybook, browser tests, docs, AI guides and QA evidence
This commit is contained in:
shkim4 committed 2026-10-19 22:46:28 +09:00
1 parent c542bdc282
commit 170a5c23ae
71 files changed
+12535 -150

No files matched your search

+49 -2
View File
@@ -6,12 +6,14 @@ import {
type SWHeaderColorMode, type SWHeaderColorOption, type SWHeaderColorValue, type SWHeaderDimensionStyleOption,
type SWHeaderDocument, type SWHeaderLayer, type SWHeaderLineStyleValue, type SWHeaderLineType, type SWHeaderLineWeight,
type SWHeaderMenuId, type SWHeaderProps, type SWHeaderTextStyleOption, type SWHeaderProperty, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyMenus, type SWHeaderPropertyShapeContent,
SWRecentFiles, SWToolButton,
} from "@hm-design/sw"
import "@hm-design/sw/styles.css"
import { SWHeaderMenuDetail } from "../../packages/sw/src/components/sw-header/components/SWHeaderDropdown"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import "./sw-stories.css"
import { SWHeaderOverview } from "./sw-header-overview"
import { figmaHomeMenuSections, figmaRecentFiles, withRecentFiles } from "./sw-home-menu-fixture"
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
// Owner: docs/sw/sw-header-design-spec.md (Figma rj6wpsfk24lOWvWiVRAANP, node 26:8944 "header_green").
@@ -179,12 +181,16 @@ function InteractiveHeader(props: Partial<SWHeaderProps>) {
)
}
// Logo menu of the Overview (Figma 26:10591 sample, same fixture as the Home Menu Story).
const OVERVIEW_HOME_MENU = withRecentFiles(figmaHomeMenuSections, <SWRecentFiles files={figmaRecentFiles} />)
export const Overview: Story = {
parameters: { controls: { disable: true } },
render: () => <SWHeaderOverview initial={{ ...figmaArgs, ...callbacks }}
fixtures={{ documents: SAMPLE_DOCUMENTS, layers: PROPERTY_LAYERS, colors: FIGMA_COLORS, userColors: FIGMA_USER_COLORS,
lineTypes: PROPERTY_LINE_TYPES, lineWeights: FIGMA_LINE_WEIGHTS, textStyles: FIGMA_TEXT_STYLES, dimensionStyles: FIGMA_DIMENSION_STYLES }}
propertyGroups={PROPERTY_GROUPS} propertyFixture={propertyFixture} propertySelection={propertySelection} barFixture={BAR_FIXTURE} />,
propertyGroups={PROPERTY_GROUPS} propertyFixture={propertyFixture} propertySelection={propertySelection} barFixture={BAR_FIXTURE}
homeMenuSections={OVERVIEW_HOME_MENU} />,
}
export const InteractiveStates: Story = {
@@ -776,6 +782,44 @@ function StyleDemo({ width, ...props }: StyleHostProps & { width: number }) {
const MORE_WIDTHS = [1920, 1800, 1799, 1440, 1180, 1179, 1080, 1079, 900, 899, 768, 640, 639, 520, 519, 390]
/**
* Measured More (2026-10-19): the header width comes from a range input and the host adds content the breakpoints
* do not know about (buttons next to the logo, extra tools). Parts move into More only while the real room is
* missing and return to their own place, in their own order, once it is back. Range / checkboxes are Storybook controls.
*/
function MeasuredMoreDemo() {
const [width, setWidth] = useState(1920)
const [leading, setLeading] = useState(true)
const [tools, setTools] = useState(true)
const [homeMenu, setHomeMenu] = useState(true)
const [barOpen, setBarOpen] = useState(false)
const [panelOpen, setPanelOpen] = useState(false)
const [log, setLog] = useState<string[]>([])
const record = (line: string) => setLog(lines => [...lines.slice(-7), line])
return (
<>
<div className="sw-story-measured-controls" role="group" aria-label="실측 More 조건">
<label>헤더 폭 <input type="range" min={390} max={1920} step={1} value={width} onChange={event => setWidth(Number(event.target.value))} aria-label="헤더 폭" /> <output data-measured-width="">{width}px</output></label>
<label><input type="checkbox" checked={leading} onChange={event => setLeading(event.target.checked)} />로고 오른쪽 버튼(leading)</label>
<label><input type="checkbox" checked={tools} onChange={event => setTools(event.target.checked)} />추가 도구(tools)</label>
<label><input type="checkbox" checked={homeMenu} onChange={event => setHomeMenu(event.target.checked)} />홈 메뉴(homeMenu)</label>
</div>
<p className="sw-story-log" aria-live="polite" data-callback-log="">Callback: {log.length ? log.join(" → ") : "없음"}</p>
<MenuFrame width={width}>
<StyleHeader onLog={record}
leading={leading ? <><SWToolButton appearance="document" onClick={() => record("leading: 저장")}>저장</SWToolButton><SWToolButton appearance="document" onClick={() => record("leading: 내보내기")}>내보내기</SWToolButton></> : undefined}
tools={tools ? <SWToolButton onClick={() => record("tools: 측정")}>측정</SWToolButton> : undefined}
homeMenu={homeMenu ? { sections: OVERVIEW_HOME_MENU, onItemSelect: event => record(`onItemSelect(${event.item.id})`) } : undefined}
onHomeMenuOpenChange={(open, reason) => record(`homeMenuOpen(${open}, ${reason})`)}
onPrint={() => record("onPrint")} onPopupGuide={() => record("onPopupGuide")}
propertyBarOpen={barOpen} onPropertyBarOpenChange={next => { setBarOpen(next); record(`propertyBarOpen(${next})`) }}
propertyPanelOpen={panelOpen} onPropertyPanelOpenChange={next => { setPanelOpen(next); record(`propertyPanelOpen(${next})`) }}
propertyPanel={{ selection: { id: "none", title: "선택된 객체 없음", kind: "none" } }} />
</MenuFrame>
</>
)
}
export const ResponsiveMore: Story = {
name: "Responsive More",
parameters: { controls: { disable: true } },
@@ -784,8 +828,11 @@ export const ResponsiveMore: Story = {
<StoryPageHeader
kicker={KICKER}
title="Responsive More — 대표 필드 전환"
description="좁은 헤더에서 더보기(…)의 필드 항목을 고르면, 그 필드가 객체 수정 영역의 첫 칸(레이어 필드 자리, 대표 슬롯)에 표시되고 Dropdown이 바로 아래에 열립니다. 레이어는 더보기로 이동해 다시 고를 수 있습니다. 메뉴 전환은 UI 상태일 뿐이며 데이터 Callback은 호출하지 않습니다(필드 클릭과 같은 on*Open 1회만). 기존 Breakpoint(1800 / 1180 / 1080 / 900 / 640 / 520)는 그대로입니다."
description="좁은 헤더에서 더보기(…)의 필드 항목을 고르면, 그 필드가 객체 수정 영역의 첫 칸(레이어 필드 자리, 대표 슬롯)에 표시되고 Dropdown이 바로 아래에 열립니다. 레이어는 더보기로 이동해 다시 고를 수 있습니다. 메뉴 전환은 UI 상태일 뿐이며 데이터 Callback은 호출하지 않습니다(필드 클릭과 같은 on*Open 1회만). 기존 Breakpoint(1800 / 1180 / 1080 / 900 / 640 / 520)는 그대로이고, 그 위에 실제 남은 공간을 측정해 부족한 메뉴만 더보기로 옮깁니다."
/>
<StorySection title="실제 사용 가능한 공간 기준 (폭 조절)" description="고정 폭만 보지 않고 객체 수정 영역에 실제로 남은 공간을 측정합니다. 로고 오른쪽 버튼·추가 도구처럼 Breakpoint가 모르는 요소가 공간을 쓰면, 표시할 수 없는 메뉴가 선·문자·치수스타일 → 그리기모드 → 속성Bar·속성창 → 색상 순(같은 단계는 오른쪽부터)으로 더보기에 들어갑니다. 공간이 다시 생기면 원래 자리·순서로 돌아옵니다. 로고 오른쪽 Open 버튼은 가장 먼저 옮겨지고 가장 늦게 돌아옵니다. Dropdown·홈 메뉴·더보기를 열고 닫아도 Header 요소의 위치와 너비는 바뀌지 않습니다.">
<MeasuredMoreDemo />
</StorySection>
<StorySection title="전환 · Callback 기록 (1440)" description="… → 선스타일: 대표 칸이 선스타일(미리보기·이름·가중치·스케일)로 바뀌고 선스타일 Dropdown이 열립니다. … → 문자스타일: 선스타일 Dropdown이 닫히고 대표 칸이 문자스타일로. … → 레이어: 원래 배치로 복귀. 값은 모두 유지됩니다.">
<StyleDemo width={1440} />
</StorySection>