ldu0009 08-11 13bf742 feat: @fox 포터블 디자인 시스템 패키지 추가 UNIX

컴포넌트 작성 규약#

아직 컴포넌트가 없습니다. 디자인 확정에 따라 하나씩 추가합니다.

파일 배치#

컴포넌트 하나당 폴더 하나. 스타일을 같은 폴더에 두어야 폴더째 복사·삭제가 가능합니다.

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를 받는 것과 같은 방식).