docs(storybook): tidy SW Storybook gauges, responsive rows and page titles
Deploy Storybook / deploy (push) Successful in 1m11s
Deploy Storybook / deploy (push) Successful in 1m11s
- SWPopup Custom Content and the SW Viewer options panel use SWSlider style B (appearance="property") only; the A/B comparison, the style select and the green panel gauge are gone (SWViewerSlider itself is unchanged). - SW Header / Viewer / Footer Responsive More: the 1920px row shows only when the content Inner is 1920px or wider (container query); Footer rows fit the page width without scrollers so ▲ More handles narrow widths. - Logo pages fit without scrollers; SW Header Logo Slot is "Logo" in the sidebar with the title "SW Header — 로고" (ID sw-header--logo-slot kept). - Property Panel (was "Property Panel · 4개 비교"): no Inner scroller, the page scrolls, 2 × 2 grid with one column below 720px. - SW Footer drops the "실제 Footer 조합 (동작)" heading; SW Viewer Header page is titled "SW Viewer Header" with 기본형 and 최소화 · 닫기 버튼 only. - Browser tests, inventory and README follow these changes; visual baselines are not updated.
This commit is contained in:
1 parent
ccafc6e332
commit
258e0a4b68
14 files changed
+123
-109
No files matched your search
@@ -29,6 +29,8 @@
|
||||
.sw-footer-story-row--tight { gap: 0; }
|
||||
.sw-footer-story-inline { display: inline-flex; flex-direction: column; padding: 0; }
|
||||
.sw-footer-story-width + .sw-footer-story-width { margin-top: 16px; }
|
||||
/* The hidden 1920px row (Responsive More, narrower than 1920px) takes its gap with it. */
|
||||
@container (max-width: 1919.98px) { .sw-story-responsive-wide + .sw-footer-story-width { margin-top: 0; } }
|
||||
.sw-footer-story-aux-compare { display: grid; grid-template-columns: repeat(auto-fill, minmax(560px, 1fr)); gap: 16px; }
|
||||
.sw-footer-story-aux-row { display: flex; align-items: center; gap: 12px; margin: 0; }
|
||||
.sw-footer-story-aux-row figcaption { flex: 0 0 120px; font-size: 12px; color: #3d4a45; }
|
||||
|
||||
@@ -29,7 +29,15 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
/** A program screen bottom: drawing area above the footer (the layout tabs sit on its lower edge). */
|
||||
function Screen({ width = 1920, canvas = 160, children }: { width?: number | string; canvas?: number; children: ReactNode }) {
|
||||
function Screen({ width = 1920, canvas = 160, fluid = false, children }: { width?: number | string; canvas?: number; fluid?: boolean; children: ReactNode }) {
|
||||
// `fluid` (2026-10-22, Responsive More): `width` is a maximum and there is no scroller, so the screen background and
|
||||
// the footer share the available width, commands that do not fit go to ▲ More, and an open menu is never clipped.
|
||||
if (fluid) return (
|
||||
<div className="sw-footer-story-screen" style={{ width: "100%", maxWidth: width }} data-width={width}>
|
||||
<div className="sw-footer-story-canvas" style={{ height: canvas }} aria-label="도면 영역 (예시)" />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="sw-story-scroller">
|
||||
<div className="sw-footer-story-screen" style={{ width }}>
|
||||
@@ -81,9 +89,11 @@ export const FullFooter: Story = {
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Footer"description="사용자 제공 실제 화면(2026-10-20) 구성: SWHeader + 도면 영역 + SWFooter(좌표 status, 바 안의 명령창 command, 오른쪽 명령 칸, Layout 탭). 모든 상태는 호스트(Story)가 Props로 소유하고, 컴포넌트는 Callback만 보냅니다." />
|
||||
<StorySection title="실제 Footer 조합 (동작)" description="명령 클릭 = 선택(다시 클릭 해제)·명령 기록 추가, 분류 탭(←/→, 바꿔도 칸 영역 고정), 빈 칸 + 클릭·Hover 가이드(영상 대신 Figma 6프레임 순환), 보조기능 토글(켜짐 = 주황 테두리·글자), 명령어 전체보기·▲ = 명령창 위로 펼치기, Layout 탭 선택·+·×·더블클릭/F2 이름 변경·Delete, 명령창 Enter 실행. 좁은 폭의 ▲ 확장 목록은 「화면 폭 변화」, Hover · Active · Selected · Disabled는 이 화면의 각 부품에서 동작합니다.">
|
||||
{/* 2026-10-22 (user request): no section heading; the live composition follows the page title. Behaviour: command
|
||||
click = select + history, category tabs, empty slot guide, aux toggles, ▲ = command window, Layout tabs, Enter runs. */}
|
||||
<section className="sb-page-section" aria-label="실제 Footer 조합 (동작)">
|
||||
<Workspace width={1920} />
|
||||
</StorySection>
|
||||
</section>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
@@ -379,13 +389,16 @@ export const Responsive: Story = {
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Responsive More" description="칸 영역은 바 폭으로만 정해지고 분류의 명령 수와 무관합니다(분류를 바꿔도 위치 고정). Figma에는 좁은 폭 정의가 없어 사용자 제공 실제 화면(2026-10-20)을 따릅니다: 빈 칸(+)이 먼저 줄고, 그래도 넘치는 명령은 마지막 칸의 ▲(ico_arrow)로 칸 위 패널에 펼침(▼ = 열림, Escape·바깥 클릭 닫힘). 분류 탭이 넘치면 ◀ ▶로 스크롤. 바 폭 1279px 이하에서 보조기능은 아이콘만(45 × 33). 바 안 명령창은 최소 280px." />
|
||||
{[1920, 1440, 1280, 950, 860, 760].map(width => (
|
||||
<div key={width} className="sw-footer-story-width">
|
||||
<StorySection title={`${width}px`}>
|
||||
<Screen width={width} canvas={90}><ReferenceFooter /></Screen>
|
||||
</StorySection>
|
||||
</div>
|
||||
))}
|
||||
{/* Same 1920px row rule as SW Header / SW Viewer Responsive More (sw-stories.css .sw-story-responsive). */}
|
||||
<div className="sw-story-responsive">
|
||||
{[1920, 1440, 1280, 950, 860, 760].map(width => (
|
||||
<div key={width} className={`sw-footer-story-width${width === 1920 ? " sw-story-responsive-wide" : ""}`}>
|
||||
<StorySection title={`${width}px`}>
|
||||
<Screen width={width} canvas={90} fluid><ReferenceFooter /></Screen>
|
||||
</StorySection>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -106,8 +106,12 @@ export const DetailButtons: Story = {
|
||||
</StoryPage>,
|
||||
}
|
||||
|
||||
/** Fixed-width frame. The outer scroller belongs to Storybook, never to the header. */
|
||||
function Frame({ width, children }: { width: number; children: ReactNode }) {
|
||||
/**
|
||||
* Fixed-width frame. The outer scroller belongs to Storybook, never to the header.
|
||||
* `fluid` (2026-10-22, Logo Slot): `width` is a maximum and there is no scroller; the header fits the available width.
|
||||
*/
|
||||
function Frame({ width, children, fluid = false }: { width: number; children: ReactNode; fluid?: boolean }) {
|
||||
if (fluid) return <div className="sw-story-frame" style={{ width: "100%", maxWidth: width }} data-frame-width={width}>{children}</div>
|
||||
return (
|
||||
<div className="sw-story-scroller">
|
||||
<div className="sw-story-frame" style={{ width }} data-frame-width={width}>{children}</div>
|
||||
@@ -447,26 +451,28 @@ const TALL_SAMPLE_LOGO = (
|
||||
</svg>
|
||||
)
|
||||
|
||||
// 2026-10-22 (user request): sidebar "Logo" (ID sw-header--logo-slot kept), page title "SW Header — 로고",
|
||||
// frames fit the page (no scroller).
|
||||
export const LogoSlot: Story = {
|
||||
name: "Logo Slot",
|
||||
name: "Logo",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
title="Logo Slot"
|
||||
title="SW Header — 로고"
|
||||
description="로고 영역의 위치·크기(홈 버튼 안 12 / 16, 32 × 17)는 공통 디자인이고, 로고 이미지는 각 SW 프로그램이 정합니다. Storybook 기본 로고는 임시 자산(sw-header-logo-placeholder.svg)이며 확정 로고가 아닙니다."
|
||||
/>
|
||||
<StorySection title="기본 임시 로고" description="logoSrc에 임시 SVG, logoAlt에 「임시 로고」를 전달합니다.">
|
||||
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} /></Frame>
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="사용자 지정 로고 — 가로형 이미지(logoSrc)" description="120 × 20 비율의 이미지도 로고 영역 안에 contain으로 맞춰지고 넘치지 않습니다.">
|
||||
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} logoSrc={WIDE_SAMPLE_LOGO} logoAlt="샘플 SW" /></Frame>
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} logoSrc={WIDE_SAMPLE_LOGO} logoAlt="샘플 SW" /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="사용자 지정 로고 — 세로형 React 요소(logo)" description="logo에 SVG 요소를 전달하면 logoSrc보다 우선합니다.">
|
||||
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} logo={TALL_SAMPLE_LOGO} logoAlt="샘플 SW" /></Frame>
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} logo={TALL_SAMPLE_LOGO} logoAlt="샘플 SW" /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="로고 미전달" description="logo·logoSrc가 없으면 로고를 그리지 않고 홈 버튼 배경과 전체 배치를 그대로 유지합니다. 접근성 이름은 「홈」입니다.">
|
||||
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} logoSrc={undefined} logoAlt={undefined} /></Frame>
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} logoSrc={undefined} logoAlt={undefined} /></Frame>
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
@@ -789,11 +795,15 @@ export const ResponsiveMore: Story = {
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Header — 화면 너비" />
|
||||
{MORE_WIDTHS.map(width => (
|
||||
<StorySection key={width} title={`${width}px`}>
|
||||
<MenuFrame width={width} fluid><StyleHeader /></MenuFrame>
|
||||
</StorySection>
|
||||
))}
|
||||
<div className="sw-story-responsive">
|
||||
{MORE_WIDTHS.map(width => (
|
||||
<div key={width} className={width === 1920 ? "sw-story-responsive-wide" : undefined}>
|
||||
<StorySection title={`${width}px`}>
|
||||
<MenuFrame width={width} fluid><StyleHeader /></MenuFrame>
|
||||
</StorySection>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
+20
-33
@@ -2,7 +2,7 @@ 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,
|
||||
type SWHeaderColorOption, type SWHeaderLayer, type SWHeaderProperty,
|
||||
} from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
import { Button, Switch } from "@hm-design/ui"
|
||||
@@ -26,7 +26,6 @@ const COLORS: SWHeaderColorOption[] = [
|
||||
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",
|
||||
@@ -71,13 +70,14 @@ function SettingsPopup({ record }: { record: (line: string) => void }) {
|
||||
<SWPopupPropertyRow property={{ id: "color", name: "색상", value: color, type: "color", groupId: "popup" }} menus={MENUS}
|
||||
onPropertyChange={(_, value) => { setColor(value); record(`color=${value}`) }} />
|
||||
</SWPopupSection>
|
||||
{/* 2026-10-20: the gauge is the SW gauge (SWSlider). SW has no normal-mode switch, so 스냅 stays the HM UI Switch. */}
|
||||
{/* 2026-10-20: the gauge is the SW gauge (SWSlider; 2026-10-22: style B, appearance="property", on this page).
|
||||
SW has no normal-mode switch, so 스냅 stays the HM UI Switch. */}
|
||||
<SWPopupSection title="스냅 · 투명도">
|
||||
<SWPopupRow label="스냅" htmlFor={ids.snap} field="none">
|
||||
<Switch id={ids.snap} checked={snap} onCheckedChange={next => { setSnap(next); record(`snap=${next}`) }} data-ui-switch="" />
|
||||
</SWPopupRow>
|
||||
<SWPopupRow label="투명도" field="none">
|
||||
<SWSlider value={opacity} min={0} max={100} step={10} onValueChange={setOpacity} onValueCommit={next => record(`opacity=${next}`)} aria-label="투명도" data-popup-slider="" />
|
||||
<SWSlider appearance="property" value={opacity} min={0} max={100} step={10} onValueChange={setOpacity} onValueCommit={next => record(`opacity=${next}`)} aria-label="투명도" data-popup-slider="" />
|
||||
</SWPopupRow>
|
||||
</SWPopupSection>
|
||||
<div className="sw-story-popup-actions">
|
||||
@@ -122,35 +122,22 @@ function PanelWithChildren({ record }: { record: (line: string) => void }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** SWSlider: the same value and events in both looks; the host only picks `appearance`. */
|
||||
function OpacityStylePopups({ record }: { record: (line: string) => void }) {
|
||||
/** SWSlider style B (appearance="property", 2026-10-22: the only gauge look on this page): value, entry and disabled. */
|
||||
function OpacityPopup({ record }: { record: (line: string) => void }) {
|
||||
const [opacity, setOpacity] = useState(40)
|
||||
const [appearance, setAppearance] = useState<SWSliderAppearance>("panel")
|
||||
const slider = (look: SWSliderAppearance, label: string) => (
|
||||
<SWSlider appearance={look} value={opacity} min={0} max={100} step={10} aria-label={label} data-sw-slider={look}
|
||||
onValueChange={setOpacity} onValueCommit={next => record(`opacity(${look})=${next}`)} />
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<SWPopup title="투명도 · 스타일 비교" data-popup="opacity-compare">
|
||||
<SWPopupSection title="A = panel · B = property (같은 값)">
|
||||
<SWPopupRow label="스타일 A" field="none" unit="%">{slider("panel", "투명도 A")}</SWPopupRow>
|
||||
<SWPopupRow label="스타일 B" field="none" unit="%">{slider("property", "투명도 B")}</SWPopupRow>
|
||||
<SWPopupRow label="값" field="compact" unit="%">
|
||||
<SWHeaderInput valueType="number" value={String(opacity)} aria-label="투명도 값" onCommit={next => { setOpacity(Math.min(100, Math.max(0, Number(next)))); record(`opacity=${next}`) }} />
|
||||
</SWPopupRow>
|
||||
</SWPopupSection>
|
||||
</SWPopup>
|
||||
<SWPopup title="투명도 · 선택 결과" data-popup="opacity-select">
|
||||
<SWPopupSection title="appearance">
|
||||
<SWPopupRow label="스타일" field="none">
|
||||
<SWSelect aria-label="게이지바 스타일" value={appearance} options={APPEARANCES} onValueChange={next => { setAppearance(next as SWSliderAppearance); record(`appearance=${next}`) }} />
|
||||
</SWPopupRow>
|
||||
<SWPopupRow label="투명도" field="none" unit="%">{slider(appearance, "투명도 (선택한 스타일)")}</SWPopupRow>
|
||||
<SWPopupRow label="비활성" field="none" unit="%" disabled><SWSlider appearance={appearance} value={opacity} disabled aria-label="투명도 (비활성)" /></SWPopupRow>
|
||||
</SWPopupSection>
|
||||
</SWPopup>
|
||||
</>
|
||||
<SWPopup title="투명도" data-popup="opacity">
|
||||
<SWPopupSection title="투명도">
|
||||
<SWPopupRow label="투명도" field="none" unit="%">
|
||||
<SWSlider appearance="property" value={opacity} min={0} max={100} step={10} aria-label="투명도 B" data-sw-slider="property"
|
||||
onValueChange={setOpacity} onValueCommit={next => record(`opacity(property)=${next}`)} />
|
||||
</SWPopupRow>
|
||||
<SWPopupRow label="값" field="compact" unit="%">
|
||||
<SWHeaderInput valueType="number" value={String(opacity)} aria-label="투명도 값" onCommit={next => { setOpacity(Math.min(100, Math.max(0, Number(next)))); record(`opacity=${next}`) }} />
|
||||
</SWPopupRow>
|
||||
<SWPopupRow label="비활성" field="none" unit="%" disabled><SWSlider appearance="property" value={opacity} disabled aria-label="투명도 (비활성)" /></SWPopupRow>
|
||||
</SWPopupSection>
|
||||
</SWPopup>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -168,9 +155,9 @@ function PopupPage() {
|
||||
<PanelWithChildren record={record} />
|
||||
</SWTheme>
|
||||
</StorySection>
|
||||
<StorySection title="투명도 게이지바 스타일 (SWSlider appearance)" description={<><code>appearance="panel"</code>(기본, A) = 지금까지 Popup에서 쓰던 게이지바(6px 트랙 · 18px 흰 Thumb · 녹색 Range, SWViewerSlider와 같은 모양). <code>appearance="property"</code>(B) = 속성창 4개 상태 비교의 게이지바(베이지 트랙 · 빨강 채움 · Figma Thumb). 값·<code>onValueChange</code>·<code>onValueCommit</code>·키보드는 같고 모양만 바뀝니다. 색은 SW 토큰(<code>--hm-sw-slider-panel-*</code>, <code>--hm-sw-property-range-*</code>)을 따릅니다.</>}>
|
||||
<StorySection title="투명도 게이지바 (SWSlider)">
|
||||
<SWTheme className="sw-story-popups" data-slider-popups="">
|
||||
<OpacityStylePopups record={record} />
|
||||
<OpacityPopup record={record} />
|
||||
</SWTheme>
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
|
||||
@@ -12,6 +12,6 @@ const meta = {
|
||||
export default meta
|
||||
|
||||
export const PropertyPanelCompare: StoryObj<typeof meta> = {
|
||||
name: "Property Panel · 4개 비교",
|
||||
name: "Property Panel", // 2026-10-22 (user request): was "Property Panel · 4개 비교"; ID unchanged
|
||||
render: () => <PropertyPanelComparePage />,
|
||||
}
|
||||
+10
-8
@@ -1,5 +1,9 @@
|
||||
/* Story canvas only: frames that simulate a header width. Never styles the header itself. */
|
||||
.sw-story-scroller { max-width: 100%; overflow-x: auto; }
|
||||
/* Responsive More pages (2026-10-22): the 1920px row shows only when the content Inner itself is 1920px or wider
|
||||
(container width, not the browser width); otherwise the whole row — title, header, background, margin — is hidden. */
|
||||
.sw-story-responsive { container-type: inline-size; }
|
||||
@container (max-width: 1919.98px) { .sw-story-responsive-wide { display: none; } }
|
||||
.sw-story-frame { background: #060606; }
|
||||
/* Shared drawing-area canvas of the SW Stories (2026-10-19): the SW/Viewer black grid — 40px cells, 1px lines of
|
||||
HM green #266651 at 18%, on #0A0A0B. Used behind the SW Header work area and dropdown room and the viewer frames. */
|
||||
@@ -33,14 +37,12 @@
|
||||
.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; }
|
||||
/* Property panel 4-state comparison: 2 columns × 2 rows at the panels' own size (314px wide, never shrunk).
|
||||
2026-10-22 (user request): no Inner scroller — the page scrolls normally, and a screen too narrow for two
|
||||
panels (2 × 314 + 24px gap + gutters) shows one column instead of scrolling sideways. */
|
||||
.sw-story-compare-page .sw-story-log { margin-top: 12px; }
|
||||
.sw-story-compare.sw-story-compare--grid { display: grid; grid-template-columns: repeat(2, max-content); align-items: start; gap: 24px; }
|
||||
@media (max-width: 719.98px) { .sw-story-compare.sw-story-compare--grid { grid-template-columns: 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; }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// UI state lives here (like a consuming program); no DWG/DXF engine — commands are logged, not executed.
|
||||
import { useMemo, useState } from "react"
|
||||
import {
|
||||
SWLayerVisibilityIcon, SWViewerBadge, SWViewerButton, SWViewerHeaderButton, SWViewerStatus, SWViewerField, SWViewerHint, SWViewerInput, SWViewerPanel, SWViewerSection, SWViewerSlider, SWViewerSwitch,
|
||||
SWLayerVisibilityIcon, SWViewerBadge, SWViewerButton, SWViewerHeaderButton, SWViewerStatus, SWViewerField, SWViewerHint, SWViewerInput, SWViewerPanel, SWViewerSection, SWSlider, SWViewerSwitch,
|
||||
type SWHeaderDocument, type SWViewerCommand, type SWViewerHeaderGroup, type SWViewerHeaderProps, type SWViewerPanelProps,
|
||||
} from "@hm-design/sw"
|
||||
import { Info, Layers, List, MousePointerClick, RotateCcw, Scan, SlidersHorizontal, ZoomIn } from "lucide-react"
|
||||
@@ -148,10 +148,12 @@ export function LayerPanel({ demo, ...placement }: { demo: ViewerDemo } & PanelP
|
||||
)
|
||||
}
|
||||
|
||||
// 2026-10-22: the viewer gauges use SWSlider style B (appearance="property", as SW/Popup › Custom Content), not
|
||||
// SWViewerSlider (fixed to the green panel look).
|
||||
function SliderRow({ label, unit, value, min, max, step, disabled, onChange }: { label: string; unit?: string; value: number; min: number; max: number; step: number; disabled?: boolean; onChange: (value: number) => void }) {
|
||||
return (
|
||||
<SWViewerField label={label} unit={unit} layout="stacked" disabled={disabled}
|
||||
below={<SWViewerSlider aria-label={label} value={value} min={min} max={max} step={step} onValueChange={onChange} />}>
|
||||
below={<SWSlider appearance="property" aria-label={label} value={value} min={min} max={max} step={step} onValueChange={onChange} />}>
|
||||
{id => <SWViewerInput id={id} type="number" min={min} max={max} step={step} value={value} onChange={event => { const next = Number(event.currentTarget.value); if (Number.isFinite(next)) onChange(next) }} />}
|
||||
</SWViewerField>
|
||||
)
|
||||
|
||||
@@ -86,11 +86,13 @@ export const ViewerHeader: Story = {
|
||||
name: "Viewer Header",
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Viewer — SWViewerHeader" />
|
||||
<StorySection title="기본형 (1440px, 도면 열림)">
|
||||
{/* 2026-10-22 (user request): page title and the two sections only. The minimize / close version keeps
|
||||
SWHeaderWindowControls at the right end of `trailing`; the program owns the real window actions. */}
|
||||
<StoryPageHeader title="SW Viewer Header" />
|
||||
<StorySection title="기본형">
|
||||
<ViewerScreen />
|
||||
</StorySection>
|
||||
<StorySection title="최소화 · 닫기 버튼 (1440px, 도면 열림)" description="SWHeader의 최소화·닫기 버튼(SWHeaderWindowControls)을 trailing 오른쪽 끝에 그대로 둔 버전입니다. 실제 창 최소화·닫기는 프로그램이 처리합니다.">
|
||||
<StorySection title="최소화 · 닫기 버튼">
|
||||
<div data-viewer-window-controls=""><ViewerScreen windowControls /></div>
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
@@ -335,12 +337,12 @@ export const Responsive: Story = {
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Viewer — 화면 너비" />
|
||||
<Log lines={demo.log} />
|
||||
<div className="sw-story-stack">
|
||||
<div className="sw-story-stack sw-story-responsive">
|
||||
{[1920, 1440, 1280, 950, 860, 760, 520].map(width => (
|
||||
<StorySection key={width} title={`${width}px`}>
|
||||
<div key={width} className={width === 1920 ? "sw-story-responsive-wide" : undefined}><StorySection title={`${width}px`}>
|
||||
{/* The row width is a maximum (2026-10-22): on a narrower screen the header shrinks with it instead of scrolling. */}
|
||||
<div className="sw-story-scroller sw-viewer-story-menu-row"><div style={{ width: "100%", maxWidth: width }} data-width={width}><SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} /></div></div>
|
||||
</StorySection>
|
||||
</StorySection></div>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
@@ -404,9 +406,10 @@ export const Logo: Story = {
|
||||
<div className="sw-story-stack">
|
||||
{rows.map(row => (
|
||||
<StorySection key={row.title} title={row.title}>
|
||||
<div className="sw-story-scroller"><div style={{ width: 1440 }} data-logo-case={row.title}>
|
||||
{/* 1440px is a maximum (2026-10-22): no scroller; the header fits the available width. */}
|
||||
<div style={{ width: "100%", maxWidth: 1440 }} data-logo-case={row.title}>
|
||||
<SWViewerHeader {...row.props} documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />
|
||||
</div></div>
|
||||
</div>
|
||||
</StorySection>
|
||||
))}
|
||||
<Log lines={demo.log} />
|
||||
|
||||
Reference in new issue
Block a user