fix: align v2 publisher review with HMDesign stories and tokens
This commit is contained in:
1 parent
1c4e0bb9a1
commit
7b22fe88a1
27 files changed
+413
-49
No files matched your search
@@ -2,23 +2,30 @@ import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { runtimeThemeContract, type RuntimeThemeToken } from "@hm-design/ui/theme"
|
||||
import { ReferencePage, ReferenceSection } from "./reference"
|
||||
|
||||
// Existing palette tokens only; values come from the public default contract.
|
||||
// ui_viz Color 4.1 confirmed stages only. Orange Dark 1/2 remain Needs Review.
|
||||
const stages = ["light-4", "light-3", "light-2", "light-1", "default", "dark-1", "dark-2", "dark-3"] as const
|
||||
const palettes = [
|
||||
{ name: "Green", prefix: "green", stages: ["light-4", "light-3", "light-2", "light-1", "default", "dark-1", "dark-2"] },
|
||||
{ name: "Brown", prefix: "brown", stages: ["light-4", "light-2", "light-1", "default", "dark-1", "deep"] },
|
||||
{ name: "Orange", prefix: "orange", stages: ["light-4", "light", "mid", "default", "dark", "deep"] },
|
||||
{ name: "한맥그린", prefix: "green", confirmed: [...stages], legacy: [] },
|
||||
{ name: "한맥브라운", prefix: "brown", confirmed: [...stages], legacy: ["deep"] },
|
||||
{ name: "한맥오렌지", prefix: "orange", confirmed: ["light-4", "light-3", "light-2", "light-1", "default", "dark-3"], legacy: ["light", "mid", "dark", "deep"] },
|
||||
]
|
||||
const stageLabel = (stage: string) => stage === "default" ? "Default · 기본색" : stage.replaceAll("-", " ").replace(/^./, letter => letter.toUpperCase())
|
||||
function ColorChip({ prefix, stage, confirmed = true }: { prefix: string; stage: string; confirmed?: boolean }) {
|
||||
const token = `color.palette.${prefix}-${stage}` as RuntimeThemeToken
|
||||
const definition = confirmed ? runtimeThemeContract[token] : undefined
|
||||
return <article className="hm-brand-color-chip" data-color-stage={stage} data-color-token={definition ? token : undefined} data-base={stage === "default" ? "" : undefined}>
|
||||
<div className="hm-foundation-swatch" style={definition ? { background: `var(${definition.property})` } : undefined} />
|
||||
<h3>{stageLabel(stage)}</h3><p>{definition ? definition.defaultValue.toUpperCase() : "미확정"}</p>
|
||||
</article>
|
||||
}
|
||||
function ColorReferencePage() {
|
||||
return <ReferencePage title="Color" intro="현재 HM 브랜드 컬러를 밝은색 → 기본색 → 어두운색 순서로 비교합니다. 기본색을 강조하며 컬러의 상세 의미와 적용 규칙은 추후 보완합니다.">
|
||||
{palettes.map(palette => <ReferenceSection key={palette.name} title={palette.name} note="기존 컬러값을 유지합니다. 가로 컬러칩에서 단계명과 HEX를 확인합니다.">
|
||||
<div className="hm-brand-color-row">{palette.stages.map(stage => {
|
||||
const token = `color.palette.${palette.prefix}-${stage}` as RuntimeThemeToken
|
||||
const definition = runtimeThemeContract[token]
|
||||
return <article key={stage} className="hm-brand-color-chip" data-base={stage === "default" ? "" : undefined}>
|
||||
<div className="hm-foundation-swatch" style={{ background: `var(${definition.property})` }} />
|
||||
<h3>{stage === "default" ? "Default · 기본색" : stage.replaceAll("-", " ")}</h3><p>{definition.defaultValue.toUpperCase()}</p>
|
||||
</article>
|
||||
})}</div>
|
||||
return <ReferencePage title="Color" intro="한맥 브랜드 컬러를 8단계로 비교합니다. 확정된 기존 색상만 표시하며, 미확정 단계는 색상을 지정하지 않습니다. 기본색을 강조합니다.">
|
||||
{palettes.map(palette => <ReferenceSection key={palette.name} title={palette.name} note="Light 4 → Light 3 → Light 2 → Light 1 → Default → Dark 1 → Dark 2 → Dark 3. 미확정 단계는 추가 확인이 필요합니다.">
|
||||
<div className="hm-brand-color-row">{stages.map(stage => <ColorChip key={stage} prefix={palette.prefix} stage={stage} confirmed={palette.confirmed.includes(stage)} />)}</div>
|
||||
{palette.legacy.length > 0 && <>
|
||||
<p>기존 버튼 그라데이션 색상 · 브랜드 단계와 별도 유지</p>
|
||||
<div className="hm-brand-color-row">{palette.legacy.map(stage => <ColorChip key={stage} prefix={palette.prefix} stage={stage} />)}</div>
|
||||
</>}
|
||||
</ReferenceSection>)}
|
||||
</ReferencePage>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/* Opt-in list composition. FilterBar's general layout contract stays unchanged. */
|
||||
/* Data Table Story composition only: intrinsic search width decides when tools wrap. */
|
||||
.hm-data-table-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--hm-filter-bar-gap); min-width: 0; }
|
||||
.hm-data-table-search { flex: 1 0 max-content; min-width: 0; max-width: 100%; }
|
||||
.hm-data-table-search [data-slot="filter-bar-search"] { width: var(--hm-filter-bar-search-width); max-width: 100%; }
|
||||
.hm-data-table-view-options { flex: 0 0 auto; margin-left: auto; }
|
||||
.hm-list-search-basic, .hm-list-search-extended { gap: 8px; }
|
||||
.hm-list-search-basic { justify-content: center; }
|
||||
.hm-list-search-extended { justify-content: flex-start; }
|
||||
|
||||
@@ -122,19 +122,21 @@ function WithFilterBar({ extended = true }: { extended?: boolean }) {
|
||||
<PageHeader><PageHeaderContent><PageHeaderTitle>사용자 목록</PageHeaderTitle><PageHeaderDescription>조건을 입력한 뒤 조회합니다.</PageHeaderDescription></PageHeaderContent></PageHeader>
|
||||
<Card className="min-w-0"><CardContent>
|
||||
<div className="grid min-w-0 gap-4">
|
||||
<form onSubmit={event => { event.preventDefault(); apply() }}>
|
||||
<FilterBar className={extended ? "hm-list-search-extended" : "hm-list-search-basic"}>
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
<SelectTrigger size="medium" aria-label="상태"><SelectValue placeholder="상태" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="all">전체 상태</SelectItem>{statusOptions.map(option => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
{extended && <><Combobox controlled size="medium" aria-label="부서" options={departmentOptions} value={department} placeholder="부서" onValueChange={setDepartment} />
|
||||
<DatePicker controlled size="medium" aria-label="등록일" placeholder="등록일" value={date} onValueChange={setDate} /></>}
|
||||
<FilterBarSearch value={search} onValueChange={setSearch} placeholder="이름·부서 검색" />
|
||||
<FilterBarActions><Button type="submit" variant="primary" appearance="solid">조회</Button><Button type="button" variant="cancel" appearance="outline" onClick={reset}>초기화</Button></FilterBarActions>
|
||||
</FilterBar>
|
||||
</form>
|
||||
<div className="flex justify-end"><DataTableViewOptions table={table} labels={columnLabels} /></div>
|
||||
<div className="hm-data-table-tools">
|
||||
<form className="hm-data-table-search" onSubmit={event => { event.preventDefault(); apply() }}>
|
||||
<FilterBar className={extended ? "hm-list-search-extended" : "hm-list-search-basic"}>
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
<SelectTrigger size="medium" aria-label="상태"><SelectValue placeholder="상태" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="all">전체 상태</SelectItem>{statusOptions.map(option => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}</SelectContent>
|
||||
</Select>
|
||||
{extended && <><Combobox controlled size="medium" aria-label="부서" options={departmentOptions} value={department} placeholder="부서" onValueChange={setDepartment} />
|
||||
<DatePicker controlled size="medium" aria-label="등록일" placeholder="등록일" value={date} onValueChange={setDate} /></>}
|
||||
<FilterBarSearch value={search} onValueChange={setSearch} placeholder="이름·부서 검색" />
|
||||
<FilterBarActions><Button type="submit" variant="primary" appearance="solid">조회</Button><Button type="button" variant="cancel" appearance="outline" onClick={reset}>초기화</Button></FilterBarActions>
|
||||
</FilterBar>
|
||||
</form>
|
||||
<div className="hm-data-table-view-options"><DataTableViewOptions table={table} labels={columnLabels} /></div>
|
||||
</div>
|
||||
<DataTable table={table} emptyMessage="조건에 맞는 사용자가 없습니다." />
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
@@ -159,8 +161,8 @@ export const All: Story = {
|
||||
<StorySection title="Overview" description="행 선택(헤더 혼합 상태), 정렬, 열 표시, 행 작업, 페이지 크기와 페이지 이동을 실제로 조작합니다.">
|
||||
<Basic />
|
||||
</StorySection>
|
||||
<StorySection title="기본 목록 검색" description="조건·검색어·조회·초기화를 8px 간격으로 가운데 정렬합니다. 제목은 Card 밖에 배치합니다."><WithFilterBar extended={false} /></StorySection>
|
||||
<StorySection title="추가 조건 목록 검색" description="부서·등록일을 제공하는 구성은 입력값과 관계없이 왼쪽 정렬합니다. 모바일 버튼 행은 우측 정렬합니다."><WithFilterBar /></StorySection>
|
||||
<StorySection title="기본 목록 검색" description="조건·검색어·조회·초기화를 8px 간격으로 가운데 정렬합니다. 공간이 충분하면 열 버튼은 같은 행 우측, 부족할 때만 다음 행에 배치합니다. 제목은 Card 밖에 배치합니다."><WithFilterBar extended={false} /></StorySection>
|
||||
<StorySection title="추가 조건 목록 검색" description="부서·등록일을 제공하는 구성은 입력값과 관계없이 왼쪽 정렬합니다. 열 버튼은 공간이 충분하면 같은 행 우측에 배치하며 모바일 버튼 행은 우측 정렬합니다."><WithFilterBar /></StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ async function verifyLoader(container: HTMLElement) {
|
||||
const initial = JSON.stringify(geometry())
|
||||
const origin = performance.timeOrigin
|
||||
const defaults = await fetchRuntimeTheme("/themes/default.json", { cache: "no-store" })
|
||||
assert(Object.keys(defaults.tokens).length === 71, "Default JSON exposes 71 color contract entries")
|
||||
assert(Object.keys(defaults.tokens).length === Object.values(runtimeThemeContract).filter(token => token.type === "color").length, "Default JSON exposes every public color contract entry")
|
||||
for (const [key, value] of Object.entries(defaults.tokens)) assert(defaultRuntimeTheme.tokens[key as keyof typeof runtimeThemeContract] === value, `${key}: JSON equals generated package fallback`)
|
||||
assert(await loadRuntimeTheme(root, "/themes/default.json", { request: { cache: "no-store" } }), "Default JSON uses official URL Loader")
|
||||
assert(JSON.stringify(geometry()) === initial, "Default JSON preserves component geometry")
|
||||
|
||||
Reference in new issue
Block a user