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:
1 parent
c542bdc282
commit
170a5c23ae
71 files changed
+12535
-150
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user