File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
"use client";
import type { CSSProperties } from "react";
import type { TokenEntry } from "./read-tokens";
import styles from "./dev-test.module.scss";
/**
* 토큰 **목록**은 CSSOM에서 읽으므로 여기에 적지 않는다. 이 표는 각 그룹을 어떻게
* 보여줄지(색은 스와치, 길이는 막대, 나머지는 값)와 한글 이름만 정한다.
* 목록에 없는 그룹이 새로 생겨도 값 표로 그려진다 — 빠지지 않는다.
*/
export const TOKEN_GROUPS: {
key: string;
label: string;
kind: "swatch" | "bar" | "value";
note?: string;
}[] = [
{
key: "color",
label: "시맨틱 색상",
kind: "swatch",
note: "light/dark 값이 light-dark() 한 줄에 함께 정의돼 있습니다. 좌측 전환기로 두 테마를 대조해 보세요.",
},
{ key: "theme", label: "브랜드 램프", kind: "swatch", note: "primary · secondary · accent" },
{
key: "primitive",
label: "원시 팔레트",
kind: "swatch",
note: "화면에서 직접 쓰지 않는 바닥층입니다. 시맨틱 색상이 이 값들을 참조합니다.",
},
{
key: "font",
label: "서체",
kind: "value",
note: "size 계열은 768px 경계에서 값이 바뀝니다.",
},
{ key: "padding", label: "패딩", kind: "bar" },
{ key: "gap", label: "간격", kind: "bar" },
{ key: "radius", label: "모서리 반경", kind: "bar" },
{ key: "icon", label: "아이콘 크기", kind: "bar" },
{ key: "border", label: "테두리 두께", kind: "bar" },
{ key: "number", label: "원시 수치", kind: "bar" },
{ key: "shadow", label: "그림자 수치", kind: "value" },
{ key: "backdrop", label: "배경 흐림", kind: "value" },
{ key: "form", label: "폼", kind: "value" },
{ key: "card", label: "카드", kind: "value" },
{ key: "modal", label: "모달", kind: "value" },
{ key: "grid", label: "그리드", kind: "value" },
{ key: "spacing", label: "여백", kind: "value" },
{ key: "section", label: "섹션", kind: "value" },
];
/**
* ⚠️ 인라인 `style`을 쓰는 **유일하게 허용되는 예외**다. 개수를 미리 알 수 없는 토큰을
* 런타임에 순회해야 하는데, CSS 클래스는 알 수 없는 이름에 대해 만들 수 없다. 여기서
* 넣는 값은 디자인 값이 아니라 **토큰 참조**(`var(--fox-*)`)이며 실제 값은 여전히
* 토큰이 소유한다. 일반 컴포넌트에서는 이 패턴을 쓰지 않는다.
*/
function ref(property: keyof CSSProperties, token: TokenEntry): CSSProperties {
return { [property]: `var(${token.property})` } as CSSProperties;
}
function Swatches({ tokens, fn }: { tokens: TokenEntry[]; fn: string }) {
return (
<div className={styles.swatchGrid}>
{tokens.map((token) => (
<div key={token.property} className={styles.swatch}>
<div className={styles.swatchChip} style={ref("background", token)} />
<div className={styles.swatchMeta}>
<span className={styles.tokenName}>
fox.{fn}({token.name})
</span>
<span className={styles.tokenValue}>{token.value}</span>
</div>
</div>
))}
</div>
);
}
function ValueTable({
tokens,
fn,
withBar,
}: {
tokens: TokenEntry[];
fn: string;
withBar: boolean;
}) {
return (
<table className={styles.table}>
<thead>
<tr>
<th scope="col">토큰</th>
<th scope="col">값</th>
{withBar ? <th scope="col">크기</th> : null}
</tr>
</thead>
<tbody>
{tokens.map((token) => (
<tr key={token.property}>
<td>
fox.{fn}({token.name})
</td>
<td className={styles.tdValue}>{token.value}</td>
{withBar ? (
<td>
<span className={styles.bar} style={ref("inlineSize", token)} />
</td>
) : null}
</tr>
))}
</tbody>
</table>
);
}
export function TokenView({ group, tokens }: { group: string; tokens: TokenEntry[] }) {
const meta = TOKEN_GROUPS.find((entry) => entry.key === group);
const kind = meta?.kind ?? "value";
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>
{meta?.label ?? group} · {tokens.length}
</h2>
{meta?.note ? <p className={styles.sectionNote}>{meta.note}</p> : null}
{kind === "swatch" ? (
<Swatches tokens={tokens} fn={group} />
) : (
<ValueTable tokens={tokens} fn={group} withBar={kind === "bar"} />
)}
</section>
);
}