File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
"use client";
import type { CSSProperties, ReactNode } from "react";
import type { ManifestSection, ManifestToken } from "./token-manifest";
import styles from "./dev-test.module.scss";
/**
* 미리보기 — 토큰 이름으로 무엇을 보여줄지 고른다.
*
* ⚠️ 값은 CSS 변수가 아니라 매니페스트의 **해석값**을 직접 넣는다. 변수를 쓰면 PC 섹션을
* 좁은 창에서 볼 때 모바일 값이 그려지고, Light 섹션이 다크 테마에서 다크 색으로 그려진다 —
* 이 화면은 "그 파일이 정의한 값"을 보여야 하므로 현재 테마·뷰포트에 흔들리면 안 된다.
* 인라인 `style`을 쓰는 예외인 이유도 같다: 렌더할 토큰을 미리 알 수 없다.
*/
const PREVIEWS: { match: RegExp; render: (value: string) => ReactNode }[] = [
{
match: /(^|-)padding(-|$)/,
render: (value) => (
<div className={styles.demoPadBox} style={{ padding: value }}>
<p className={styles.demoText}>첫 번째 문단</p>
<p className={styles.demoText}>두 번째 문단</p>
</div>
),
},
{
match: /(^|-)(gap|gutter)(-|$)|^spacing-/,
render: (value) => (
<div className={styles.demoGapBox} style={{ gap: value }}>
<p className={styles.demoText}>첫 번째 문단</p>
<p className={styles.demoText}>두 번째 문단</p>
</div>
),
},
{
match: /(^|-)radius(-|$)/,
render: (value) => <div className={styles.demoRadius} style={{ borderRadius: value }} />,
},
{
// 브라우저가 자체적으로 그리는 컨트롤이라 별도 에셋 없이 크기만 확인할 수 있다.
match: /(^|-)icon(-|$)/,
render: (value) => (
<input
type="checkbox"
defaultChecked
aria-label="크기 미리보기"
className={styles.demoIcon}
style={{ inlineSize: value, blockSize: value }}
/>
),
},
{
match: /^border-/,
render: (value) => <div className={styles.demoBorder} style={{ borderWidth: value }} />,
},
{
match: /^shadow-/,
render: (value) => (
<div
className={styles.demoShadow}
style={{ boxShadow: `0 0 ${value} var(--demo-shadow-color)` }}
/>
),
},
{
match: /^backdrop-/,
render: (value) => (
<div className={styles.demoBackdrop}>
{/* eslint-disable-next-line @next/next/no-img-element -- @fox는 프레임워크에 의존하지
않아야 이식된다. next/image를 쓰면 이 폴더가 Next 전용이 되고 외부 호스트마다
remotePatterns 설정도 필요해진다. */}
<img
src="https://picsum.photos/seed/picsum/200/300"
alt=""
className={styles.demoBackdropImage}
/>
<div
className={styles.demoBackdropOverlay}
style={{ backdropFilter: `blur(${value})`, WebkitBackdropFilter: `blur(${value})` }}
/>
</div>
),
},
{
match: /^font-size-/,
render: (value) => (
<p className={styles.demoType} style={{ fontSize: value }}>
다람쥐 헌 쳇바퀴에 타고파 · Handgloves 0123
</p>
),
},
{
match: /^font-family-/,
render: (value) => (
<p className={styles.demoType} style={{ fontFamily: value }}>
다람쥐 헌 쳇바퀴에 타고파 · Handgloves 0123
</p>
),
},
{
match: /^font-weight-/,
render: (value) => (
<p className={styles.demoType} style={{ fontWeight: value }}>
다람쥐 헌 쳇바퀴에 타고파 · Handgloves 0123
</p>
),
},
];
function previewFor(token: ManifestToken): ReactNode {
if (token.type === "color") {
// 색은 값 칸의 칩이 이미 보여준다.
return null;
}
const hit = PREVIEWS.find((entry) => entry.match.test(token.name));
return hit ? hit.render(token.resolved) : null;
}
/** 참조 대상이 우리가 갖고 있지 않은 컬렉션일 때 생성기가 붙이는 표시. */
function isForeign(alias: string | null): boolean {
return Boolean(alias?.includes("미제공"));
}
function chipStyle(token: ManifestToken): CSSProperties {
return { background: token.resolved };
}
export function TokenView({ section }: { section: ManifestSection }) {
const hasPreview = section.tokens.some((token) => previewFor(token) !== null);
const foreignCount = section.tokens.filter((token) => isForeign(token.alias)).length;
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>
{section.label} · {section.tokens.length}
</h2>
<p className={styles.sectionNote}>
{section.file} — 파일에 값이 적혀 있으면 그 값을, 다른 토큰을 가리키면 참조 토큰을
표시합니다.
{foreignCount > 0
? ` 이 중 ${foreignCount}개는 제공되지 않은 컬렉션을 가리킵니다.`
: ""}
</p>
<table className={styles.table}>
<thead>
<tr>
<th scope="col">토큰</th>
<th scope="col">값 / 참조</th>
{hasPreview ? <th scope="col">미리보기</th> : null}
</tr>
</thead>
<tbody>
{section.tokens.map((token) => (
<tr key={token.name}>
<td>
<span className={styles.tokenName}>{token.name}</span>
<span className={styles.tokenFigma}>{token.figma}</span>
</td>
<td className={styles.tdValue}>
{token.type === "color" ? (
<span className={styles.colorChip} style={chipStyle(token)} />
) : null}
{token.literal !== null ? (
token.literal
) : (
<span
className={isForeign(token.alias) ? styles.aliasForeign : styles.aliasRef}
title={isForeign(token.alias) ? "제공된 파일에 없는 컬렉션" : "참조 토큰"}
>
→ {token.alias}
</span>
)}
</td>
{hasPreview ? <td className={styles.tdDemo}>{previewFor(token)}</td> : null}
</tr>
))}
</tbody>
</table>
</section>
);
}