- CENTER plain page layout and shorter reference demo notes (spec values moved to the CENTER README); chart specimen descriptions trimmed - Org chart page names and previews; SW width rows follow the 30px frame margin (1980 / 1065 / 727px) and the SW tests match the current layout - Story inventory regenerated and WORKING_CONTEXT updated
258 lines
14 KiB
TypeScript
258 lines
14 KiB
TypeScript
import type { ComponentType, ReactNode } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import {
|
|
CompactFacilityConnection,
|
|
FACILITY_CONFIGS,
|
|
LocalAssetFacility,
|
|
RoadsideFacilityTag,
|
|
TunnelFacility,
|
|
type FacilityConfig,
|
|
type RoadsideEntity,
|
|
} from "@hm-design/center-graphic"
|
|
import { LinkedFacilityReferenceDemo } from "@hm-design/center-graphic/reference"
|
|
import { StoryPage, StoryPageHeader } from "@/components/story-page"
|
|
import { withRepresentation } from "./center-shared"
|
|
|
|
// Owners: ui-viz docs/visualization/facility.md and facility-assets.md (128 approved SVG references, shipped unchanged).
|
|
// The display scale (Q2b) is HOLD and Facility has no Selected / Hover state.
|
|
const meta = {
|
|
title: "노선도/시설물",
|
|
id: "center-graphic-facility",
|
|
component: TunnelFacility,
|
|
parameters: { layout: "padded" },
|
|
} satisfies Meta<typeof TunnelFacility>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
// Common Story page. The CENTER representation wraps only the specimen, so the page title is never scaled with the graphic.
|
|
// 2026-11-13: no visible "개요" section title (사업명 page rule); the specimens keep their indent via the common `sb-page-indent`.
|
|
const page = (name: string, Demo: ComponentType): Story => ({
|
|
name,
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title={name} />
|
|
<div className="sb-page-indent">
|
|
{withRepresentation(() => <Demo />)}
|
|
</div>
|
|
</StoryPage>
|
|
),
|
|
})
|
|
|
|
// 터널 page layout (2026-11-13): composed here from the public API (same props as the package TunnelReferenceDemo) so the
|
|
// specimens can be grouped — one width-behaviour section with a Position Map column (SEPARATED → SHORT → MEDIUM → LONG) and an
|
|
// Actual / Detail column (SEPARATED → ACTUAL SHORT → REF → LONG), same steps on the same row (1 column below a 1180px content
|
|
// width), and the compact applied example full width below. The former standalone Position Map / Actual examples are the
|
|
// MEDIUM / ACTUAL REF rows (same default name and length). Package classes are reused so the common title row, 12px title gap and the
|
|
// 8px local scrollbar still apply; the layout lives in center-stories.css (`.sb-center-tunnel*`).
|
|
const TUNNEL_WIDTH_NAMES: [string, string, string][] = [
|
|
["SHORT", "터널", "L=80m"],
|
|
["MEDIUM", "대산2터널", "L=750m"],
|
|
["LONG", "서해안고속도로 대산연결 제2터널", "L=12,480m"],
|
|
]
|
|
const TUNNEL_ACTUAL_LABELS = ["ACTUAL SHORT", "ACTUAL REF", "ACTUAL LONG"]
|
|
|
|
function TunnelSample({ title, note, children }: { title: string; note?: string; children: ReactNode }) {
|
|
return (
|
|
<article className="tunnel-reference__sample">
|
|
<header>
|
|
<strong>{title}</strong>
|
|
{note ? <span>{note}</span> : null}
|
|
</header>
|
|
<div className="tunnel-reference__stage">{children}</div>
|
|
</article>
|
|
)
|
|
}
|
|
|
|
function TunnelSpecimens() {
|
|
return (
|
|
<div className="sb-center-tunnel">
|
|
<div className="tunnel-reference">
|
|
{/* 2026-11-13: no "너비 동작" title row; each column carries the common specimen title (English + Korean note). */}
|
|
<article className="tunnel-reference__sample">
|
|
<div className="tunnel-reference__stage sb-center-tunnel__width-stage">
|
|
<div className="sb-center-tunnel__width">
|
|
{(["positionMap", "actualDetail"] as const).map((variant) => (
|
|
<div className="tunnel-reference__width-tests sb-center-tunnel__width-group" key={variant}>
|
|
<header>
|
|
<strong>{variant === "positionMap" ? "Position Map" : "Actual / Detail"}</strong>
|
|
<span>{variant === "positionMap" ? "위치도" : "실사화"}</span>
|
|
</header>
|
|
<div>
|
|
<span>SEPARATED</span>
|
|
<TunnelFacility variant={variant} separated />
|
|
</div>
|
|
{TUNNEL_WIDTH_NAMES.map(([size, name, length], index) => (
|
|
<div key={size}>
|
|
<span>{variant === "positionMap" ? size : TUNNEL_ACTUAL_LABELS[index]}</span>
|
|
<TunnelFacility name={name} secondaryText={length} variant={variant} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<TunnelSample title="간략 적용 예제" note="노선 + 연결 + 터널 + 레이블">
|
|
<CompactFacilityConnection className="tunnel-applied-example" routeMode="inline" segmentShape="tunnel" sourceStatus="SOURCE_RECEIVED" label="노선과 터널 시설물의 간략 적용 예제">
|
|
<TunnelFacility name="대산2터널" secondaryText="L=750m" variant="positionMap" />
|
|
</CompactFacilityConnection>
|
|
</TunnelSample>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 교량 · 교차로 · 분기점 · 나들목 page layout (2026-11-13): the 터널 layout applied to the package FacilityReferenceDemo
|
|
// specimens, composed from the public API with the same props. Position Map | Actual / Detail columns (SEPARATED first, then
|
|
// SHORT / MEDIUM / LONG and ACTUAL SHORT / REF / LONG on the same rows), the compact applied example full width below. The former
|
|
// standalone Position Map / Actual examples are the MEDIUM / ACTUAL REF rows (default name and secondary text) and the former
|
|
// Separated example is each column's first row. `data-sb-compare-at` raises the 2-column breakpoint when the widest row needs it
|
|
// (교량 LONG 511px artwork: 1210px); without it the 터널 1180px rule applies.
|
|
function facilityWidthNames(config: FacilityConfig) {
|
|
return [
|
|
config.id === "bridge" ? "1교" : "신시",
|
|
config.defaultName,
|
|
config.id === "bridge" ? "서해안고속도로 대산연결 제2교량" : `서해안고속도로 ${config.defaultName}`,
|
|
]
|
|
}
|
|
|
|
function FacilitySpecimens({ config, compareAt }: { config: FacilityConfig; compareAt?: "1210" }) {
|
|
const widthNames = facilityWidthNames(config)
|
|
return (
|
|
<div className="sb-center-tunnel" data-sb-compare-at={compareAt}>
|
|
<div className="facility-reference" data-reference-facility={config.id}>
|
|
<article className="facility-reference__sample">
|
|
<div className="facility-reference__stage sb-center-tunnel__width-stage">
|
|
<div className="sb-center-tunnel__width">
|
|
{(["positionMap", "actualDetail"] as const).map((variant) => (
|
|
<div className="facility-reference__width-tests sb-center-tunnel__width-group" key={variant}>
|
|
<header>
|
|
<strong>{variant === "positionMap" ? "Position Map" : "Actual / Detail"}</strong>
|
|
<span>{variant === "positionMap" ? "위치도" : "실사화"}</span>
|
|
</header>
|
|
<div>
|
|
<span>SEPARATED</span>
|
|
<LocalAssetFacility config={config} variant={variant} separated />
|
|
</div>
|
|
{widthNames.map((name, index) => (
|
|
<div key={name}>
|
|
<span>{variant === "positionMap" ? ["SHORT", "MEDIUM", "LONG"][index] : `ACTUAL ${["SHORT", "REF", "LONG"][index]}`}</span>
|
|
<LocalAssetFacility config={config} name={name} variant={variant} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<TunnelSample
|
|
title="간략 적용 예제"
|
|
note={config.id === "interchange" ? "SOURCE_PENDING · 기존 Preview 전용; 정확한 IC 구성 추론 안 함" : "SOURCE_RECEIVED · 관계 비교, 정확한 픽셀 정합 아님"}
|
|
>
|
|
<CompactFacilityConnection
|
|
className={`facility-applied facility-applied--${config.routeMode}`}
|
|
routeMode={config.routeMode}
|
|
segmentShape={config.id === "bridge" ? "bridge" : undefined}
|
|
sourceStatus={config.id === "interchange" ? "SOURCE_PENDING" : "SOURCE_RECEIVED"}
|
|
label={`${config.label} 간략 적용 예제`}
|
|
>
|
|
<LocalAssetFacility config={config} />
|
|
</CompactFacilityConnection>
|
|
</TunnelSample>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 연계 노선 page layout (2026-11-13): the package RoadsideReferenceDemo specimens in the same 2-column structure and the same
|
|
// single-line row labels as the other facility pages (SHORT / MEDIUM / LONG, ACTUAL SHORT / REF / LONG; the former two-line
|
|
// "휴게소 · SHORT" meta is replaced — the entity stays in the tag text and its accessible name). Widest row 64px + 16px + 450px
|
|
// LONG tag, so the 터널 1180px rule applies. Separated exists for Actual / Detail only, so it is that column's first row; at
|
|
// 2 columns the Position Map column keeps an empty first row so the steps stay on the same rows, at 1 column the empty row is
|
|
// not rendered. The 연계 / 노선 맥락 example is full width below.
|
|
const ROADSIDE_TESTS: { size: string; actual: string; entity: RoadsideEntity; label: string }[] = [
|
|
{ size: "SHORT", actual: "ACTUAL SHORT", entity: "rest-area", label: "휴게소" },
|
|
{ size: "MEDIUM", actual: "ACTUAL REF", entity: "drowsy-shelter", label: "졸음쉼터" },
|
|
{ size: "LONG", actual: "ACTUAL LONG", entity: "drowsy-shelter", label: "서해안고속도로 대산방향 졸음쉼터" },
|
|
]
|
|
|
|
function RoadsideSpecimens() {
|
|
return (
|
|
<div className="sb-center-tunnel sb-center-roadside">
|
|
<div className="facility-reference roadside-reference" data-reference-facility="roadside">
|
|
<article className="facility-reference__sample">
|
|
<div className="facility-reference__stage sb-center-tunnel__width-stage">
|
|
<div className="sb-center-tunnel__width">
|
|
<div className="facility-reference__width-tests sb-center-tunnel__width-group sb-center-roadside__position">
|
|
<header><strong>Position Map</strong><span>위치도</span></header>
|
|
<div className="sb-center-roadside__blank" aria-hidden="true" />
|
|
{ROADSIDE_TESTS.map((test) => (
|
|
<div key={test.size} data-roadside-test={test.size.toLowerCase()}>
|
|
<span>{test.size}</span>
|
|
{/* The tag is itself a span: the wrapper keeps the row-label rule (`> div > span`) off the tag text. */}
|
|
<div><RoadsideFacilityTag entity={test.entity} label={test.label} /></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="facility-reference__width-tests sb-center-tunnel__width-group">
|
|
<header><strong>Actual / Detail</strong><span>실사화</span></header>
|
|
<div data-roadside-actual-test="separated">
|
|
<span>SEPARATED</span>
|
|
<RoadsideFacilityTag entity="drowsy-shelter" variant="actualDetail" separated />
|
|
</div>
|
|
{ROADSIDE_TESTS.map((test) => (
|
|
<div key={test.size} data-roadside-actual-test={test.size.toLowerCase()}>
|
|
<span>{test.actual}</span>
|
|
<RoadsideFacilityTag entity={test.entity} label={test.label} variant="actualDetail" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<TunnelSample title="연계 / 노선 맥락" note="노선 + 앵커 + 연결선 + 시설물 태그">
|
|
<CompactFacilityConnection className="facility-applied facility-applied--anchored roadside-applied" routeMode="anchored" sourceStatus="SOURCE_PENDING" label="졸음쉼터 연계 노선 간략 적용 예제">
|
|
<RoadsideFacilityTag entity="drowsy-shelter" />
|
|
</CompactFacilityConnection>
|
|
</TunnelSample>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// `name` is repeated as a literal because Storybook's static indexer cannot read it through page(); it drives the sidebar label.
|
|
export const Tunnel: Story = { ...page("터널", TunnelSpecimens), name: "터널" }
|
|
export const Bridge: Story = { ...page("교량", () => <FacilitySpecimens config={FACILITY_CONFIGS.bridge} compareAt="1210" />), name: "교량" }
|
|
export const Intersection: Story = { ...page("교차로", () => <FacilitySpecimens config={FACILITY_CONFIGS.intersection} />), name: "교차로" }
|
|
export const Junction: Story = { ...page("분기점", () => <FacilitySpecimens config={FACILITY_CONFIGS.junction} />), name: "분기점" }
|
|
export const Interchange: Story = { ...page("나들목", () => <FacilitySpecimens config={FACILITY_CONFIGS.interchange} />), name: "나들목" }
|
|
export const Roadside: Story = { ...page("휴게소 · 졸음쉼터", RoadsideSpecimens), name: "연계 노선" }
|
|
// Linked facilities are a standalone section heading followed by item cards.
|
|
// Keep the reference data/artwork and road-class grouping owned by the package.
|
|
// 2026-11-13: same `sb-page-indent` start as the other facility pages; the page class scopes the Linked-only CSS
|
|
// (hidden context notice / READY count / duplicate card name, card title and POSITION · ACTUAL label style).
|
|
export const Linked: Story = {
|
|
...page("연계 시설물", LinkedFacilityReferenceDemo),
|
|
name: "연계 시설물",
|
|
render: () => (
|
|
<StoryPage className="sb-center-linked-page">
|
|
<StoryPageHeader title="연계 시설물" />
|
|
<div className="sb-page-indent">
|
|
{withRepresentation(() => <LinkedFacilityReferenceDemo />)}
|
|
</div>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const TunnelPlayground: Story = {
|
|
tags: ["verification"],
|
|
name: "직접 테스트 (터널)",
|
|
decorators: [withRepresentation],
|
|
args: { name: "대산2터널", secondaryText: "L=750m", variant: "positionMap", separated: false },
|
|
argTypes: { variant: { control: "inline-radio", options: ["positionMap", "actualDetail"] } },
|
|
}
|