Skip to content

Thêm component C*

Quy trình đưa một component mới vào @tasco/ui, từ quyết định có nên làm tới lúc có trang docs. Claude Code trong repo core có skill /new-component <Tên> làm phần lớn các bước, gồm soạn nháp trang docs bằng skill /doc-component — trang này là checklist để làm tay hoặc để duyệt kết quả của skill.

Trước khi viết

  1. Đã có chưa? Xem packages/ui/src/index.ts: ngoài 17 component thương hiệu còn nhiều primitive antdv đã re-export (CInput, CSelect, CModal…). Cần một primitive antdv chưa re-export thì chỉ thêm alias — xem mục cuối.
  2. Có đáng vào core không? Trả lời bộ câu hỏi "core-worthy" trong RFC Process. Component chứa tên hay quy tắc nghiệp vụ của một sản phẩm thì để ở repo sản phẩm.
  3. Có cần RFC không? Component mới là API công khai (tầng T2): nên có RFC khi nhiều team dùng hoặc API chưa rõ.
  4. Tên theo dạng C<PascalCase>, vd CDateRangeLabel.

1. Component

packages/ui/src/components/<Tên>.vue — lấy CStatus.vue làm mẫu.

vue
<script setup lang="ts">
import { Tag } from 'ant-design-vue'

// Nhãn khoảng ngày: hiển thị "01/09/2026 – 30/09/2026", rút gọn khi cùng tháng.
defineOptions({ name: 'CDateRangeLabel' })

const props = withDefaults(defineProps<{
  /** Ngày bắt đầu, chuỗi `YYYY-MM-DD`. */
  from: string
  /** Ngày kết thúc, chuỗi `YYYY-MM-DD`. */
  to: string
  /** Rút gọn khi hai ngày cùng tháng: `01 – 30/09/2026`. */
  compact?: boolean
}>(), {
  compact: false,
})

const emit = defineEmits<{
  /** Bấm vào nhãn. */
  click: [event: MouseEvent]
}>()

defineSlots<{
  /** Icon trước nhãn. */
  icon?: () => unknown
}>()
</script>
Quy tắcVì sao
Comment // đầu <script setup> mô tả componentThành mô tả trong component-meta.json và MCP
defineOptions({ name }) trùng tên fileTên hiện đúng trong devtools, test tìm component theo tên
withDefaults(defineProps<…>()), JSDoc cho mọi prop, event, slotdocs-coverage --strict fail nếu thiếu mô tả
Khai slot bằng defineSlotsKhông khai thì slot không có trong bảng API
Import ant-design-vue ở đây là được phép@tasco/ui là nơi duy nhất bọc antdv
<style scoped>, class BEM c-<tên>__phần--biến-thểKhông đụng style của component khác, app ghi đè được có chủ đích
Màu, khoảng cách chỉ dùng var(--tasco-*)Đúng theme và chế độ tối; cần giá trị mới thì thêm token trước
Icon dùng @tabler/icons-vueMột bộ icon cho cả hệ thống
Dùng sai API thì throw new Error('[@tasco/ui] … cách sửa')Lỗi nói rõ cách sửa thay vì hỏng âm thầm
Không cấu hình style qua theme.componentsantdv 4 bỏ qua; style chung đặt ở @tasco/theme/base.css

2. Story (tuỳ chọn)

Storybook chỉ còn là công cụ dev cục bộ, không deploy (RFC 0003, chốt ở GĐ5). Nó chạy thẳng trên source nên sửa component thấy ngay, và có panel Accessibility (axe) — docs và playground dùng dist, phải build lại mới thấy thay đổi.

Viết <Tên>.stories.ts khi có ích cho việc phát triển, vd component nhiều trạng thái hay cần soi a11y: đặt cạnh component, mỗi story một nhóm biến thể, theo khuôn CStatus.stories.ts (title: 'Components/<Tên>'). Ví dụ cho người dùng framework là demo trong trang docs (mục 5), không phải story.

3. Test

<Tên>.test.ts cạnh component — Vitest + @vue/test-utils, môi trường happy-dom.

  • Stub primitive antdv bằng vi.mock('ant-design-vue', …) đặt trước import component: test hành vi của lớp bọc, không test lại antdv.
  • Tên test dạng it('<hành vi> → <kết quả>').
  • Phủ giá trị mặc định, từng prop chính, từng biến thể, lỗi khi dùng sai.
  • Thêm đường dẫn component vào coverage.include trong packages/ui/vitest.config.ts@tasco/ui chỉ tính ngưỡng coverage cho file có trong danh sách này.

Chi tiết: Test & coverage.

4. Đăng ký

Thiếu một chỗ là lỗi âm thầm: component chạy ở chỗ này nhưng không có type hoặc không có ở chỗ khác.

FileThêm
packages/ui/src/index.tsexport { default as <Tên> } from './components/<Tên>.vue'export type của kiểu đi kèm
packages/ui/src/install.tsImport, thêm vào cả khai báo kiểu lẫn object components — kiểu phải viết tường minh <Tên>: typeof <Tên>
packages/nuxt-layer-base/tasco.d.tsImport type và dòng trong GlobalComponents — app sản phẩm có type cho component toàn cục
docs/.vitepress/env.d.tsDòng trong GlobalComponents — demo trong docs được vue-tsc kiểm như code app

install.ts khai kiểu tường minh vì để TypeScript tự suy luận thì d.ts in lại cả cây kiểu component, và build dừng ở chốt chặn typeof import(...) trong vite.config.ts.

5. Trang docs

docs-coverage --strict fail nếu component đăng ký toàn cục mà chưa có docs/components/<tên-kebab>/index.md.

  1. Tạo docs/components/c-date-range-label/index.md theo chuẩn trang component và demo trong demos/.
  2. Thêm mục vào sidebar (docs/.vitepress/config.ts, nhóm Component phù hợp) và vào bảng ở Tổng quan bộ C*.
  3. Chạy pnpm turbo run dev --filter=docs xem demo chạy thật ở cả chế độ sáng và tối.

Skill /doc-component <Tên> soạn nháp trang và demo từ source, test và story sẵn có. Nháp chỉ là điểm bắt đầu: người mở MR đọc lại từng câu, chạy thử từng demo và sửa chỗ AI đoán sai hành vi.

6. Changeset và kiểm tra

md
---
"@tasco/ui": minor
"@tasco/nuxt-layer-base": patch
---

Thêm component `CDateRangeLabel`: nhãn khoảng ngày, rút gọn khi cùng tháng.
bash
pnpm --filter @tasco/ui test
pnpm --filter @tasco/ui lint
pnpm --filter @tasco/ui typecheck
pnpm --filter @tasco/nuxt-layer-base typecheck
pnpm build && node scripts/docs-coverage.mjs --strict

Thử trên playground (pnpm turbo run dev --filter=playground) và ghi bước thử vào Test plan của MR.

Checklist MR

  • [ ] Component có comment mô tả, defineOptions, JSDoc cho mọi prop/event/slot, defineSlots.
  • [ ] Chỉ dùng biến --tasco-*; đã xem ở chế độ tối.
  • [ ] Test; đã thêm vào coverage.include.
  • [ ] Đăng ký đủ 4 file.
  • [ ] Trang docs, demo, sidebar, bảng tổng quan.
  • [ ] Changeset.

Chỉ thêm primitive antdv

Khi chỉ cần dùng một component antdv chưa có trong @tasco/ui, không tạo wrapper:

  1. Thêm alias vào khối re-export trong packages/ui/src/index.ts, đúng nhóm: Timeline as CTimeline.
  2. Ghi vào trang nhóm tương ứng trong docs/components/primitives/.
  3. Changeset minor cho @tasco/ui.

Primitive không đăng ký toàn cục — app import từ @tasco/ui. Chưa có yêu cầu style riêng thì không cần story và test.

Liên quan