Initial commit: HMDesignSystem baseline
This commit is contained in:
commit
b6a0a61103
486 files changed
+68432
No files matched your search
@@ -0,0 +1,78 @@
|
||||
import type { ComponentType } from "react"
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { TunnelFacility } from "@hm-design/center-graphic"
|
||||
import {
|
||||
BridgeReferenceDemo,
|
||||
IntersectionReferenceDemo,
|
||||
InterchangeReferenceDemo,
|
||||
JunctionReferenceDemo,
|
||||
LinkedFacilityReferenceDemo,
|
||||
RoadsideReferenceDemo,
|
||||
TunnelReferenceDemo,
|
||||
} from "@hm-design/center-graphic/reference"
|
||||
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import { CENTER_BOUNDARY, 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: "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.
|
||||
const page = (name: string, Demo: ComponentType): Story => ({
|
||||
name,
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={STORY_KICKER.center}
|
||||
title={name}
|
||||
description="CENTER 시설물 그래픽입니다. 승인 SVG Reference를 위치도(Position Map)와 실사화(Actual / Detail)로 표시합니다."
|
||||
/>
|
||||
<StorySection title="Overview" description={CENTER_BOUNDARY}>
|
||||
{withRepresentation(() => <Demo />)}
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
})
|
||||
|
||||
export const Tunnel: Story = page("터널", TunnelReferenceDemo)
|
||||
export const Bridge: Story = page("교량", BridgeReferenceDemo)
|
||||
export const Intersection: Story = page("교차로", IntersectionReferenceDemo)
|
||||
export const Junction: Story = page("분기점", JunctionReferenceDemo)
|
||||
export const Interchange: Story = page("나들목", InterchangeReferenceDemo)
|
||||
export const Roadside: Story = page("휴게소 · 졸음쉼터", RoadsideReferenceDemo)
|
||||
// Linked facilities are a standalone section heading followed by item cards.
|
||||
// Keep the reference data/artwork and road-class grouping owned by the package.
|
||||
export const Linked: Story = {
|
||||
...page("연계 시설물", LinkedFacilityReferenceDemo),
|
||||
render: () => (
|
||||
<StoryPage className="sb-center-linked-page">
|
||||
<StoryPageHeader
|
||||
kicker={STORY_KICKER.center}
|
||||
title="연계 시설물"
|
||||
description={
|
||||
<>
|
||||
CENTER 시설물 그래픽입니다. 승인 SVG Reference를 위치도(Position Map)와 실사화(Actual / Detail)로 표시합니다.
|
||||
<br />
|
||||
{CENTER_BOUNDARY}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{withRepresentation(() => <LinkedFacilityReferenceDemo />)}
|
||||
</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"] } },
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { CenterInformationDisplayReferenceDemo } from "@hm-design/center-graphic/reference"
|
||||
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import { CENTER_BOUNDARY, withRepresentation } from "./center-shared"
|
||||
|
||||
// Owner: ui-viz docs/patterns/center-information-presentation.md and center-information-{box,table,emphasis}.md.
|
||||
// Single-line ellipsis is resolved; multi-line overflow, exact placement and the presentation scale (Q3) are not.
|
||||
const meta = {
|
||||
title: "Center Graphic/Information",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
} satisfies Meta
|
||||
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.
|
||||
export const Information: Story = {
|
||||
name: "Information",
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={STORY_KICKER.center}
|
||||
title="Information"
|
||||
description="CENTER 정보 표시(Box · Table · Emphasis) Reference입니다. 단일행 말줄임만 해결된 상태이며 배율(Q3)은 정해지지 않았습니다."
|
||||
/>
|
||||
<StorySection title="Overview" description={CENTER_BOUNDARY}>
|
||||
{withRepresentation(() => (
|
||||
<div className="sb-center-information">
|
||||
<CenterInformationDisplayReferenceDemo />
|
||||
</div>
|
||||
))}
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import { CENTER_BOUNDARY, CenterFrame, SCALE_NOTE, StatusTable } from "./center-shared"
|
||||
|
||||
// Mirrors the ui-viz docs/governance/center-domain.md §5 registry rows. The values stay in the owner documents.
|
||||
const STATUS: [string, string, string, string][] = [
|
||||
["Representation Scale (7680×2160 = 1.00 / 3840×2160 = 0.75)", "CONFIRMED_BY_DESIGN", "foundation/center-scale.md §3.5", "CenterRepresentation. 다른 viewport는 PREVIEW_ONLY 1.00"],
|
||||
["FACILITY_PRESENTATION_SCALE (Q2b)", "HOLD — OWNER_DECISION_REQUIRED", "visualization/facility.md §14.1", "구현하지 않음. scale 값·preset을 만들지 않음"],
|
||||
["INFORMATION_PRESENTATION_SCALE (Q3)", "HOLD — OWNER_DECISION_REQUIRED", "patterns/center-information-presentation.md §5.7", "구현하지 않음"],
|
||||
["Title Resolution B 수치 / per-Type palette", "TBD / SCOPED_DESIGN_APPROVAL", "components/center-title.md §6.7 · §8.1", "Reference 그대로"],
|
||||
["Information body-kind · padding · multi-line · KD-1/KD-4", "TBD", "patterns/center-information-presentation.md §5.3 · §8", "단일행 말줄임(해결)만 표시"],
|
||||
["Information Box badge anchor · §5.2.2 ink-position", "OWNER_DECISION_REQUIRED / Candidate (authored)", "components/center-information-box.md §5.2", "Reference 그대로. 승격 없음"],
|
||||
["Facility §12.24 rules (ink basis · LABEL_REGION …)", "OWNER_REVIEW (Q2c)", "visualization/facility.md §12.24", "Reference 동작 표시. 검증 완료로 표현하지 않음"],
|
||||
["Facility asset manifest (128 references)", "Confirmed current mapping · stable-ID/alias policy PARTIAL", "visualization/facility-assets.md", "승인 SVG를 바이트 그대로 사용"],
|
||||
["Spatial · Title · Information source SVG 65종", "CANONICAL_ASSET_CANDIDATE (64 RECHECK · 1 ADOPTED)", "visualization/spatial-title-information-assets.md", "runtime 미사용 · 규칙 기반 재구성"],
|
||||
["Route layer order Shadow → Outline → Primary Stroke", "CONFIRMED", "visualization/composition.md §5.1 · route.md §7.10", "Spatial 노선"],
|
||||
["전체 Visualization layer stack · collision algorithm", "Observed Candidate / TBD", "visualization/composition.md §6 · §7.5", "구현하지 않음"],
|
||||
["Railway outline width · line join", "TBD", "visualization/route.md §4.1 · §6.4", "Reference 값 표시, 확정 아님"],
|
||||
["Spatial §6.2 tile layer · 읍·면·동 radius · §8.1 paint order", "Candidate (authored) · OWNER_REVIEW (R2)", "visualization/spatial-map.md", "Reference 그대로"],
|
||||
["Color Candidate application", "TBD", "foundation/center-color.md §6–7", "Reference 그대로"],
|
||||
]
|
||||
|
||||
const meta = { title: "Center Graphic/Overview", parameters: { layout: "fullscreen", controls: { disable: true } } } satisfies Meta
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Overview: Story = {
|
||||
name: "Overview",
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={STORY_KICKER.center} title="Overview" description={CENTER_BOUNDARY} />
|
||||
<CenterFrame>
|
||||
<div className="sb-center-overview">
|
||||
<StorySection title="Representation Scale" description="제작 해상도별 CENTER 그래픽 표시 배율입니다.">
|
||||
<p className="sb-center-note">{SCALE_NOTE}</p>
|
||||
</StorySection>
|
||||
<StorySection title="Status" description="ui-viz center-domain.md §5 registry의 항목별 상태와 이 Storybook에서의 표현입니다.">
|
||||
<div className="sb-center-scroll">
|
||||
<StatusTable rows={STATUS} />
|
||||
</div>
|
||||
</StorySection>
|
||||
</div>
|
||||
</CenterFrame>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { ReactNode } from "react"
|
||||
import { CenterRepresentation, type CenterTargetResolution } from "@hm-design/center-graphic"
|
||||
import "./center-stories.css"
|
||||
|
||||
// CENTER is a separate domain from the UI Component System. These stories present the packaged CENTER components and the
|
||||
// reference demo pages; they do not own, create or promote CENTER contract values (see docs/center-graphic/README.md).
|
||||
export const CENTER_BOUNDARY =
|
||||
"CENTER Web Reference 렌더링입니다. Canonical 값·상태는 ui-viz CENTER owner 문서가 소유하며 HOLD / TBD / Candidate / OWNER_DECISION_REQUIRED를 이 화면이 확정하지 않습니다."
|
||||
|
||||
export const SCALE_NOTE =
|
||||
"제작 대상이 7680 × 2160이면 CENTER 그래픽을 100%, 3840 × 2160이면 75%로 표시합니다. Storybook 화면 크기는 배율을 정하지 않으며, 다른 제작 해상도의 배율은 정해지지 않았습니다. 제작 대상은 주소에 centerResolution=3840x2160 을 붙여 바꿉니다."
|
||||
|
||||
export const CenterFrame = ({ children }: { children: ReactNode }) => (
|
||||
<div className="sb-center" lang="ko">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
// A shareable Storybook URL states the production target explicitly; the default is the 7680 reference.
|
||||
export const centerTargetResolution = (): CenterTargetResolution => {
|
||||
const search = window.parent !== window ? window.parent.location.search : window.location.search
|
||||
return new URLSearchParams(search).get("centerResolution") === "3840x2160" ? "3840x2160" : "7680x2160"
|
||||
}
|
||||
|
||||
export const withRepresentation = (Story: () => ReactNode) => (
|
||||
<CenterFrame>
|
||||
<CenterRepresentation targetResolution={centerTargetResolution()}>
|
||||
<Story />
|
||||
</CenterRepresentation>
|
||||
</CenterFrame>
|
||||
)
|
||||
|
||||
export function BoundaryNote({ children }: { children?: ReactNode }) {
|
||||
return <p className="sb-center-note">{children ?? CENTER_BOUNDARY}</p>
|
||||
}
|
||||
|
||||
export function StatusTable({ rows, label = "CENTER 상태" }: { rows: [string, string, string, string][]; label?: string }) {
|
||||
return (
|
||||
<table className="sb-center-status" aria-label={label}>
|
||||
<thead>
|
||||
<tr><th>항목</th><th>상태</th><th>Owner (ui-viz)</th><th>이 Storybook에서의 표현</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map(([item, status, owner, presentation]) => (
|
||||
<tr key={item} data-center-status={status}>
|
||||
<td>{item}</td><td><code>{status}</code></td><td>{owner}</td><td>{presentation}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { RouteLine } from "@hm-design/center-graphic"
|
||||
import { CenterSpatialInformationReferenceDemo } from "@hm-design/center-graphic/reference"
|
||||
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import { CENTER_BOUNDARY, withRepresentation } from "./center-shared"
|
||||
|
||||
// Owners: ui-viz docs/visualization/route.md, spatial-map.md, spatial-section.md, composition.md.
|
||||
// Route layer order Shadow -> Outline -> Primary Stroke is CONFIRMED; the whole-map layer order is Candidate and line join is TBD.
|
||||
const meta = {
|
||||
title: "Center Graphic/Spatial",
|
||||
component: RouteLine,
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
} satisfies Meta<typeof RouteLine>
|
||||
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.
|
||||
export const Spatial: Story = {
|
||||
name: "Spatial",
|
||||
render: () => (
|
||||
<StoryPage className="sb-center-spatial-page">
|
||||
<StoryPageHeader
|
||||
kicker={STORY_KICKER.center}
|
||||
title="Spatial"
|
||||
description="CENTER 노선·공간 정보 Reference입니다. 노선 레이어 순서(Shadow → Outline → Primary Stroke)는 확정이며, 전체 지도 레이어 순서는 Candidate, line join은 TBD입니다."
|
||||
/>
|
||||
<StorySection title="Overview" description={CENTER_BOUNDARY}>
|
||||
{withRepresentation(() => <CenterSpatialInformationReferenceDemo />)}
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/* Storybook review chrome only; never select the CENTER artwork itself.
|
||||
Existing package demo classes are adapters to this common card frame, so
|
||||
@hm-design/center-graphic and its /reference API remain unchanged. */
|
||||
.sb-center {
|
||||
--color-fg-secondary: #6e6759;
|
||||
--color-stroke-subtle: #e0dace;
|
||||
--color-bg-content: #f5f0e5;
|
||||
--color-action-primary: #123328;
|
||||
/* Measured from TunnelReferenceDemo at 1280px before consolidation.
|
||||
Local presentation values, not new Foundation / runtime theme tokens. */
|
||||
--sb-center-card-background: rgba(217, 217, 217, .48);
|
||||
--sb-center-card-border: 1px solid rgba(64, 5, 0, .14);
|
||||
--sb-center-card-radius: 8px;
|
||||
--sb-center-header-background: rgba(255, 255, 255, .62);
|
||||
--sb-center-header-border: 1px solid rgba(64, 5, 0, .12);
|
||||
--sb-center-header-padding: 8px 14px;
|
||||
--sb-center-header-gap: 12px;
|
||||
--sb-center-content-padding: 24px;
|
||||
--sb-center-section-gap: 18px;
|
||||
--sb-center-column-gap: 14px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.sb-center-note { max-width: 880px; margin: 0; color: var(--hm-sem-fg-secondary); font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); overflow-wrap: anywhere; }
|
||||
.sb-center-status { width: 100%; border-collapse: collapse; font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
||||
.sb-center-status th, .sb-center-status td { border: 1px solid #e0dace; padding: 6px 10px; text-align: left; vertical-align: top; }
|
||||
.sb-center-status th { background: #f4f1ea; font-weight: 600; }
|
||||
.sb-center-status code { font-size: inherit; }
|
||||
.sb-center-scroll { min-width: 0; max-width: 100%; overflow-x: auto; }
|
||||
.sb-center-information { min-width: 0; width: 100%; }
|
||||
|
||||
/* One frame for all ordinary reference cards, including Linked item cards.
|
||||
Overview reuses StorySection rather than introducing another React helper. */
|
||||
.sb-center :is(.tunnel-reference__sample, .facility-reference__sample,
|
||||
.center-title-reference__sample, .cid-demo__block, .linked-facility__card,
|
||||
.sb-page-section, .csi-panel) {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background: var(--sb-center-card-background);
|
||||
border: var(--sb-center-card-border);
|
||||
border-radius: var(--sb-center-card-radius);
|
||||
}
|
||||
|
||||
.sb-center :is(.tunnel-reference, .facility-reference, .center-title-reference,
|
||||
.cid-demo, .linked-facility, .csi-demo, .sb-center-overview) {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: var(--sb-center-section-gap);
|
||||
}
|
||||
.sb-center-overview { margin-top: 32px; }
|
||||
.sb-center :is(.tunnel-reference__variant-grid, .facility-reference__variant-grid) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--sb-center-column-gap);
|
||||
}
|
||||
|
||||
.sb-center :is(.tunnel-reference__sample, .facility-reference__sample,
|
||||
.center-title-reference__sample, .cid-demo__block,
|
||||
.linked-facility__card, .sb-page-section) > header {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 42px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: var(--sb-center-header-gap);
|
||||
margin: 0;
|
||||
padding: var(--sb-center-header-padding);
|
||||
background: var(--sb-center-header-background);
|
||||
border-bottom: var(--sb-center-header-border);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sb-center .center-title-reference__sample > header > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--sb-center-header-gap);
|
||||
}
|
||||
.sb-center :is(.tunnel-reference__sample, .facility-reference__sample,
|
||||
.center-title-reference__sample, .cid-demo__block, .linked-facility__group,
|
||||
.linked-facility__card, .sb-page-section) > header :is(strong, h2, h3, .sb-page-section-title) {
|
||||
margin: 0;
|
||||
color: var(--hm-sem-fg-primary);
|
||||
font-size: var(--hm-label-font-size);
|
||||
line-height: var(--hm-label-line-height);
|
||||
font-weight: var(--hm-font-weight-bold);
|
||||
}
|
||||
.sb-center :is(.tunnel-reference__sample, .facility-reference__sample,
|
||||
.center-title-reference__sample, .cid-demo__block, .linked-facility__group,
|
||||
.linked-facility__card, .sb-page-section) > header :is(span, small, p, .sb-page-section-description) {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
color: var(--hm-sem-fg-secondary);
|
||||
font-size: var(--hm-button-font-size-sm);
|
||||
line-height: var(--hm-button-line-height-sm);
|
||||
font-weight: var(--hm-input-font-weight);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* No common height: keep each demo's content-driven height / existing minimum.
|
||||
Safe centering keeps the start of wide specimens reachable by local scroll. */
|
||||
.sb-center :is(.tunnel-reference__stage, .facility-reference__stage,
|
||||
.center-title-reference__stage, .cid-demo__stage,
|
||||
.sb-center-overview .sb-page-section-body, .linked-facility__variant) {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: var(--sb-center-content-padding);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.sb-center :is(.tunnel-reference__stage, .facility-reference__stage) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
justify-content: normal;
|
||||
justify-items: safe center;
|
||||
}
|
||||
.sb-center :is(.center-title-reference__stage, .cid-demo__stage) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
justify-content: safe center;
|
||||
}
|
||||
.sb-center .cid-demo__stage > * { flex: 0 0 auto; }
|
||||
.sb-center .linked-facility__grid {
|
||||
min-width: 0;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
|
||||
gap: var(--sb-center-column-gap);
|
||||
padding: 0;
|
||||
align-items: start;
|
||||
}
|
||||
/* Linked is Section Header + Common Card List. Road-class groups and context
|
||||
are plain sections, so an item card never sits inside another visual card. */
|
||||
.sb-center-linked-page .sb-center {
|
||||
padding-top: var(--sb-center-section-gap);
|
||||
}
|
||||
.sb-center .linked-facility :is(.linked-facility__group, .linked-facility__context, .linked-facility__pending) {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.sb-center .linked-facility__group {
|
||||
display: grid;
|
||||
gap: var(--sb-center-section-gap);
|
||||
}
|
||||
.sb-center .linked-facility__group > header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--sb-center-header-gap);
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
.sb-center .linked-facility__context {
|
||||
gap: var(--sb-center-section-gap);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sb-center .linked-facility__card {
|
||||
container: sb-linked-card / inline-size;
|
||||
}
|
||||
/* 56px label + 10px gap + measured 251px artwork + 48px common padding.
|
||||
Stack the label when that row cannot fit; preserve artwork size and local scroll. */
|
||||
@container sb-linked-card (max-width: 365px) {
|
||||
.sb-center .linked-facility__variant {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: auto;
|
||||
justify-items: safe center;
|
||||
gap: var(--sb-center-header-gap);
|
||||
}
|
||||
.sb-center .linked-facility__variant > small {
|
||||
grid-row: auto;
|
||||
}
|
||||
}
|
||||
.sb-center .sb-center-status { min-width: 720px; }
|
||||
|
||||
/* Spatial cards reuse the common frame above. Contrast belongs to the inner
|
||||
specimen canvas, never the card, heading, description or controls. */
|
||||
.sb-center-spatial-page .csi-panel {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
padding: var(--sb-center-content-padding);
|
||||
gap: var(--sb-center-header-gap);
|
||||
overflow: hidden;
|
||||
}
|
||||
.sb-center-spatial-page .csi-panel > h4 {
|
||||
margin: calc(-1 * var(--sb-center-content-padding)) calc(-1 * var(--sb-center-content-padding)) 0;
|
||||
padding: var(--sb-center-header-padding);
|
||||
background: var(--sb-center-header-background);
|
||||
border-bottom: var(--sb-center-header-border);
|
||||
color: var(--hm-sem-fg-primary);
|
||||
font-size: var(--hm-label-font-size);
|
||||
line-height: var(--hm-label-line-height);
|
||||
font-weight: var(--hm-font-weight-bold);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sb-center-spatial-page .csi-panel > :is(.csi-legend, .csi-note) {
|
||||
color: var(--hm-sem-fg-primary);
|
||||
font-size: var(--hm-button-font-size-sm);
|
||||
line-height: var(--hm-button-line-height-sm);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Direct demo divs are canvas/control groups, not nested cards. Keep wide
|
||||
swatches, compass rows and the fixed 840px extent fixture locally scrollable;
|
||||
headings and explanatory text stay within the card at every viewport. */
|
||||
.sb-center-spatial-page .csi-panel > div {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.sb-center-spatial-page .csi-panel :is(.csi-row-label, .csi-controls label) {
|
||||
color: var(--hm-sem-fg-primary);
|
||||
}
|
||||
/* These fluid-width route canvases already fit the card. Their marker/shadow
|
||||
overhang must not create a subpixel scrollbar or change the content height. */
|
||||
.sb-center-spatial-page :is(#csi-area-project-route, #csi-area-direction) > div {
|
||||
overflow: visible;
|
||||
}
|
||||
/* District and boundary canvases stay transparent on the common surface.
|
||||
Only their review labels use Foundation black; artwork colours stay intact. */
|
||||
.sb-center-spatial-page :is(#csi-area-administrative, #csi-area-zone-boundary) .csi-row-label {
|
||||
color: var(--hm-sem-palette-black);
|
||||
}
|
||||
/* Preserve the extent fixture's white terminal lines and translucent washes
|
||||
on its original light canvas. The compass needs no separate light backing. */
|
||||
.sb-center-spatial-page #csi-area-spatial-section > .csi-spatial-specimens {
|
||||
background: #efefef;
|
||||
}
|
||||
/* The reference demo authors white state captions inline for its dark canvas.
|
||||
This exact caption selector changes review text only, never semantic artwork. */
|
||||
.sb-center-spatial-page #csi-area-route .csi-swatchgrid > div > div > div:last-child {
|
||||
color: var(--hm-sem-fg-primary) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.sb-center :is(.tunnel-reference__variant-grid, .facility-reference__variant-grid) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { CenterTitle, CENTER_TITLE_COMPOSITIONS, type CenterTitleComposition } from "@hm-design/center-graphic"
|
||||
import { CenterTitleReferenceDemo } from "@hm-design/center-graphic/reference"
|
||||
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import { CENTER_BOUNDARY, withRepresentation } from "./center-shared"
|
||||
|
||||
// Owner: ui-viz docs/components/center-title.md. Title Resolution B numeric output is TBD; the per-Type palette is SCOPED_DESIGN_APPROVAL.
|
||||
const meta = {
|
||||
title: "Center Graphic/Title",
|
||||
component: CenterTitle,
|
||||
parameters: { layout: "padded" },
|
||||
} satisfies Meta<typeof CenterTitle>
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative Story: the four title types. Named after the area so Storybook shows it as a single sidebar item.
|
||||
// Common Story page. The CENTER representation wraps only the specimen, so the page title is never scaled with the graphic.
|
||||
export const Compositions: Story = {
|
||||
name: "Title",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
kicker={STORY_KICKER.center}
|
||||
title="Title"
|
||||
description="CENTER 제목 그래픽의 네 가지 Type Reference입니다. Resolution B 수치는 TBD, Type별 palette는 SCOPED_DESIGN_APPROVAL입니다."
|
||||
/>
|
||||
<StorySection title="Overview" description={CENTER_BOUNDARY}>
|
||||
{withRepresentation(() => <CenterTitleReferenceDemo />)}
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const Playground: Story = {
|
||||
tags: ["verification"],
|
||||
decorators: [withRepresentation],
|
||||
args: { composition: "routeProjectNameRoute", context: "서산영덕선", projectName: "대산~당진 건설공사", routeClassification: "고속국도 제30호", projectRoute: "L=25.36km" },
|
||||
argTypes: {
|
||||
composition: {
|
||||
control: { type: "select", labels: Object.fromEntries(Object.entries(CENTER_TITLE_COMPOSITIONS).map(([key, { type, label }]) => [key, `${type} · ${label}`])) },
|
||||
options: Object.keys(CENTER_TITLE_COMPOSITIONS) as CenterTitleComposition[],
|
||||
},
|
||||
},
|
||||
render: (args) => <CenterTitle {...args} />,
|
||||
}
|
||||
Reference in new issue
Block a user