chore(sw): remove selected Storybook demos
This commit is contained in:
1 parent
92830fe11d
commit
50bf5bda8e
11 files changed
+2387
-265
No files matched your search
@@ -198,14 +198,6 @@ export const Overview: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
render: args => <Frame width={1920}><SWHeader {...args} /></Frame>,
|
||||
}
|
||||
|
||||
export const Controls: Story = {
|
||||
render: args => <SWHeader {...args} />,
|
||||
}
|
||||
|
||||
export const InteractiveStates: Story = {
|
||||
name: "Interactive States",
|
||||
parameters: { controls: { disable: true } },
|
||||
@@ -260,26 +252,6 @@ export const InteractiveStates: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
const RESPONSIVE_WIDTHS = [1920, 1800, 1799, 1770, 1600, 1440, 1280, 1180, 1179, 1080, 1079, 900, 899, 768, 640, 639, 560, 390]
|
||||
|
||||
export const Responsive: Story = {
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={KICKER}
|
||||
title="Responsive"
|
||||
description="필드는 모든 폭에서 Figma 폭을 유지하고, 공간이 부족하면 기능을 더보기로 옮깁니다. 헤더 폭 1800 미만: 선·문자·치수 스타일 / 1180 미만: 그리기모드 라벨 숨김 / 1080 미만: 속성Bar·속성창 / 900 미만: 색상 / 640 미만: 인쇄·팝업 가이드. 520 미만에서만 레이어명이 말줄임됩니다. 헤더 폭 기준이므로 각 프레임에서 그대로 확인할 수 있습니다."
|
||||
/>
|
||||
{RESPONSIVE_WIDTHS.map(width => (
|
||||
<StorySection key={width} title={`${width}px`}>
|
||||
<Frame width={width}><InteractiveHeader /></Frame>
|
||||
</StorySection>
|
||||
))}
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
/** Preserve GIF phase ratios/positions, with the user-requested 1640 -> 1400ms speed change. */
|
||||
const motionTime = (gifTime: number) => gifTime * 1400 / 1640
|
||||
const MOTION_STAGES = [
|
||||
@@ -320,58 +292,12 @@ function MotionSpecimen({ time }: { time?: number }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Live header with a callback / animation log, to check drag and clicks under the motion. */
|
||||
function MotionDemo({ width }: { width: number }) {
|
||||
const [log, setLog] = useState<string[]>([])
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const add = (line: string) => setLog(lines => [...lines.slice(-9), line])
|
||||
useEffect(() => {
|
||||
const area = ref.current?.querySelector(".hm-sw-header__motion")
|
||||
if (!area) return
|
||||
// One tooltip animation per play stands for the whole motion.
|
||||
const listen = (type: string, line: string) => {
|
||||
const handler = (event: Event) => { if ((event as AnimationEvent).animationName.endsWith("-tooltip")) add(line) }
|
||||
area.addEventListener(type, handler)
|
||||
return () => area.removeEventListener(type, handler)
|
||||
}
|
||||
const cleanups = [listen("animationstart", "motion start"), listen("animationiteration", "motion cycle"), listen("animationend", "motion end"), listen("animationcancel", "motion reset")]
|
||||
return () => cleanups.forEach(cleanup => cleanup())
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<p className="sw-story-log" aria-live="polite" data-callback-log="">기록: {log.length ? log.join(" → ") : "없음"}</p>
|
||||
<div ref={ref}>
|
||||
<Frame width={width}>
|
||||
<SWHeader
|
||||
{...figmaArgs}
|
||||
{...callbacks}
|
||||
onWindowMoveStart={() => { console.info("[SWHeader] windowMoveStart"); add("onWindowMoveStart") }}
|
||||
onMinimize={() => { callbacks.onMinimize(); add("onMinimize") }}
|
||||
onClose={() => { callbacks.onClose(); add("onClose") }}
|
||||
/>
|
||||
</Frame>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const WindowMoveMotion: Story = {
|
||||
name: "Window Move Motion",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={KICKER}
|
||||
title="Window Move Motion"
|
||||
description="ani_hover_move.gif의 이동 경로·구간 비율을 유지하고 사용자 요청으로 1640ms → 1400ms로 단축했습니다: 커서 상승 → 약 444ms 문구 등장 → 오른쪽 위로 이동 → 약 888ms부터 512ms 유지 → 1400ms 반복. 이탈 시 기본 SVG로 복귀하고 누르는 동안 모션 정지·문구 숨김을 유지합니다. 88 × 48 영역·줄무늬·SVG·Tooltip 규격은 그대로입니다."
|
||||
/>
|
||||
<StorySection title="GIF 주요 3상태 (정적, 3배 확대)" description="왼쪽부터 기존 Default · 문구 등장 약 444ms · 이동 완료 약 888ms. 창 제어 영역(창 이동 88 + 최소화·종료 64)만 공개 하위 컴포넌트로 표시합니다. Tooltip은 창 이동 영역 밖으로 나갈 수 있으며 포인터를 가로채지 않습니다.">
|
||||
<div className="sw-story-row">
|
||||
<MotionSpecimen />
|
||||
<MotionSpecimen time={motionTime(520)} />
|
||||
<MotionSpecimen time={motionTime(1040)} />
|
||||
</div>
|
||||
</StorySection>
|
||||
<StoryPageHeader kicker={KICKER} title="Window Move Motion" />
|
||||
<StorySection title="단계별 진행 (3배 확대)" description="CSS 애니메이션을 해당 시점에 멈춘 화면입니다(Storybook 전용 data-sb-hover + Web Animations API로 시간만 고정).">
|
||||
<div className="sw-story-stages">
|
||||
{MOTION_STAGES.map(stage => (
|
||||
@@ -382,21 +308,6 @@ export const WindowMoveMotion: Story = {
|
||||
))}
|
||||
</div>
|
||||
</StorySection>
|
||||
<StorySection title="실제 Hover · 창 이동 · 클릭 (1920)" description="Hover 동안 1.4초마다 반복(motion cycle)하고 이탈하면 복귀합니다(motion reset). 누르는 동안 모션 일시정지·Tooltip 숨김으로 Drag를 방해하지 않습니다. onWindowMoveStart는 기존대로 한 번 호출되며 최소화·종료 클릭도 그대로 전달됩니다.">
|
||||
<MotionDemo width={1920} />
|
||||
</StorySection>
|
||||
<StorySection title="이동 완료 실제 배치 (1920, 고정)" description="현재 이동 완료 약 888ms(GIF 1040ms와 같은 위치)를 고정합니다. Tooltip은 최소화·종료 일부에 겹치지만 헤더 안에 머물러 가로 스크롤을 만들지 않습니다.">
|
||||
<Frame width={1920}><MotionAt time={motionTime(1040)}><SWHeader {...figmaArgs} {...callbacks} /></MotionAt></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="헤더 1080 미만 (1079 고정 · 768 실제 Hover)" description="창 이동 영역이 24px로 줄어드는 단계에서는 아이콘 모션 공간이 없어 Tooltip만 표시합니다(영역 오른쪽, Figma hover2와 같은 관계 x = 영역 폭 + 1, y 11). 640 미만에서는 기존대로 창 이동 영역이 숨겨집니다.">
|
||||
<div className="sw-story-stack">
|
||||
<Frame width={1079}><MotionAt time={500}><SWHeader {...figmaArgs} {...callbacks} /></MotionAt></Frame>
|
||||
<MotionDemo width={768} />
|
||||
</div>
|
||||
</StorySection>
|
||||
<StorySection title="Reduced Motion" description="OS의 동작 줄이기(prefers-reduced-motion: reduce)가 켜져 있으면 기존 Default SVG를 유지하고, Hover 동안 정적 Tooltip만 표시됩니다. Drag 중에는 문구를 숨깁니다.">
|
||||
<p className="sw-story-log">OS 설정을 켠 뒤 위 '실제 Hover' 예제에 마우스를 올려 확인합니다. Storybook은 이 미디어 조건을 강제로 켤 수 없습니다.</p>
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
@@ -1093,47 +1004,6 @@ export const ResponsiveMore: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
export const TextStroke: Story = {
|
||||
name: "Text Stroke",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={KICKER}
|
||||
title="Text Stroke"
|
||||
description="Figma 26:8944와 메뉴의 텍스트 Stroke(모두 OUTSIDE) 중 검정 외곽선을 text-shadow로 표시합니다: 14 · 16px 값 = 검정 1.3px, 12px 라벨 = 검정 1px. 자기 색 Stroke(0.3px · '… 상세' 0.4px)는 2026-10-16 추가 수정으로 제거했습니다. 글꼴·크기·굵기·자간·기준선·말줄임은 그대로이며 Hover/Selected는 채움색만 바뀝니다."
|
||||
/>
|
||||
<StorySection title="구현 (1920)">
|
||||
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="3배 확대 — Default · Hover (고정) · Selected" description="12px 라벨 · 14px 값(한글·영문·숫자) · 16px 글꼴 미리보기 · 상세보기 토글.">
|
||||
<div className="sw-story-zoom sw-story-stack">
|
||||
<SWTheme className="sw-story-composed">
|
||||
<SWHeaderLayerControl value={{ name: "레이어_Layer_0123" }} />
|
||||
<SWHeaderColorControl value={figmaArgs.color!} />
|
||||
<SWHeaderTextStyle value={figmaArgs.textStyle!} />
|
||||
</SWTheme>
|
||||
<PinHover selectors={[".hm-sw-header__field"]}>
|
||||
<SWTheme className="sw-story-composed">
|
||||
<SWHeaderLayerControl value={{ name: "레이어_Layer_0123" }} />
|
||||
<SWHeaderColorControl value={figmaArgs.color!} />
|
||||
<SWHeaderTextStyle value={figmaArgs.textStyle!} />
|
||||
</SWTheme>
|
||||
</PinHover>
|
||||
<SWTheme className="sw-story-composed">
|
||||
<SWHeaderLayerControl value={{ name: "레이어_Layer_0123" }} selected />
|
||||
<SWHeaderColorControl value={figmaArgs.color!} selected />
|
||||
<SWHeaderProperties barOpen panelOpen />
|
||||
</SWTheme>
|
||||
</div>
|
||||
</StorySection>
|
||||
<StorySection title="긴 텍스트 말줄임" description="말줄임 텍스트는 Stroke가 잘리지 않도록 2px 여유(overflow-clip-margin)를 둡니다.">
|
||||
<Frame width={1440}><SWHeader {...figmaArgs} {...callbacks} documentTitle="C0020101-006-평면및종단면도(06)-수정본-최종-검토용-2026-10-16.dwg" layer={{ name: "Sample-매우-긴-레이어-이름-Ellipsis-확인용-Layer-Name" }} /></Frame>
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
/** Host-owned detail values for the editable input (the detail popup itself is not designed yet). */
|
||||
function InputDemo() {
|
||||
const [value, setValue] = useState("4.0526")
|
||||
@@ -1229,135 +1099,6 @@ export const LayerDetailInput: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
/* Dynamic data: every list is host data. Adding, removing, renaming, revaluing and reordering items
|
||||
needs no component change; ids keep each row's state; a removed current item leaves no selection. */
|
||||
type DynamicLists = {
|
||||
layers: SWHeaderLayer[]
|
||||
colors: SWHeaderColorOption[]
|
||||
userColors: SWHeaderColorOption[]
|
||||
lineTypes: SWHeaderLineType[]
|
||||
lineWeights: SWHeaderLineWeight[]
|
||||
textStyles: SWHeaderTextStyleOption[]
|
||||
dimensionStyles: SWHeaderDimensionStyleOption[]
|
||||
documents: SWHeaderDocument[]
|
||||
}
|
||||
type DynamicKey = keyof DynamicLists
|
||||
const DYNAMIC_INITIAL: DynamicLists = {
|
||||
layers: SAMPLE_LAYERS.slice(0, 4),
|
||||
colors: FIGMA_COLORS,
|
||||
userColors: FIGMA_USER_COLORS,
|
||||
lineTypes: FIGMA_LINE_TYPES,
|
||||
lineWeights: FIGMA_LINE_WEIGHTS,
|
||||
textStyles: FIGMA_TEXT_STYLES,
|
||||
dimensionStyles: FIGMA_DIMENSION_STYLES,
|
||||
documents: CURRENT_DOCUMENT,
|
||||
}
|
||||
const DYNAMIC_LABELS: Record<DynamicKey, string> = {
|
||||
layers: "레이어", colors: "기본 색상", userColors: "사용자 색상", lineTypes: "선 종류", lineWeights: "가중치",
|
||||
textStyles: "문자스타일", dimensionStyles: "치수스타일", documents: "문서",
|
||||
}
|
||||
/** New sample item of each list (`n` keeps ids unique). */
|
||||
const DYNAMIC_FACTORY: { [K in DynamicKey]: (n: number) => DynamicLists[K][number] } = {
|
||||
layers: n => ({ id: `new-layer-${n}`, name: `New-Layer-${n}`, color: "#ffa500" }),
|
||||
colors: n => ({ id: `color-${n}`, color: `hsl(${(n * 67) % 360} 80% 55%)`, label: `추가 색상 ${n}` }),
|
||||
userColors: n => ({ id: `user-color-${n}`, color: `hsl(${(n * 83) % 360} 70% 60%)`, label: `사용자 색상 추가 ${n}` }),
|
||||
lineTypes: n => ({ id: `line-${n}`, name: `DASH-${n}`, pattern: [12, 4, 12, 4] }),
|
||||
lineWeights: n => ({ id: `weight-${n}`, name: `${(0.05 * n).toFixed(2)} mm`, value: (0.05 * n).toFixed(2) }),
|
||||
textStyles: n => ({ id: `text-${n}`, font: "굴림", name: `Text-${n}` }),
|
||||
dimensionStyles: n => ({ id: `dim-${n}`, name: `DIM-${n}` }),
|
||||
documents: n => ({ id: `doc-${n}`, name: `Sample-New-${n}.dwg` }),
|
||||
}
|
||||
const renameFirst = <T extends { name?: string; label?: string }>(list: T[]) =>
|
||||
list.map((item, index) => (index !== 0 ? item : "name" in item ? { ...item, name: `${item.name}*` } : { ...item, label: `${item.label ?? ""}*` }))
|
||||
|
||||
function DynamicDataDemo() {
|
||||
const [lists, setLists] = useState(DYNAMIC_INITIAL)
|
||||
const [active, setActive] = useState({ layer: "layer-0", lineType: "continuous", lineWeight: "w-default", textStyle: "standard", dimensionStyle: "standard", document: "doc-current" })
|
||||
const [lineStyle, setLineStyle] = useState<SWHeaderLineStyleValue>(figmaArgs.lineStyle!)
|
||||
const [log, setLog] = useState<string[]>([])
|
||||
const counter = useRef(0)
|
||||
const add = (line: string) => setLog(lines => [...lines.slice(-9), line])
|
||||
const update = <K extends DynamicKey>(key: K, next: (list: DynamicLists[K]) => DynamicLists[K], line: string) => {
|
||||
setLists(current => ({ ...current, [key]: next(current[key]) }))
|
||||
add(line)
|
||||
}
|
||||
const keys = Object.keys(lists) as DynamicKey[]
|
||||
const currentDocument = lists.documents.find(item => item.id === active.document)
|
||||
return (
|
||||
<>
|
||||
<div className="sw-story-dynamic">
|
||||
{keys.map(key => (
|
||||
<div key={key} className="sw-story-controls" data-dynamic-list={key}>
|
||||
<strong>{DYNAMIC_LABELS[key]} ({lists[key].length})</strong>
|
||||
<button type="button" onClick={() => update(key, list => [...list, DYNAMIC_FACTORY[key](++counter.current)] as typeof list, `${key}: 추가`)}>추가</button>
|
||||
<button type="button" onClick={() => update(key, list => list.slice(1) as typeof list, `${key}: 첫 항목 삭제`)}>첫 항목 삭제</button>
|
||||
<button type="button" onClick={() => update(key, list => renameFirst(list as { name?: string; label?: string }[]) as typeof list, `${key}: 첫 항목 이름 변경`)}>이름 변경</button>
|
||||
<button type="button" onClick={() => update(key, list => [...list].reverse() as typeof list, `${key}: 순서 뒤집기`)}>순서 뒤집기</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="sw-story-controls">
|
||||
<strong>값 · 상태 변경</strong>
|
||||
<button type="button" onClick={() => update("lineWeights", list => list.map(item => (item.editable ? { ...item, value: "0.50" } : item)), "lineWeights: User = 0.50")}>User 가중치 0.50</button>
|
||||
<button type="button" onClick={() => update("layers", list => list.map((item, index) => (index === 1 ? { ...item, visible: item.visible === false } : item)), "layers: 2번째 표시 반전")}>2번째 레이어 표시 반전</button>
|
||||
<button type="button" onClick={() => update("lineTypes", list => list.map(item => (item.id === "continuous" ? { ...item, disabled: !item.disabled } : item)), "lineTypes: Continuous disabled 반전")}>Continuous Disabled</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="sw-story-log" aria-live="polite" data-callback-log="">기록: {log.length ? log.join(" → ") : "없음"}</p>
|
||||
<pre className="sw-story-layer-state" data-dynamic-state="">{`active: ${JSON.stringify(active)}`}</pre>
|
||||
<MenuFrame width={1920}>
|
||||
<SWHeader
|
||||
{...figmaArgs}
|
||||
{...callbacks}
|
||||
documentTitle={currentDocument?.name ?? "열린 문서 없음"}
|
||||
documents={lists.documents}
|
||||
activeDocumentId={active.document}
|
||||
onDocumentSelect={document => { add(`onDocumentSelect(${document.id})`); setActive(current => ({ ...current, document: document.id })) }}
|
||||
onDocumentClose={document => update("documents", list => list.filter(item => item.id !== document.id), `onDocumentClose(${document.id})`)}
|
||||
layers={lists.layers}
|
||||
activeLayerId={active.layer}
|
||||
onLayerSelect={layer => { add(`onLayerSelect(${layer.id})`); setActive(current => ({ ...current, layer: layer.id })) }}
|
||||
onLayerVisibilityChange={(layer, next) => update("layers", list => list.map(item => (item.id === layer.id ? { ...item, visible: next } : item)), `onLayerVisibilityChange(${layer.id}, ${next})`)}
|
||||
colors={lists.colors}
|
||||
userColors={lists.userColors}
|
||||
onColorSelect={option => add(`onColorSelect(${option.id})`)}
|
||||
lineStyle={lineStyle}
|
||||
lineTypes={lists.lineTypes}
|
||||
lineWeights={lists.lineWeights}
|
||||
activeLineTypeId={active.lineType}
|
||||
activeLineWeightId={active.lineWeight}
|
||||
onLineTypeSelect={type => { add(`onLineTypeSelect(${type.id})`); setActive(current => ({ ...current, lineType: type.id })); setLineStyle(current => ({ ...current, name: type.name })) }}
|
||||
onLineWeightSelect={weight => { add(`onLineWeightSelect(${weight.id})`); setActive(current => ({ ...current, lineWeight: weight.id })); setLineStyle(current => ({ ...current, weight: weight.value ?? weight.name })) }}
|
||||
onLineWeightValueChange={(weight, value) => update("lineWeights", list => list.map(item => (item.id === weight.id ? { ...item, value } : item)), `onLineWeightValueChange(${weight.id}, ${value})`)}
|
||||
onLineScaleChange={value => { add(`onLineScaleChange(${value})`); setLineStyle(current => ({ ...current, scale: value })) }}
|
||||
textStyles={lists.textStyles}
|
||||
activeTextStyleId={active.textStyle}
|
||||
onTextStyleSelect={style => { add(`onTextStyleSelect(${style.id})`); setActive(current => ({ ...current, textStyle: style.id })) }}
|
||||
dimensionStyles={lists.dimensionStyles}
|
||||
activeDimensionStyleId={active.dimensionStyle}
|
||||
onDimensionStyleSelect={style => { add(`onDimensionStyleSelect(${style.id})`); setActive(current => ({ ...current, dimensionStyle: style.id })) }}
|
||||
/>
|
||||
</MenuFrame>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const DynamicData: Story = {
|
||||
name: "Dynamic Data",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={KICKER}
|
||||
title="Dynamic Data"
|
||||
description="모든 Dropdown 항목은 호스트 데이터(id · 이름 · 값 · 순서 · disabled / editable)로 렌더링합니다. 아래 버튼으로 항목을 추가·삭제·이름 변경·값 변경·순서 변경해도 컴포넌트 소스 수정이 필요 없고, id 기준으로 해당 항목만 갱신됩니다. 현재 항목을 삭제하면 선택 표시가 없어지고(다음 항목 결정은 호스트) 필드는 값 Props를 그대로 표시합니다. 목록이 길면 Dropdown 안에서만 스크롤합니다."
|
||||
/>
|
||||
<StorySection title="항목 추가 · 삭제 · 이름 · 값 · 순서 변경" description="Dropdown을 연 채로 버튼을 눌러도 됩니다(버튼은 Storybook 전용 조작 UI, 헤더 스타일과 무관). 열린 목록은 버튼 클릭(바깥 클릭)으로 닫히므로 다시 열어 확인합니다.">
|
||||
<DynamicDataDemo />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
/* Additional review 2026-10-16: fixture values are host data, never package defaults. */
|
||||
const PROPERTY_GROUPS: SWHeaderPropertyGroup[] = [
|
||||
{ id: "common", collapsedCount: 6 },
|
||||
|
||||
Reference in new issue
Block a user