Atoms — Button · Banner · StatusPill · …
packages/ui/src/components/ (flat — atoms/ 하위 디렉토리 없음). SPEC #012 v2.
Inventory
@linkmusic/ui 가 export 하는 컴포넌트 전체 (packages/ui/src/index.ts 단일 소스):
| 컴포넌트 | 용도 | API / variants |
|---|---|---|
Button | primary action | variant: primary · secondary · ghost · danger · size: sm/md/lg |
Banner | 정보 / 경고 / 위험 / 성공 | variant(=tone 별칭): info · warn · danger · success (default info) |
StatusPill | enum status 시각 | tone: success · warn · danger · info · muted · playing (default muted) |
OrgAvatar | 본사/매장 아바타 | initial-based fallback (이름 첫 글자) |
Card | 카드 컨테이너 | Card · CardHeader · CardTitle · CardContent |
Dialog | Radix Dialog wrapper | 개별 export — DialogTrigger·DialogPortal·DialogOverlay·DialogContent·DialogHeader·DialogBody·DialogFooter·DialogTitle·DialogDescription·DialogClose (namespace Dialog.X 없음) |
DropdownMenu | Radix DropdownMenu wrapper | DropdownMenuTrigger·DropdownMenuPortal·DropdownMenuContent·DropdownMenuItem·DropdownMenuLabel·DropdownMenuSeparator |
Tabs / Tab | 필터 button group | role="group" + aria-pressed (ARIA Tabs pattern 아님 — button group 구현) |
ListToolbar | 목록 공용 필터바 (검색 + 필터 N개 + 적용/초기화) — 운영자·매장·본사·감사 4목록 공용 | controlled — search/onSearch · filters[](ListToolbarFilter — kind:"select"(기본) | "date". date 면 <input type=date> + min/max, 감사 기간 from/to 용) · dirty(미적용 변경 힌트) · onApply(form submit) · onReset · rightSlot. 시안 ops-list-toolbar(배치6) |
ListPagination | 목록 공용 페이지네이션 footer | total · page(1-base) · totalPages · onPrev/onNext(경계 disabled) · summary(0건 override) |
ListNoResults | 조건 0건 빈 상태(필터 초기화 CTA) | onReset · icon/title/description/resetLabel. 진짜 빈 목록(OpsEmpty)과 구분 |
Toast | Radix Toast wrapper | ToastProvider·ToastViewport·Toast·ToastTitle·ToastDescription·ToastAction·ToastClose |
Switch | on/off 토글 | SwitchProps |
Field | label + input + error | required indicator · helperText |
Input / Label | 폼 primitive | InputProps · LabelProps |
FormSection | 폼 영역 grouping | title · description · children |
AuthShell | 인증 화면 셸 | → Shells 참고 |
AuthCard | 인증 폼 카드 | login · change-password 본문 카드 |
OpsStepper·PageHeader는@linkmusic/ui가 아니라apps/admin의 셸 컴포넌트다 (apps/admin/src/components/shell/ops-stepper.tsx·page-header.tsx). 디자인 시스템 인벤토리가 아니므로 여기서 다루지 않는다.
Button
import { Button } from "@linkmusic/ui";
<Button variant="primary" size="md" onClick={...}>등록</Button>CVA variants:
variant: primary · secondary · ghost · dangersize: sm (--tap-min 28px) · md (default 32) · lg (40)disabledstate
Banner
<Banner variant="warn">임시 비밀번호 사용 중</Banner>
<Banner tone="danger" title="장애">송출이 중단되었습니다</Banner>
<Banner variant="info">안내 메시지</Banner>
<Banner variant="success">저장되었습니다</Banner>variant(info · warn · danger · success, default info) + tone(variant 별칭, 같은 값, 둘 다 오면 tone 우선).
옵셔널 title · action. 임퍼소네이션 빨간 배너는 별도 컴포넌트로, Banner variant 에는 없다.
StatusPill
<StatusPill tone="success">ACTIVE</StatusPill>
<StatusPill tone="warn">UNPAID</StatusPill>
<StatusPill tone="danger">SUSPENDED</StatusPill>HqStatus / StoreStatus enum 표시.
OrgAvatar
<OrgAvatar name="본사 X" size="sm" />
// 첫 글자 "본" + 색상 hash (name 기반)Dialog
Radix UI Dialog wrapper. namespace API (Dialog.Content) 가 아니라 개별 export 를 import 한다
(Dialog.Content 로 쓰면 빌드 깨짐):
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@linkmusic/ui";
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>본사 모드로 전환</DialogTitle>
<DialogDescription>모든 작업은 감사 로그에 기록됩니다.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>취소</Button>
<Button variant="primary" onClick={confirm}>전환</Button>
</DialogFooter>
</DialogContent>
</Dialog>DialogContent 가 내부에서 DialogPortal + DialogOverlay 를 렌더하므로 호출처는 DialogContent 만
배치하면 된다.
Tabs
Radix Tabs primitive 이 아니라 button group 구현 (role="group" + aria-pressed). 라우팅·content
panel 전환 없이 클라이언트 측 필터링에만 쓰여 ARIA Tabs pattern (role="tablist"/role="tab"/화살표 키
내비) 은 도입하지 않았다.
import { Tabs, Tab } from "@linkmusic/ui";
<Tabs aria-label="본사 상태 필터">
<Tab active={filter === "all"} count={42} onClick={() => setFilter("all")}>전체</Tab>
<Tab active={filter === "active"} count={30} onClick={() => setFilter("active")}>활성</Tab>
</Tabs>Field
<Field label="이메일" required error={errors.email?.message}>
<input type="email" {...register("email")} />
</Field>Constraints
- 모두 토큰만 사용 (raw color 금지)
- Radix primitive (Dialog · DropdownMenu · Toast) 위에 우리 토큰으로 스킨.
Tabs는 Radix 미사용 (button group) - 화살표 함수 + 명명 export
- props 명시적 type
Storybook 카탈로그
@linkmusic/ui 는 Storybook 9 (react-vite) 카탈로그를 갖는다. 토큰 cascade(Pretendard + Tailwind v4 + src/styles/all.css)가 preview 에 주입되고 light/dark 테마 토글(toolbar)로 토큰 변경 영향을 즉시 확인한다.
pnpm --filter @linkmusic/ui storybook # dev (localhost:6006)
pnpm --filter @linkmusic/ui build-storybook # 정적 빌드- config:
packages/ui/.storybook/{main.ts,preview.tsx,storybook.css} - 대표 stories(컴포넌트 인접
*.stories.tsx):Button·StatusPill·Banner·Card·Field·Input·Dialog·Tabs·OrgAvatar·ListToolbar. 나머지 atom 은 후속 확장. - stories 는 프로덕션
pnpm -r build에 섞이지 않는다(Storybook deps = devDependencies).
Roadmap
- Table primitive (PRD Page 2·5·10 공통)
- 미리듣기 Player (Surface 11)
- Storybook stories 잔여 확장 (
Switch·DropdownMenu·Toast·FormSection·AuthShell/AuthCard)
Toast·DropdownMenu는 이미@linkmusic/ui에 존재하며 admin 에서 사용 중이다 (roadmap 항목 아님). 잔여 정비 항목이 있다면 API/스타일 정돈 수준이다.
References
- SPEC #006 §2-3 (v1 atoms)
- SPEC #012 (v2 재정의)
linkmusic-frontend-space/packages/ui/src/components/(flat)linkmusic-frontend-space/packages/ui/src/index.ts(export 단일 소스)