UNIAI Design System; Primitives Components Package
@uniai-fe/uds-foundation 토큰 위에 Radix UI 컴포넌트를 얇게 감싼 기초 UI 컴포넌트 컬렉션입니다. Next.js 등 React 런타임에서 바로 import해 버튼·입력·네비게이션 등 공통 요소를 일관된 스타일로 사용할 수 있습니다. Templates(@uniai-fe/uds-templates) 패키지에서 사용하는 Phone Input/Email Verification Input/OneTimeCode Input 등의 인증 시나리오 컴포넌트도 이곳에서 제공합니다.
``bash`
pnpm add @uniai-fe/uds-foundation @uniai-fe/util-functions @uniai-fe/uds-primitives
@uniai-fe/uds-primitives는 디자인 토큰과 유틸리티 로직을 공유하기 위해 다음 패키지를 같이 설치해야 합니다.
- @uniai-fe/uds-foundation@^0.0.1@uniai-fe/util-functions@^0.2.3
- react@>=19
- , react-dom@>=19react-hook-form@>=7
-
peer dependency가 빠져 있을 경우 앱 번들 시점에 에러가 발생하니, 위 목록을 프로젝트 package.json에 명시해 주세요.
`ts`
// next.config.ts
const nextConfig = {
transpilePackages: ["@uniai-fe/uds-foundation", "@uniai-fe/uds-primitives"],
};
export default nextConfig;
앱 루트에서 foundation CSS를 1회 import 하면 모든 primitives가 토큰을 공유합니다.
`ts`
import "@uniai-fe/uds-foundation/css";
`tsx
import { Button } from "@uniai-fe/uds-primitives";
export default function Page() {
return (
확인
);
}
`
`tsx
import Link from "next/link";
import { Button } from "@uniai-fe/uds-primitives";
function LinkButton() {
return (
href="/dashboard"
fill="outlined"
size="medium"
priority="secondary"
>
대시보드로 이동
);
}
`
- 기본 클래스: .button (padding/height/radius/타이포/커서 담당)button-label
- 슬롯: , button-icon, button-left, button-right, button-loadingbutton-scale-
- Modifier: , button-fill-, button-priority-, button-state-, button-size-*, button-block, button-icon-left/rightscale
- prop은 legacy 호환용이며 fill/size를 명시 사용하는 것이 권장된다.loading
- 상태는 readonly와 동일하게 잠기며 hover/pressed 반응을 막는다. anchor 등 커스텀 요소도 aria-disabled="true"를 통해 동일한 스타일을 적용받는다.
Button 객체는 템플릿별 컴포넌트를 포함한다.
`tsx
import { Button } from "@uniai-fe/uds-primitives";
function Templates() {
return (
<>
링크 스타일
>
);
}
`
- TextButton(Button.Text)은 size="small|medium|large" + priority="secondary|tertiary"만 허용한다.Button.Rounded
- RoundButton()은 size만 지정하면 되고 priority는 기본 Button과 동일하게 primary|secondary|tertiary를 사용한다..button-template-text
- 템플릿별 클래스를 추가로 노출한다: , .button-template-text-size-, .button-template-round, .button-template-round-size-.primitives/Button
- 스토리북 Story에서 solid/outlined/텍스트/라운드 4가지 카테고리를 한 번에 확인할 수 있다.
- Button.Default는 SlotComponent를 통해 as prop으로 전달된 요소(예: Link, a, button)에 공통 속성/ref를 안전하게 전달한다.SlotComponentProps
- 는 React가 허용하는 모든 native props + data-* 속성을 그대로 포함하고, as/children/className만 재정의한다.import { SlotComponent } from "@uniai-fe/uds-primitives";
- 필요 시 로 직접 사용해 카드/배너 등의 래퍼를 구현할 수 있다.docs/CONTEXT-SLOT.md
- 자세한 도입 배경과 API는 에서 확인할 수 있다.
- TextInput Base 컴포넌트의 clear 버튼 로직을 pointer 이벤트 기반으로 재작성해 모바일 터치 환경에서도 안정적으로 입력값이 초기화되도록 했다.register
- react-hook-form 와 연계된 값도 동일하게 초기화되며, focus가 빠지면 clear 버튼이 자동으로 숨겨진다.
foundation CSS는 앱 루트에서 한 번만 직접 import해야 한다. primitives styles/css 엔트리는 컴포넌트 SCSS만 포함하며 foundation 토큰을 다시 로드하지 않는다.
`scss`
@use "@uniai-fe/uds-foundation/css";
@use "@uniai-fe/uds-primitives/styles";
Next.js/webpack 환경에서 Sass를 사용하지 않는 경우에는 아래처럼 CSS를 순서대로 import한다.
`ts`
import "@uniai-fe/uds-foundation/css";
import "@uniai-fe/uds-primitives/css";
컴포넌트 단위로 필요한 스타일만 선택해 불러오려면 각 경로(components/{name}/index.scss)를 직접 import하면 된다.
`scss`
@use "@uniai-fe/uds-primitives/button/index.scss";
@use "@uniai-fe/uds-primitives/navigation/index.scss";
ThemeProvider는 CSS를 import하지 않으므로 foundation/primitives styles를 앱 루트에서 1회만 로드하면 중복 없이 토큰 매핑이 적용된다. Provider 자체는 foundation 패키지(@uniai-fe/uds-foundation/provider)에서만 export된다(one-source 규칙).
> modules 레포 내부(Storybook 등)에서는 개발 편의를 위해 @uniai-fe/uds-primitives/src/index.scss를 직접 import하지만, 외부 서비스/패키지는 @uniai-fe/uds-primitives/css 엔트리만 사용한다.
- primitives 소스 SCSS는 모든 디자인 토큰을 :root에 선언하고, 빌드 시 scripts/merge-theme-root.mjs가 이 토큰 블록을 하나의 :root { ... }로 합쳐 중복 선언을 제거한다..uds-theme-root
- ThemeProvider가 루트 DOM에 클래스를 주입하므로, 서비스 앱은 ThemeProvider를 layout 최상단에 배치한 뒤 @uniai-fe/uds-foundation/css → @uniai-fe/uds-primitives/css 순으로 import하면 된다.
- modules 레포(Storybook 등)는 SCSS 원본을 직접 import하지만 외부 소비자는 CSS 엔트리만 사용한다는 정책을 README/CODEX-RULES에 명시해둔다.
Next.js 15(app router 기준)에서 primitives를 사용하는 최소 구성 예시는 다음과 같다.
`ts`
// next.config.ts
const nextConfig = {
transpilePackages: ["@uniai-fe/uds-foundation", "@uniai-fe/uds-primitives"],
sassOptions: {
// monorepo가 아니어도 node_modules 경로를 자동 탐색하지만,
// 필요 시 디자인 토큰 경로를 명시해 두면 빌드 환경 차이를 줄일 수 있다.
includePaths: ["./node_modules"],
},
};
export default nextConfig;
`scss`
/ app/globals.scss /
@use "@uniai-fe/uds-foundation/css";
@use "@uniai-fe/uds-primitives/styles";
`tsx
// app/layout.tsx
import type { ReactNode } from "react";
import "@uniai-fe/uds-foundation/css";
import "@uniai-fe/uds-primitives/styles";
import { ThemeProvider } from "@uniai-fe/uds-foundation/provider";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
위 예시는 ThemeProvider가 foundation 패키지에서만 export되고 CSS를 재import하지 않는 현재 구조를 기준으로 하므로,
globals.scss 또는 루트에서 foundation/primitives styles를 반드시 각각 한 번 로드해야 한다. Sass 기반 프로젝트는 @use "@uniai-fe/uds-foundation/css"; @use "@uniai-fe/uds-primitives/styles";, CSS-only 프로젝트는 import "@uniai-fe/uds-foundation/css"; import "@uniai-fe/uds-primitives/css";를 사용한다.모든 컴포넌트는
.component 클래스 + CSS 변수 기반으로 override가 가능하며, 버튼처럼 Slot(left/right/icon 등)을 제공하는 항목은 CONTEXT-.md 문서에 상세 API를 기록했습니다. 불필요한 data- attribute는 제거했고, 상태 표시는 :disabled, [aria-busy="true"] 같은 표준 attribute만 사용합니다.구조
`plaintext
src/components/{category}/
markup/ // 컴포넌트 구현
types/ // 외부 노출 타입
styles/ // SCSS (foundation 토큰 기반)
hooks/ // 카테고리 전용 훅
`- 배럴(
components/{category}/index.tsx)은 항상 import "./index.scss"를 포함합니다.
- 스타일은 foundation CSS 변수만 사용하며, .button.button-priority- / .button.button-fill- 클래스 조합으로 상태를 분기합니다.스크립트
-
pnpm module:lint
- pnpm module:typecheck
- pnpm module:build루트에서는
pnpm --filter @uniai-fe/uds-primitives 로 실행할 수 있습니다.문서
-
CONTEXT.md 및 CONTEXT-*.md: 각 컴포넌트의 상태/진행/디자인 근거
- CONTEXT-INPUT.md: Phone/Email/OneTimeCode 등 인증 입력 시나리오 규칙을 포함하며, templates CONTEXT-SIGNUP*.md와 항상 동기화해야 한다.
- RADIX-SIZE-GUIDE.md: primitives 사이즈 체계와 Radix 매핑 규칙$3
- PhoneInput: 기본은 마스킹된 전화번호 입력만 제공한다.
onRequestCode(optional)를 주입하면 인증요청 버튼이 우측 right 슬롯에 노출된다. Step1 User Info에서는 단순 입력만 필요하므로 optional props를 생략한다.
- EmailInput: 이메일 입력 + 인증요청 버튼 + countdown + OneTimeCode 입력을 하나로 묶는다. countdownText, codeVisible, codeLength, codeLabel, codeHelper, codeState, onCodeComplete로 Step2 Verify & Agreement 상태를 제어한다.
- AuthCodeInput: length 지정형 OneTimeCode grid. EmailInput 내부에서 사용하지만 서비스 앱도 직접 import할 수 있다.
- 변경 시에는 다음 문서를 함께 업데이트한다: packages/design/primitives/docs/CONTEXT-INPUT.md, packages/design/templates/docs/CONTEXT-SIGNUP.md, CONTEXT-SIGNUP-FLOW.md, packages/design/templates/docs/STORYBOOK.md, apps/design-storybook/src/stories/templates/auth/AuthSignup.stories.tsx. 컨벤션: 모든 컴포넌트/스토리/문서는 slot/prefix/suffix 용어를 사용하지 않고, 레이아웃 기준(
header/body/footer, 2단 구조는 upper/lower)과 util 키워드를 사용한다. 인터랙션 함수는 on* 접두사를 사용하고, JSDoc @param`은 depth 전체를 풀어 쓴다.필요한 컨텍스트를 확인한 뒤 컴포넌트를 import해 사용하면 됩니다.