---
name: lumina-admin-ui
description: >-
  Vietnamese light-theme admin / CRM design system for React + TypeScript + Tailwind +
  shadcn/ui (Radix). Ships the actual component source in assets/: brand tokens mapped to
  shadcn CSS variables, Roboto type, solid high-contrast status colors, and a 30+ component
  kit — button / input / select / dropdown / dialog / sheet / popover / command (⌘K) / table
  with card sub-row / pagination / calendar with date, time, date-time and date-range pickers /
  tabs / switch / checkbox / radio / tooltip / avatar / breadcrumb / progress / accordion /
  skeleton / toast (sonner) / chart (Recharts) — plus a shared sidebar+topbar shell. Use when
  building or extending Vietnamese admin, dashboard, CRM or HRM screens.
---

# Lumina Admin — React + shadcn/ui design system

Skill này **tự chứa**: `assets/` là bản source thật của bộ component, copy thẳng vào dự án
đích. Không cần chạy `shadcn add` rồi vá lại — các component ở đây đã tuỳ biến sâu so với bản
shadcn gốc (kích cỡ nút, badge/alert nền đặc, lịch tiếng Việt, sub-row cho bảng, 4 bộ picker…).

## Cài vào một dự án mới

**1. Khởi tạo dự án** (nếu chưa có):

```bash
yarn create vite my-admin --template react-ts && cd my-admin
```

**2. Cài dependency:**

```bash
yarn add @radix-ui/react-accordion @radix-ui/react-avatar @radix-ui/react-checkbox \
  @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label \
  @radix-ui/react-popover @radix-ui/react-progress @radix-ui/react-radio-group \
  @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot \
  @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-tooltip \
  class-variance-authority clsx cmdk date-fns lucide-react react-day-picker \
  react-router-dom recharts sonner tailwind-merge tailwindcss-animate

yarn add -D tailwindcss postcss autoprefixer
```

**3. Copy `assets/` vào dự án** (giữ nguyên cấu trúc):

```
assets/components/  →  src/components/     (ui/ + layout/)
assets/lib/         →  src/lib/            (utils.ts, nav.ts, data.ts)
assets/index.css    →  src/index.css       (token → biến CSS shadcn)
assets/tailwind.config.ts   →  ./tailwind.config.ts
assets/postcss.config.js    →  ./postcss.config.js
assets/components.json      →  ./components.json
```

**4. Bật alias `@` → `src/`** trong `vite.config.ts` và `tsconfig.json`:

```ts
// vite.config.ts
resolve: { alias: { '@': path.resolve(__dirname, './src') } }
```
```jsonc
// tsconfig.json → compilerOptions
"baseUrl": ".", "paths": { "@/*": ["./src/*"] }
```

**5. Nạp font Roboto** trong `index.html`:

```html
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700&display=swap" rel="stylesheet" />
```

**6. Bọc app** bằng `BrowserRouter` (`main.tsx`) và dùng `AppLayout` làm khung route cha.
`AppLayout` đã gắn sẵn `TooltipProvider` + `<Toaster />`.

## Nội dung `assets/`

| Đường dẫn | Vai trò |
|---|---|
| `index.css` | **Nguồn token**: biến HSL cho shadcn (`--primary`, `--success`, `--warning`…), Roboto, `::selection`, con trỏ pointer cho mọi `<button>` |
| `tailwind.config.ts` | Map biến → utility; thang `brand.50…700`; `shadow-card/pop/brand` |
| `components.json` | Cấu hình shadcn CLI (alias, style `new-york`) — để thêm component mới bằng CLI sau này |
| `lib/utils.ts` | `cn()` + `formatMoney()` |
| `lib/nav.ts`, `lib/data.ts` | Điều hướng (kèm **submenu**) + dữ liệu mẫu (`CHART_COLORS`, KPI, contacts) — thay bằng dữ liệu thật |
| `lib/settings-config.ts` | Kiểu `FieldSpec` / `Block` / `ScreenSpec` / `CategorySpec` cho màn Cài đặt khai báo bằng dữ liệu |
| `components/ui/` | Bộ component (xem `references/component-catalog.md`) |
| `components/layout/` | `sidebar` (có submenu) · `topbar` · `command-palette` · `app-layout` |
| `lib/chat-data.ts` | Dữ liệu mẫu hộp thư: hội thoại, tin nhắn, SLA, mẫu trả lời nhanh |
| `components/settings/` | `field-control` (10 kiểu field) · `catalog-block` (bảng + hộp thoại thêm/sửa) · `custom-blocks` (ma trận quyền, thẻ kênh kết nối) |
| `login.tsx` | Trang đăng nhập mẫu → copy thành `src/pages/login.tsx`, route **ngoài** `AppLayout` |
| `settings.tsx` | Hub Cài đặt hai tầng (nhóm → tab màn) → copy thành `src/pages/settings.tsx` |
| `chat.tsx` | Hộp thư hội thoại 3 cột chiếm trọn chiều cao → `src/pages/chat.tsx` |
| `contact-detail.tsx` | Hồ sơ khách hàng 360° (KPI, vòng tiến độ, đường ống, tab) → `src/pages/contact-detail.tsx` |
| `App.tsx` | Router mẫu kèm route `/login` standalone — tham khảo, đừng ghi đè nếu dự án đã có routes |

### Điều hướng nhiều tầng

`NavItem` có `children` thì sidebar tự render thành submenu mở/đóng, tự bung khi đang
ở một route con (`/settings/*`). Module chỉ có **một** màn thì bỏ `children` để nó là
link phẳng — submenu một mục là nhiễu. Sidebar bị ẩn dưới 1024px, nên màn nào dựa vào
submenu để chuyển nhóm phải có đường nhảy thay thế trong trang (xem dải chip
`lg:hidden` trong `settings.tsx`).

### Màn Cài đặt khai báo bằng dữ liệu

Phần lớn màn cài đặt chỉ là *form field* hoặc *bảng danh mục + hộp thoại thêm/sửa*.
Mô tả chúng trong `settings-config.ts` rồi để `FormBlock` / `CatalogBlock` render —
20 màn không cần 20 file JSX gần giống nhau. Màn thật sự đặc thù dùng
`{ kind: 'custom', id }` và tự viết component.

## Cách dựng màn hình mới

1. Tạo `src/pages/<ten>.tsx`, khai báo route, thêm mục vào `src/lib/nav.ts`.
2. Bố cục bằng Tailwind utility; **control luôn lấy từ `@/components/ui/*`**, không viết lại.
3. Số liệu dùng `tabular-nums`; tiền dùng `formatMoney()`.
4. Hành động có phản hồi: `toast` của sonner.
5. Chạy `references/checklist.md` trước khi coi là xong.

## Đổi thương hiệu cho sản phẩm khác (CRM → HRM)

Chỉ sửa **`index.css`** (biến HSL) và thang `brand.*` trong `tailwind.config.ts`. Mọi component
ăn theo biến nên đổi màu là đổi cả hệ. Font đổi ở `index.html` + `fontFamily.sans`.

## Ràng buộc đã bake sẵn — đừng phá

- **Thang mực 3 bậc** `#363F4D` → `#4A5566` → `#616E80` (10.6 · 7.6 · 5.0 : 1). Đừng
  kéo bậc phụ sát bậc chính (màn sẽ nặng và bẹt) cũng đừng làm nhạt hơn (rơi dưới sàn).
- **`--primary` là brand-600 `#1F6BDC`, không phải brand-500 `#2E7DF0`.** Trên
  brand-500, cả chữ trắng trên nền nút lẫn `text-primary` trên nền trắng chỉ đạt
  3.97:1. brand-500 chỉ dùng cho gradient và nền nhạt.
- **Không hạ độ đục cho chữ, placeholder hay icon** (`text-muted-foreground/70`…):
  đã nhân với nền là rơi xuống ~3:1. Chỉ thành phần `disabled` được phép mờ.
- **Badge/Alert nền đặc, chữ trắng** (tông `-600`, warn `#9A5D06`) để luôn ≥ 4.5:1.
- **Mọi `<button>` dùng con trỏ pointer** (khai báo trong `index.css`).
- **`::selection`** nền brand nhạt + giữ màu chữ.
- **Lịch**: `fixedWeeks` (6 hàng, popover không nhảy) và ngày chọn dùng `!text-primary-foreground`
  — bỏ dấu `!` là class nền `day` đè mất màu trắng.
- **Biểu đồ**: cột gradient dọc, bo đầu 4px, nhãn số trên cột, lưới nét đứt (theo `DESIGN.md`).
- Icon dùng `lucide-react`; không emoji.

## Thêm component shadcn khác

```bash
npx shadcn@latest add slider toggle-group hover-card
```
`components.json` đã cấu hình sẵn alias và style; nhớ chỉnh lại token màu cho khớp hệ.
