컴포넌트 작성 규약#
아직 컴포넌트가 없습니다. 디자인 확정에 따라 하나씩 추가합니다.
파일 배치#
컴포넌트 하나당 폴더 하나. 스타일을 같은 폴더에 두어야 폴더째 복사·삭제가 가능합니다.
components/
button/
button.tsx 컴포넌트
button.module.scss 스타일 (반드시 .module.scss — 전역 오염 방지)
index.ts export { Button } from "./button";
index.ts 배럴 — export * from "./button";
작성 규칙#
1. 스타일은 abstracts만 @use 한다.
@use "@fox/styles/abstracts" as fox;
.button {
padding: fox.space(2) fox.space(4);
border-radius: fox.radius(md);
background: fox.color(primary);
color: fox.color(on-primary);
@include fox.typo(label-lg);
@include fox.focus-ring;
@include fox.transition((background-color, color));
}
@fox/styles(진입점)를 @use 하면 안 됩니다 — 그쪽은 CSS를 출력하므로 컴포넌트마다
토큰 선언 전체가 복제됩니다. abstracts는 출력이 0입니다.
2. 원시 디자인 값을 쓰지 않는다.
color: #4f46e5, padding: 13px, border-radius: 12px 금지. 전부 fox.*() 함수로
토큰을 지목합니다. 없는 토큰 이름을 쓰면 빌드가 실패하므로 오타는 배포까지 갈 수
없습니다. 맞는 토큰이 없다고 해서 원시 값을 쓰지 말고, 토큰을 먼저 추가합니다
(@fox/styles/tokens/).
예외는 의미가 고정되어 토큰화가 무의미한 값뿐입니다 — 0, 100%, 1px(구분선),auto, inherit.
3. 기본은 Server Component로 둔다.
'use client'는 상호작용(상태·이벤트 핸들러·브라우저 API)이 실제로 필요한 컴포넌트에만
붙입니다. CSS Module은 빌드타임에 클래스명 문자열로 바뀔 뿐이라 런타임 JS가 없으므로,
스타일링만 하는 컴포넌트는 서버에 남을 수 있습니다. 여기에 불필요하게 'use client'를
붙이면 이 컴포넌트를 쓰는 화면 전체가 클라이언트로 내려갑니다.
4. variant는 클래스 맵으로 고정한다.
import styles from "./button.module.scss";
import { cx } from "@fox/core/utils";
type ButtonVariant = "primary" | "secondary" | "ghost";
const VARIANT: Record<ButtonVariant, string> = {
primary: styles.primary,
secondary: styles.secondary,
ghost: styles.ghost,
};
export function Button({ variant = "primary", className, ...props }: ButtonProps) {
return <button className={cx(styles.button, VARIANT[variant], className)} {...props} />;
}
Record<Variant, string>이라 variant를 추가하면 맵에 항목을 빠뜨릴 수 없습니다(타입 에러).
5. className prop은 배치용으로만 열어 둔다.
호출부가 className으로 넘기는 것은 margin·grid 배치 같은 위치 조정이지, 색·크기 같은
디자인 값이 아닙니다. 디자인이 달라져야 하면 variant를 추가합니다.
6. 호스트 앱에 의존하지 않는다.
@fox/ 안에서는 @/lib/..., @/app/... 같은 앱 경로를 import 하지 않습니다. 이 폴더는
다른 프로젝트로 통째로 복사되어야 하므로, 앱이 주는 값은 반드시 prop이나 CSS 변수로
받습니다(폰트가 --app-font-sans를 받는 것과 같은 방식).