@openway/ui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +203 -181
  2. package/dist/chunk-CJQNI3QX.js +3 -0
  3. package/dist/chunk-CJQNI3QX.js.map +1 -0
  4. package/dist/index.cjs +5 -1
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +2642 -1103
  7. package/dist/index.d.ts +2642 -1103
  8. package/dist/index.js +5 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/query.cjs +3 -0
  11. package/dist/query.cjs.map +1 -0
  12. package/dist/query.d.cts +309 -0
  13. package/dist/query.d.ts +309 -0
  14. package/dist/query.js +3 -0
  15. package/dist/query.js.map +1 -0
  16. package/dist/useInfiniteScroll-DAlgjUN4.d.cts +325 -0
  17. package/dist/useInfiniteScroll-DAlgjUN4.d.ts +325 -0
  18. package/docs/AGENTS.md +144 -0
  19. package/docs/README.md +150 -0
  20. package/docs/SKILL.md +54 -0
  21. package/docs/components/alert.md +246 -0
  22. package/docs/components/badge.md +238 -0
  23. package/docs/components/button.md +262 -0
  24. package/docs/components/carousel.md +354 -0
  25. package/docs/components/checkbox.md +252 -0
  26. package/docs/components/collapse.md +318 -0
  27. package/docs/components/confirm.md +322 -0
  28. package/docs/components/datepicker.md +259 -0
  29. package/docs/components/daterangepicker.md +260 -0
  30. package/docs/components/datetimepicker.md +226 -0
  31. package/docs/components/datetimerangepicker.md +222 -0
  32. package/docs/components/dropdown.md +275 -0
  33. package/docs/components/empty.md +200 -0
  34. package/docs/components/file-preview.md +180 -0
  35. package/docs/components/input.md +559 -0
  36. package/docs/components/modal.md +293 -0
  37. package/docs/components/popover.md +255 -0
  38. package/docs/components/radio.md +245 -0
  39. package/docs/components/select.md +254 -0
  40. package/docs/components/skeleton.md +150 -0
  41. package/docs/components/slider.md +346 -0
  42. package/docs/components/table.md +316 -0
  43. package/docs/components/tabs.md +432 -0
  44. package/docs/components/textarea.md +193 -0
  45. package/docs/components/timepicker.md +242 -0
  46. package/docs/components/timerangepicker.md +210 -0
  47. package/docs/components/toast.md +282 -0
  48. package/docs/components/toggle.md +211 -0
  49. package/docs/components/tooltip.md +213 -0
  50. package/docs/components/upload-avatar.md +318 -0
  51. package/docs/components/upload-file.md +245 -0
  52. package/docs/components/upload-image.md +126 -0
  53. package/docs/hooks/useDebounce.md +92 -0
  54. package/docs/hooks/useInfiniteScroll.md +95 -0
  55. package/docs/hooks/useSelectInfiniteQuery.md +123 -0
  56. package/docs/hooks/useTableQuery.md +124 -0
  57. package/package.json +31 -12
@@ -0,0 +1,282 @@
1
+ # 🍞 Toast Component & API (`@owa/ui`)
2
+
3
+ Hệ thống thông báo dạng pop-up nổi (**Toast Notifications**) với hiệu ứng **xếp chồng thẻ 3D (Card Stacking)** mượt mà từ động cơ **Sonner**, tích hợp trực quan hoàn hảo với thiết kế của component **`<Alert />`** chuẩn **Design System**, **100% Type-safe (Zero `any`)** và hỗ trợ **WAI-ARIA Accessibility**.
4
+
5
+ ---
6
+
7
+ ## 🌟 Điểm nổi bật
8
+
9
+ - **Tích hợp giao diện `<Alert />` 100%**: Mọi thông báo Toast đều kế thừa toàn bộ vẻ đẹp của `<Alert />` (5 sizes, 6 variants, 7 colors, custom icon, action button, bo góc).
10
+ - **Xếp chồng 3D thông minh (`Card Stacking`)**: Mặc định (`expand={false}`) các thông báo sẽ tự động xếp lồng vào nhau thành 1 tập thẻ 3D gọn gàng, tự động trượt mở rộng khi người dùng rê chuột (hover) vào.
11
+ - **Imperative API tiện lợi**: Có thể gọi hàm `toast.success()`, `toast.error()` ở bất kỳ đâu (trong React Component, Event Handler, API Axios/Fetch Interceptors) mà không cần bọc context provider phức tạp.
12
+ - **Cú pháp chuẩn hóa gọn gàng**: `toast.success(title, description?, options?)` — trực quan, dễ nhớ, dễ dùng.
13
+ - **Xử lý tự động Promise (`toast.promise`)**: Tự động quản lý vòng đời Async Operation: hiển thị spinner lúc tải (*Loading*), tự động cập nhật sang *Success* hoặc *Error* mượt mà tại cùng 1 thẻ thông báo.
14
+ - **Type Safety tuyệt đối**: Không sử dụng `any`, hỗ trợ Generic `<T>` chuẩn xác cho dữ liệu trả về từ API trong `toast.promise`.
15
+ - **Hằng số chuẩn hóa**: Sử dụng `DEFAULT_TOAST_DURATION = 4000ms`, dễ dàng tùy biến thời gian hiển thị toàn cục hoặc cục bộ.
16
+
17
+ ---
18
+
19
+ ## 🚀 Cài đặt & Thiết lập
20
+
21
+ ### Bước 1: Gắn `<Toaster />` tại Root Layout
22
+ Mount component `<Toaster />` một lần duy nhất tại file layout cao nhất của ứng dụng (ví dụ: `App.tsx`, `main.tsx` hoặc `app/layout.tsx`):
23
+
24
+ ```tsx
25
+ import { Toaster } from "@owa/ui";
26
+
27
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
28
+ return (
29
+ <html lang="vi">
30
+ <body>
31
+ {children}
32
+ {/* Đặt Toaster ở cuối body */}
33
+ <Toaster position="top-right" duration={4000} />
34
+ </body>
35
+ </html>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ---
41
+
42
+ ### Bước 2: Gọi thông báo ở bất kỳ đâu
43
+ ```tsx
44
+ import { toast } from "@owa/ui";
45
+
46
+ export function SaveButton() {
47
+ const handleSave = () => {
48
+ toast.success("Lưu thành công!", "Dữ liệu hồ sơ của bạn đã được cập nhật.");
49
+ };
50
+
51
+ return <button onClick={handleSave}>Lưu thông tin</button>;
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 📖 Hướng dẫn sử dụng chi tiết
58
+
59
+ ### 1. Các phương thức thông báo cơ bản
60
+
61
+ ```tsx
62
+ import { toast } from "@owa/ui";
63
+
64
+ // 1. Thành công (Success)
65
+ toast.success("Thành công!", "Tạo tài khoản mới thành công.");
66
+
67
+ // 2. Lỗi (Error)
68
+ toast.error("Đã xảy ra lỗi!", "Không thể kết nối đến máy chủ.");
69
+
70
+ // 3. Cảnh báo (Warning)
71
+ toast.warning("Cảnh báo dung lượng", "Bộ nhớ đám mây của bạn sắp đầy (90%).");
72
+
73
+ // 4. Thông tin (Info / Mặc định)
74
+ toast.info("Có bản cập nhật mới", "Vui lòng tải lại trang để áp dụng.");
75
+ // hoặc gọi trực tiếp:
76
+ toast("Thông báo hệ thống", "Phiên làm việc sẽ hết hạn sau 15 phút.");
77
+
78
+ // 5. Đang tải (Loading - không tự tắt)
79
+ const loadingId = toast.loading("Đang đồng bộ dữ liệu...", "Vui lòng không tắt trình duyệt.");
80
+ ```
81
+
82
+ ---
83
+
84
+ ### 2. Tùy biến giao diện Alert qua `options`
85
+
86
+ Bạn có thể truyền tham số thứ 3 (`options`) để thay đổi biến thể hiển thị, thời gian, kích cỡ hoặc thêm nút thao tác:
87
+
88
+ ```tsx
89
+ import { toast, Button } from "@owa/ui";
90
+
91
+ // Biến thể nền đậm (filled) và hiển thị trong 8 giây:
92
+ toast.error("Xóa thất bại!", "Bạn không có quyền xóa tài nguyên này.", {
93
+ variant: "filled",
94
+ duration: 8000,
95
+ });
96
+
97
+ // Thêm nút bấm Action slot:
98
+ toast.info("Đã chuyển vào thùng rác", "Tệp tin 'tailieu.pdf' đã bị xóa.", {
99
+ action: (
100
+ <Button
101
+ size="xs"
102
+ variant="outline"
103
+ color="info"
104
+ onClick={() => {
105
+ // Xử lý logic hoàn tác
106
+ console.log("Hoàn tác");
107
+ }}
108
+ >
109
+ Hoàn tác
110
+ </Button>
111
+ ),
112
+ });
113
+
114
+ // Thay đổi độ bo góc & size:
115
+ toast.success("Đã sao chép liên kết!", undefined, {
116
+ size: "sm",
117
+ radius: "full",
118
+ });
119
+ ```
120
+
121
+ ---
122
+
123
+ ### 3. Tự động hóa gọi API bất đồng bộ với `toast.promise`
124
+
125
+ `toast.promise` tự động hiển thị trạng thái đang tải (Loading spinner), sau đó cập nhật sang Success hoặc Error khi API hoàn tất:
126
+
127
+ #### Cách 1: Chuỗi thông báo đơn giản
128
+ ```tsx
129
+ import { toast } from "@owa/ui";
130
+
131
+ async function handleUpdateProfile() {
132
+ await toast.promise(updateUserApi(data), {
133
+ loading: "Đang cập nhật hồ sơ...",
134
+ success: "Cập nhật hồ sơ thành công!",
135
+ error: "Không thể cập nhật hồ sơ.",
136
+ });
137
+ }
138
+ ```
139
+
140
+ #### Cách 2: Sử dụng dữ liệu trả về từ API và tùy biến giao diện
141
+ ```tsx
142
+ import { toast } from "@owa/ui";
143
+
144
+ interface Invoice {
145
+ code: string;
146
+ total: number;
147
+ }
148
+
149
+ async function handleCreateInvoice() {
150
+ await toast.promise<Invoice>(createInvoiceApi(), {
151
+ loading: {
152
+ title: "Đang tạo hóa đơn...",
153
+ description: "Hệ thống đang xuất mã hóa đơn điện tử.",
154
+ },
155
+ success: (invoice) => ({
156
+ title: "Tạo hóa đơn thành công!",
157
+ description: `Mã hóa đơn: #${invoice.code} - Tổng tiền: ${invoice.total.toLocaleString()}đ`,
158
+ variant: "filled",
159
+ }),
160
+ error: (err) => ({
161
+ title: "Xuất hóa đơn thất bại!",
162
+ description: err instanceof Error ? err.message : "Đã có lỗi xảy ra.",
163
+ }),
164
+ finally: () => {
165
+ console.log("Hoàn tất tác vụ.");
166
+ },
167
+ });
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ### 4. Đóng thông báo chủ động (`toast.dismiss`)
174
+
175
+ ```tsx
176
+ import { toast } from "@owa/ui";
177
+
178
+ // 1. Đóng một thông báo cụ thể bằng ID nhận được:
179
+ const id = toast.loading("Đang nén tệp...");
180
+ // ...sau khi xong việc:
181
+ toast.dismiss(id);
182
+
183
+ // 2. Đóng TẤT CẢ thông báo đang hiện trên màn hình:
184
+ toast.dismiss();
185
+ ```
186
+
187
+ ---
188
+
189
+ ### 5. Tùy biến toàn diện với `toast.custom`
190
+
191
+ Nếu bạn muốn hiển thị một component JSX bất kỳ theo phong cách riêng:
192
+
193
+ ```tsx
194
+ import { toast } from "@owa/ui";
195
+
196
+ // Cách 1: Dùng hàm render (nhận vào id để đóng)
197
+ toast.custom((id) => (
198
+ <div className="p-4 bg-purple-900 text-white rounded-xl shadow-xl flex items-center justify-between">
199
+ <span>✨ Thông báo khuyến mãi đặc biệt!</span>
200
+ <button
201
+ onClick={() => toast.dismiss(id)}
202
+ className="ml-3 px-2 py-1 bg-white/20 hover:bg-white/30 rounded text-xs"
203
+ >
204
+ Đóng
205
+ </button>
206
+ </div>
207
+ ));
208
+
209
+ // Cách 2: Truyền trực tiếp AlertProps
210
+ toast.custom({
211
+ color: "secondary",
212
+ variant: "outline",
213
+ title: "Tùy biến qua AlertProps",
214
+ description: "Render trực tiếp cấu hình Alert.",
215
+ });
216
+ ```
217
+
218
+ ---
219
+
220
+ ## ⚙️ Bảng tra cứu API Reference
221
+
222
+ ### 1. `ToasterProps` (Cấu hình cho thẻ `<Toaster />`)
223
+
224
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
225
+ | :--- | :--- | :--- | :--- |
226
+ | `position` | `ToastPosition` | `'top-right'` | Vị trí hiển thị danh sách Toast trên màn hình (`'top-left'`, `'top-right'`, `'top-center'`, `'bottom-left'`, `'bottom-right'`, `'bottom-center'`). |
227
+ | `visibleToasts` | `number` | `3` | Số lượng thông báo tối đa hiển thị cùng lúc trước khi xếp chồng 3D. |
228
+ | `expand` | `boolean` | `false` | Mặc định `false` để gộp thành stack thẻ 3D (chỉ bung ra khi hover). Nếu `true` sẽ luôn bung rộng toàn bộ. |
229
+ | `duration` | `number` | `4000` (`DEFAULT_TOAST_DURATION`) | Thời gian hiển thị mặc định của các toasts (ms). |
230
+ | `closeButton` | `boolean` | `false` | Hiển thị nút đóng mặc định của Sonner (Alert đã tích hợp sẵn nút đóng đẹp mắt). |
231
+ | `className` | `string` | `""` | Tùy biến class cho container bao ngoài Toaster. |
232
+
233
+ ---
234
+
235
+ ### 2. `ToastOptions` (Tham số `options` khi gọi `toast.xxx`)
236
+
237
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
238
+ | :--- | :--- | :--- | :--- |
239
+ | `duration` | `number` | `4000` | Thời gian hiển thị của thông báo này (ms). Truyền `Infinity` nếu muốn giữ mãi. |
240
+ | `position` | `ToastPosition` | Theo Toaster | Ghi đè vị trí hiển thị chỉ riêng cho thông báo này. |
241
+ | `variant` | `AlertVariant` | `'soft'` | Biến thể giao diện Alert (`'soft'`, `'filled'`, `'outline'`, `'accent-left'`, `'ghost'`, `'other'`). |
242
+ | `color` | `AlertColor` | Theo hàm gọi | Màu sắc chủ đề (`'primary'`, `'secondary'`, `'neutral'`, `'error'`, `'success'`, `'warning'`, `'info'`). |
243
+ | `size` | `AlertSize` | `'md'` | Kích cỡ thông báo (`'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`). |
244
+ | `radius` | `AlertRadius` | `'lg'` | Độ bo góc (`'none'`, `'sm'`, `'md'`, `'lg'`, `'xl'`, `'full'`). |
245
+ | `icon` | `ReactNode \| boolean` | `true` | Icon hiển thị (`true`: icon mặc định, `false`: ẩn icon, `ReactNode`: icon tùy chọn). |
246
+ | `action` | `ReactNode` | `undefined` | Phần tử nút bấm / liên kết thao tác nhanh trong Alert. |
247
+ | `closable` | `boolean` | `true` | Cho phép đóng thông báo qua nút `(X)` (tự động dismiss khỏi Sonner). |
248
+ | `onClose` | `() => void` | `undefined` | Callback chạy khi người dùng bấm nút đóng `(X)`. |
249
+ | `onDismiss` | `(toast: ToastT) => void` | `undefined` | Callback chạy khi toast bị dismiss khỏi màn hình. |
250
+ | `onAutoClose` | `(toast: ToastT) => void` | `undefined` | Callback chạy khi toast tự động đóng do hết thời gian `duration`. |
251
+ | `className` | `string` | `""` | Class tùy biến cho khung Alert. |
252
+
253
+ ---
254
+
255
+ ### 3. `ToastPromiseOptions<T>` (Cấu hình cho `toast.promise`)
256
+
257
+ | Thuộc tính | Kiểu dữ liệu | Mô tả |
258
+ | :--- | :--- | :--- |
259
+ | `loading` | `ToastMessageResult` | Nội dung hiển thị trong lúc Promise đang chạy (Loading state). |
260
+ | `success` | `ToastMessageResult \| (data: T) => ToastMessageResult` | Nội dung hiển thị khi Promise hoàn tất thành công (Resolve state). |
261
+ | `error` | `ToastMessageResult \| (error: unknown) => ToastMessageResult` | Nội dung hiển thị khi Promise thất bại (Reject state). |
262
+ | `finally` | `() => void \| Promise<void>` | Callback luôn chạy sau khi Promise hoàn tất (cả success và error). |
263
+ | `duration` | `number` | Thời gian hiển thị thông báo kết quả (mặc định 4000ms). |
264
+ | `size` | `AlertSize` | Kích cỡ thông báo áp dụng cho cả chu trình promise. |
265
+ | `variant` | `AlertVariant` | Biến thể Alert áp dụng cho cả chu trình promise. |
266
+ | `radius` | `AlertRadius` | Độ bo góc áp dụng cho cả chu trình promise. |
267
+
268
+ ---
269
+
270
+ ### 4. Danh sách các phương thức `toast`
271
+
272
+ | Phương thức | Cú pháp | Mô tả |
273
+ | :--- | :--- | :--- |
274
+ | `toast()` | `toast(title, description?, options?)` | Hiển thị Toast thông tin mặc định (`color="info"`). |
275
+ | `toast.success()` | `toast.success(title, description?, options?)` | Hiển thị Toast thành công (`color="success"`). |
276
+ | `toast.error()` | `toast.error(title, description?, options?)` | Hiển thị Toast lỗi (`color="error"`). |
277
+ | `toast.warning()` | `toast.warning(title, description?, options?)` | Hiển thị Toast cảnh báo (`color="warning"`). |
278
+ | `toast.info()` | `toast.info(title, description?, options?)` | Hiển thị Toast thông tin (`color="info"`). |
279
+ | `toast.loading()` | `toast.loading(title, description?, options?)` | Hiển thị Toast đang tải dữ liệu (`icon=Spinner`, `duration=Infinity`). |
280
+ | `toast.promise()` | `toast.promise(promise, options)` | Tự động theo dõi Promise và cập nhật trạng thái Toast. |
281
+ | `toast.custom()` | `toast.custom(jsxFn \| alertProps, options?)` | Hiển thị Toast tùy biến hoàn toàn. |
282
+ | `toast.dismiss()` | `toast.dismiss(id?)` | Đóng Toast theo ID hoặc đóng toàn bộ nếu không truyền ID. |
@@ -0,0 +1,211 @@
1
+ # 🎚️ Toggle Component (`@owa/ui`)
2
+
3
+ Component **Toggle** (Switch công tắc) hiện đại, linh hoạt, tương tác cao, thiết kế chuẩn **Design System**, hỗ trợ **Safe Config Fallback**, **Start/End Content & Thumb Icons**, **Loading & Spinners**, và tuân thủ đầy đủ tiêu chuẩn **WAI-ARIA Accessibility** (`role="switch"`).
4
+
5
+ ---
6
+
7
+ ## 🌟 Điểm nổi bật
8
+
9
+ - **5 Kích thước tiêu chuẩn (`size`)**:
10
+ - `xs`: Track 28x16px, thumb 12px, text 12px.
11
+ - `sm`: Track 36x20px, thumb 14px, text 14px.
12
+ - `md` *(mặc định)*: Track 44x24px, thumb 20px, text 14px.
13
+ - `lg`: Track 52x28px, thumb 24px, text 16px.
14
+ - `xl`: Track 64x36px, thumb 28px, text 18px.
15
+ - **4 Biến thể giao diện (`variant`)**:
16
+ - `filled` *(mặc định)*: Nền track màu đặc tương phản cao khi bật.
17
+ - `outline`: Nền track trong suốt/trắng, viền và thumb mang màu chủ đề.
18
+ - `soft`: Nền track pastel dịu nhẹ theo tone màu chủ đề.
19
+ - `other`: Bỏ qua style mặc định, tự do tùy biến hoàn toàn qua `trackClassName` và `thumbClassName`.
20
+ - **7 Chủ đề màu sắc (`color`)**: `primary`, `secondary`, `error`, `success`, `warning`, `info`, `neutral`.
21
+ - **6 Mức độ bo góc (`radius` & `thumbRadius`)**: `none`, `sm`, `md`, `lg`, `xl`, `full` *(mặc định: `full`)*.
22
+ - **2 Vị trí đặt nhãn (`labelPlacement`)**:
23
+ - `right` *(mặc định)*: Toggle bên trái, nhãn bên phải.
24
+ - `left`: Nhãn bên trái, toggle bên phải.
25
+ - **Tùy biến Icon & Nội dung linh hoạt**:
26
+ - `thumbIcon`: Icon tùy biến bên trong nút trượt (hỗ trợ cả ReactNode tĩnh hoặc function `({ isChecked, className }) => ReactNode`).
27
+ - `startContent` & `endContent`: Icon / nội dung hiển thị trực tiếp trong lòng thanh trượt (track).
28
+ - **Trạng thái Loading & Spinners (`isLoading`)**:
29
+ - Tự động hiển thị spinner xoay tròn bên trong nút trượt (thumb) và khóa tương tác (`disabled`).
30
+ - **Trạng thái Báo lỗi & Hướng dẫn (`isInvalid`, `errorMessage`, `helperText`)**:
31
+ - Tự động chuyển đổi màu viền báo lỗi và hiển thị animation mở rộng mượt mà.
32
+ - **Safe Config Fallback**: Tích hợp hàm `getSafeConfig` đảm bảo an toàn tuyệt đối khi truyền props sai hoặc không tồn tại.
33
+ - **React 19 Ref Forwarding**: Tích hợp `useMergeRefs` chuyển tiếp ref trực tiếp đến thẻ `<input type="checkbox" role="switch">`.
34
+
35
+ ---
36
+
37
+ ## 🚀 Cài đặt & Import
38
+
39
+ ```tsx
40
+ import { Toggle } from "@owa/ui";
41
+ import type {
42
+ ToggleProps,
43
+ ToggleConfig,
44
+ ToggleSize,
45
+ ToggleVariant,
46
+ ToggleColor,
47
+ ToggleRadius,
48
+ ToggleLabelPlacement,
49
+ } from "@owa/ui";
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 📖 Hướng dẫn sử dụng
55
+
56
+ ### 1. Cách sử dụng cơ bản
57
+
58
+ ```tsx
59
+ import { useState } from "react";
60
+ import { Toggle } from "@owa/ui";
61
+
62
+ export function BasicToggleExample() {
63
+ const [enabled, setEnabled] = useState(false);
64
+
65
+ return (
66
+ <Toggle
67
+ checked={enabled}
68
+ onChange={(e) => setEnabled(e.target.checked)}
69
+ label="Bật tính năng thông báo"
70
+ />
71
+ );
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ### 2. Vị trí đặt nhãn (`labelPlacement`)
78
+
79
+ ```tsx
80
+ // 1. Label bên phải (Mặc định)
81
+ <Toggle label="Label bên phải" labelPlacement="right" />
82
+
83
+ // 2. Label bên trái
84
+ <Toggle label="Label bên trái" labelPlacement="left" />
85
+ ```
86
+
87
+ ---
88
+
89
+ ### 3. Biến thể (`variant`) & Màu sắc (`color`)
90
+
91
+ ```tsx
92
+ // Filled (mặc định)
93
+ <Toggle variant="filled" color="primary" label="Primary Filled" defaultChecked />
94
+ <Toggle variant="filled" color="success" label="Success Filled" defaultChecked />
95
+
96
+ // Outline
97
+ <Toggle variant="outline" color="primary" label="Primary Outline" defaultChecked />
98
+
99
+ // Soft (Pastel)
100
+ <Toggle variant="soft" color="secondary" label="Secondary Soft" defaultChecked />
101
+
102
+ // Other (Custom gradient)
103
+ <Toggle
104
+ variant="other"
105
+ trackClassName="bg-gradient-to-r from-purple-600 to-pink-500 border-0"
106
+ thumbClassName="bg-white text-purple-600 shadow-md"
107
+ label="Custom Gradient"
108
+ defaultChecked
109
+ />
110
+ ```
111
+
112
+ ---
113
+
114
+ ### 4. Icon bên trong Thumb & Start/End Content
115
+
116
+ ```tsx
117
+ // Static icon trong Thumb
118
+ <Toggle thumbIcon={<span>🔒</span>} label="Bảo mật" />
119
+
120
+ // Dynamic icon thay đổi theo trạng thái checked
121
+ <Toggle
122
+ thumbIcon={({ isChecked }) => (
123
+ <span>{isChecked ? "🌙" : "☀️"}</span>
124
+ )}
125
+ label="Chế độ giao diện"
126
+ />
127
+
128
+ // Start / End Content nằm trong Track
129
+ <Toggle
130
+ startContent="☀️"
131
+ endContent="🌙"
132
+ size="lg"
133
+ label="Ngày & Đêm"
134
+ />
135
+ ```
136
+
137
+ ---
138
+
139
+ ### 5. Trạng thái Loading, Error & Helper Text
140
+
141
+ ```tsx
142
+ // Trạng thái Loading (qua config)
143
+ <Toggle config={{ isLoading: true }} label="Đang đồng bộ dữ liệu..." />
144
+
145
+ // Trạng thái Báo lỗi & Bắt buộc
146
+ <Toggle
147
+ config={{ isRequired: true }}
148
+ errorMessage="Bạn phải đồng ý với điều khoản dịch vụ!"
149
+ label="Tôi đồng ý với điều khoản"
150
+ />
151
+
152
+ // Helper text hướng dẫn
153
+ <Toggle
154
+ label="Xác thực 2 bước"
155
+ helperText="Nhận mã OTP qua số điện thoại đã đăng ký."
156
+ />
157
+ ```
158
+
159
+ ---
160
+
161
+ ### 6. Cấu hình tập trung qua prop `config` (`ToggleConfig`)
162
+
163
+ ```tsx
164
+ <Toggle
165
+ label="Tự động sao lưu"
166
+ config={{
167
+ isRequired: true,
168
+ isLoading: false,
169
+ isInvalid: false,
170
+ }}
171
+ />
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 🛠 Bảng thông số Props (`ToggleProps`)
177
+
178
+ | Tên Prop | Kiểu dữ liệu | Giá trị mặc định | Mô tả |
179
+ | :--- | :--- | :---: | :--- |
180
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Kích cỡ toggle (track, thumb, font label, khoảng cách). |
181
+ | `variant` | `'filled' \| 'outline' \| 'soft' \| 'other'` | `'filled'` | Biến thể hiển thị giao diện khi toggle ở trạng thái bật. |
182
+ | `color` | `'primary' \| 'secondary' \| 'error' \| 'success' \| 'warning' \| 'info' \| 'neutral'` | `'primary'` | Chủ đề màu sắc theo Design System. |
183
+ | `radius` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'full'` | Độ bo góc của thanh trượt (track). |
184
+ | `thumbRadius` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'full'` | Độ bo góc của nút trượt (thumb). |
185
+ | `config` | `ToggleConfig` | — | Cấu hình tập trung các cờ trạng thái (`isRequired`, `isInvalid`, `isLoading`). |
186
+ | `label` | `ReactNode` | — | Nhãn văn bản hiển thị cạnh toggle. |
187
+ | `labelPlacement` | `'right' \| 'left'` | `'right'` | Vị trí hiển thị của nhãn so với toggle. |
188
+ | `checked` | `boolean` | — | Trạng thái bật/tắt (Controlled). |
189
+ | `defaultChecked` | `boolean` | `false` | Trạng thái mặc định ban đầu (Uncontrolled). |
190
+ | `disabled` | `boolean` | `false` | Vô hiệu hóa tương tác của toggle. |
191
+ | `readOnly` | `boolean` | `false` | Chế độ chỉ đọc, không cho phép đổi trạng thái. |
192
+ | `helperText` | `ReactNode` | — | Đoạn văn bản hướng dẫn/trợ giúp bên dưới. |
193
+ | `errorMessage` | `ReactNode` | — | Thông báo lỗi (tự động kích hoạt trạng thái báo lỗi). |
194
+ | `thumbIcon` | `ReactNode \| (({ isChecked, className }) => ReactNode)` | — | Icon hiển thị bên trong nút trượt (thumb). |
195
+ | `startContent` | `ReactNode` | — | Nội dung/icon hiển thị bên trong track (phía bên trái khi bật). |
196
+ | `endContent` | `ReactNode` | — | Nội dung/icon hiển thị bên trong track (phía bên phải khi tắt). |
197
+ | `wrapperClassName` | `string` | — | ClassName tùy biến cho container bao bọc (toggle + label). |
198
+ | `trackClassName` | `string` | — | ClassName tùy biến cho thanh trượt (track). |
199
+ | `thumbClassName` | `string` | — | ClassName tùy biến cho nút trượt (thumb). |
200
+ | `labelClassName` | `string` | — | ClassName tùy biến cho nhãn `<label>`. |
201
+ | `helperClassName` | `string` | — | ClassName tùy biến cho đoạn văn bản helperText / errorMessage. |
202
+ | `ref` | `Ref<HTMLInputElement>` | — | Ref chuyển tiếp đến thẻ `<input>` bên dưới. |
203
+
204
+ ### Cấu hình `ToggleConfig`
205
+
206
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
207
+ | :--- | :--- | :---: | :--- |
208
+ | `isRequired` | `boolean` | `false` | Hiển thị dấu `*` đỏ và đánh dấu `aria-required="true"`. |
209
+ | `isInvalid` | `boolean` | `false` | Kích hoạt giao diện báo lỗi và `aria-invalid="true"`. |
210
+ | `isLoading` | `boolean` | `false` | Khóa tương tác (`aria-busy="true"`). |
211
+ | `showSpinner` | `boolean` | `false` | Hiển thị biểu tượng xoay spinner bên trong nút trượt (thumb) khi `isLoading = true`. |