@openway/ui 1.0.0 → 1.0.2

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 (58) hide show
  1. package/README.md +203 -181
  2. package/dist/chunk-X4LIYOS5.js +3 -0
  3. package/dist/chunk-X4LIYOS5.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 +2740 -1473
  7. package/dist/index.d.ts +2740 -1473
  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 +431 -0
  13. package/dist/query.d.ts +431 -0
  14. package/dist/query.js +3 -0
  15. package/dist/query.js.map +1 -0
  16. package/dist/useInfiniteScroll-D9AW6cQV.d.cts +598 -0
  17. package/dist/useInfiniteScroll-D9AW6cQV.d.ts +598 -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/useMutationApp.md +242 -0
  56. package/docs/hooks/useSelectInfiniteQuery.md +123 -0
  57. package/docs/hooks/useTableQuery.md +124 -0
  58. package/package.json +31 -12
@@ -0,0 +1,432 @@
1
+ # 📑 Tabs Component (`@owa/ui`)
2
+
3
+ Bộ component **Tabs** tương tác cao, thiết kế chuẩn **Compound Components Pattern** (`<Tabs>`, `<TabList>`, `<Tab>`, `<TabPanels>`, `<TabPanel>`), tích hợp **Sliding Animated Indicator**, **Overflow Scroll**, **Custom Hook `useTabIndicator`**, **Safe Config Fallback** và tuân thủ đầy đủ tiêu chuẩn **WAI-ARIA Accessibility**.
4
+
5
+ ---
6
+
7
+ ## 🌟 Điểm nổi bật
8
+
9
+ - **Compound Components Pattern chuẩn chỉ**: Tách biệt rõ ràng các thành phần `<Tabs>`, `<TabList>`, `<Tab>`, `<TabPanels>`, `<TabPanel>`, tối đa hóa khả năng tùy biến layout và composition.
10
+ - **Sliding Animated Indicator mượt mà**: Tự động tính toán vị trí, kích thước và chuyển động trượt mượt mà cho mọi variant (`line`, `solid`, `bordered`, `flat`), hỗ trợ tự động căn chỉnh khi resize màn hình thông qua `ResizeObserver`.
11
+ - **Custom Hook `useTabIndicator` độc lập**: Tách biệt toàn bộ logic tính toán toạ độ indicator, kiểm tra tràn viền và điều hướng bàn phím thành hook riêng để dễ bảo trì và tái sử dụng.
12
+ - **Controlled & Uncontrolled Mode**: Hỗ trợ linh hoạt cả `activeKey` + `onChange` (Controlled) và `defaultActiveKey` (Uncontrolled).
13
+ - **3 Kích thước tiêu chuẩn (`size`)**: `sm`, `md` (*mặc định*), `lg`.
14
+ - **5 Biến thể giao diện (`variant`)**:
15
+ - `line` *(mặc định)*: Thanh gạch dưới/bên trượt mượt mà.
16
+ - `solid`: Khối pill nền đậm nổi bật.
17
+ - `bordered`: Bao khung viền quanh tab.
18
+ - `flat`: Khối pill nền mềm nhạt (soft pill).
19
+ - `other`: Tự do tùy biến 100% qua `className`.
20
+ - **7 Chủ đề màu sắc (`color`)**: `primary`, `secondary`, `neutral`, `error`, `success`, `warning`, `info`.
21
+ - **6 Tùy chỉnh bo góc (`radius`)**: `none`, `sm`, `md` (*mặc định*), `lg`, `xl`, `full`.
22
+ - **Hướng & Vị trí đa dạng (`orientation` & `placement`)**:
23
+ - Ngang (`horizontal`): `top` (*mặc định*), `bottom`.
24
+ - Dọc (`vertical`): `left`, `right`.
25
+ - **Cuộn khi tràn viền (Scrollable Overflow & Chevrons)**: Tự động hiển thị 2 nút chevron cuộn trái/phải khi danh sách tabs vượt quá chiều rộng container, tự động cuộn `scrollIntoView` tab active.
26
+ - **Đóng / Xóa tab tiện lợi (`closable`)**: Cho phép click nút đóng hoặc nhấn phím `Delete` / `Backspace` khi đang focus vào tab.
27
+ - **WAI-ARIA Accessibility & Keyboard Navigation**: Hỗ trợ đầy đủ `role="tablist"`, `role="tab"`, `role="tabpanel"`, `aria-selected`, `aria-controls`, phím mũi tên `ArrowLeft` / `ArrowRight` / `ArrowUp` / `ArrowDown`, `Home`, `End`.
28
+ - **Safe Config Fallback**: Tích hợp hàm `getSafeConfig` giúp component luôn an toàn, không bị crash kể cả khi truyền prop không hợp lệ.
29
+
30
+ ---
31
+
32
+ ## 🚀 Cài đặt & Import
33
+
34
+ ```tsx
35
+ import {
36
+ Tabs,
37
+ TabList,
38
+ Tab,
39
+ TabPanels,
40
+ TabPanel,
41
+ useTabsContext,
42
+ useTabIndicator,
43
+ } from "@owa/ui";
44
+
45
+ import type {
46
+ TabsProps,
47
+ TabListProps,
48
+ TabProps,
49
+ TabPanelsProps,
50
+ TabPanelProps,
51
+ TabSize,
52
+ TabVariant,
53
+ TabColor,
54
+ TabRadius,
55
+ TabOrientation,
56
+ TabPlacement,
57
+ UseTabIndicatorOptions,
58
+ } from "@owa/ui";
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 📖 Hướng dẫn sử dụng
64
+
65
+ ### 1. Cách sử dụng cơ bản (Compound Components)
66
+
67
+ ```tsx
68
+ import { Tabs, TabList, Tab, TabPanels, TabPanel } from "@owa/ui";
69
+
70
+ export function BasicTabsExample() {
71
+ return (
72
+ <Tabs defaultActiveKey="overview" variant="line" color="primary">
73
+ <TabList>
74
+ <Tab value="overview" label="Tổng quan" />
75
+ <Tab value="profile" label="Hồ sơ" />
76
+ <Tab value="settings" label="Cài đặt" />
77
+ </TabList>
78
+
79
+ <TabPanels>
80
+ <TabPanel value="overview">
81
+ <p className="p-4 text-neutral-700">Nội dung trang tổng quan</p>
82
+ </TabPanel>
83
+ <TabPanel value="profile">
84
+ <p className="p-4 text-neutral-700">Thông tin hồ sơ người dùng</p>
85
+ </TabPanel>
86
+ <TabPanel value="settings">
87
+ <p className="p-4 text-neutral-700">Cấu hình cài đặt hệ thống</p>
88
+ </TabPanel>
89
+ </TabPanels>
90
+ </Tabs>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ### 2. Chế độ điều khiển (Controlled vs Uncontrolled)
98
+
99
+ #### a) Controlled Mode (Quản lý state từ bên ngoài)
100
+ ```tsx
101
+ import { useState } from "react";
102
+ import { Tabs, TabList, Tab, TabPanels, TabPanel } from "@owa/ui";
103
+
104
+ export function ControlledTabs() {
105
+ const [activeKey, setActiveKey] = useState<string | number>("tab-1");
106
+
107
+ return (
108
+ <Tabs activeKey={activeKey} onChange={(key) => setActiveKey(key)}>
109
+ <TabList>
110
+ <Tab value="tab-1" label="Tab 1" />
111
+ <Tab value="tab-2" label="Tab 2" />
112
+ </TabList>
113
+ <TabPanels>
114
+ <TabPanel value="tab-1">Nội dung 1</TabPanel>
115
+ <TabPanel value="tab-2">Nội dung 2</TabPanel>
116
+ </TabPanels>
117
+ </Tabs>
118
+ );
119
+ }
120
+ ```
121
+
122
+ #### b) Uncontrolled Mode (Tự quản lý nội bộ với `defaultActiveKey`)
123
+ ```tsx
124
+ <Tabs defaultActiveKey="tab-2">
125
+ <TabList>
126
+ <Tab value="tab-1" label="Tab 1" />
127
+ <Tab value="tab-2" label="Tab 2" />
128
+ </TabList>
129
+ <TabPanels>
130
+ <TabPanel value="tab-1">Nội dung 1</TabPanel>
131
+ <TabPanel value="tab-2">Nội dung 2</TabPanel>
132
+ </TabPanels>
133
+ </Tabs>
134
+ ```
135
+
136
+ ---
137
+
138
+ ### 3. Các kích thước (`size`)
139
+
140
+ Hỗ trợ 3 kích cỡ: `sm`, `md` (*mặc định*), `lg`:
141
+
142
+ ```tsx
143
+ <Tabs size="sm" defaultActiveKey="1">
144
+ <TabList>
145
+ <Tab value="1" label="Small Tab" />
146
+ <Tab value="2" label="Tab 2" />
147
+ </TabList>
148
+ <TabPanels>
149
+ <TabPanel value="1">Nội dung kích thước nhỏ</TabPanel>
150
+ <TabPanel value="2">Nội dung 2</TabPanel>
151
+ </TabPanels>
152
+ </Tabs>
153
+
154
+ <Tabs size="md" defaultActiveKey="1">
155
+ <TabList>
156
+ <Tab value="1" label="Medium Tab (Mặc định)" />
157
+ <Tab value="2" label="Tab 2" />
158
+ </TabList>
159
+ <TabPanels>
160
+ <TabPanel value="1">Nội dung kích thước vừa</TabPanel>
161
+ <TabPanel value="2">Nội dung 2</TabPanel>
162
+ </TabPanels>
163
+ </Tabs>
164
+
165
+ <Tabs size="lg" defaultActiveKey="1">
166
+ <TabList>
167
+ <Tab value="1" label="Large Tab" />
168
+ <Tab value="2" label="Tab 2" />
169
+ </TabList>
170
+ <TabPanels>
171
+ <TabPanel value="1">Nội dung kích thước lớn</TabPanel>
172
+ <TabPanel value="2">Nội dung 2</TabPanel>
173
+ </TabPanels>
174
+ </Tabs>
175
+ ```
176
+
177
+ ---
178
+
179
+ ### 4. Các biến thể giao diện (`variant`)
180
+
181
+ ```tsx
182
+ // 1. Line (Mặc định): Thanh gạch dưới/bên trượt
183
+ <Tabs variant="line" defaultActiveKey="1">...</Tabs>
184
+
185
+ // 2. Solid: Khối pill nền nổi bật
186
+ <Tabs variant="solid" defaultActiveKey="1">...</Tabs>
187
+
188
+ // 3. Bordered: Bao viền khung
189
+ <Tabs variant="bordered" defaultActiveKey="1">...</Tabs>
190
+
191
+ // 4. Flat: Khối pill nền mềm nhạt
192
+ <Tabs variant="flat" defaultActiveKey="1">...</Tabs>
193
+
194
+ // 5. Other: Tự do tùy biến class hoàn toàn
195
+ <Tabs variant="other" defaultActiveKey="1">...</Tabs>
196
+ ```
197
+
198
+ ---
199
+
200
+ ### 5. Các chủ đề màu sắc (`color`)
201
+
202
+ Cung cấp 7 màu sắc theo chuẩn Design System:
203
+
204
+ ```tsx
205
+ <Tabs color="primary" variant="solid" defaultActiveKey="1">...</Tabs>
206
+ <Tabs color="secondary" variant="solid" defaultActiveKey="1">...</Tabs>
207
+ <Tabs color="neutral" variant="solid" defaultActiveKey="1">...</Tabs>
208
+ <Tabs color="error" variant="solid" defaultActiveKey="1">...</Tabs>
209
+ <Tabs color="success" variant="solid" defaultActiveKey="1">...</Tabs>
210
+ <Tabs color="warning" variant="solid" defaultActiveKey="1">...</Tabs>
211
+ <Tabs color="info" variant="solid" defaultActiveKey="1">...</Tabs>
212
+ ```
213
+
214
+ ---
215
+
216
+ ### 6. Hướng và Vị trí (`orientation` & `placement`)
217
+
218
+ ```tsx
219
+ // Dọc bên trái (Vertical Left)
220
+ <Tabs orientation="vertical" placement="left" defaultActiveKey="1">
221
+ <TabList>
222
+ <Tab value="1" label="Menu 1" />
223
+ <Tab value="2" label="Menu 2" />
224
+ </TabList>
225
+ <TabPanels>
226
+ <TabPanel value="1">Nội dung menu 1</TabPanel>
227
+ <TabPanel value="2">Nội dung menu 2</TabPanel>
228
+ </TabPanels>
229
+ </Tabs>
230
+
231
+ // Dọc bên phải (Vertical Right)
232
+ <Tabs orientation="vertical" placement="right" defaultActiveKey="1">...</Tabs>
233
+
234
+ // Ngang phía dưới (Horizontal Bottom)
235
+ <Tabs orientation="horizontal" placement="bottom" defaultActiveKey="1">...</Tabs>
236
+ ```
237
+
238
+ ---
239
+
240
+ ### 7. Icon, Badge & Tab có thể đóng (`closable` & `onClose`)
241
+
242
+ ```tsx
243
+ import { Tabs, TabList, Tab, TabPanels, TabPanel } from "@owa/ui";
244
+ import { HomeIcon, UserIcon, SettingsIcon } from "@/components/icons";
245
+
246
+ export function RichTabsExample() {
247
+ const handleClose = (key: string | number) => {
248
+ console.log("Đóng tab:", key);
249
+ };
250
+
251
+ return (
252
+ <Tabs defaultActiveKey="tab-1" onClose={handleClose}>
253
+ <TabList>
254
+ {/* Tab có icon đầu */}
255
+ <Tab value="tab-1" startIcon={<HomeIcon />} label="Trang chủ" />
256
+
257
+ {/* Tab có badge số lượng */}
258
+ <Tab value="tab-2" startIcon={<UserIcon />} badge={5} label="Thông báo" />
259
+
260
+ {/* Tab có thể đóng (closable) & vô hiệu hóa */}
261
+ <Tab value="tab-3" startIcon={<SettingsIcon />} label="Tạm thời" closable />
262
+ <Tab value="tab-4" label="Bị khóa" disabled />
263
+ </TabList>
264
+
265
+ <TabPanels>
266
+ <TabPanel value="tab-1">Nội dung trang chủ</TabPanel>
267
+ <TabPanel value="tab-2">Nội dung thông báo</TabPanel>
268
+ <TabPanel value="tab-3">Nội dung tab tạm thời</TabPanel>
269
+ <TabPanel value="tab-4">Nội dung bị khóa</TabPanel>
270
+ </TabPanels>
271
+ </Tabs>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ### 8. Cuộn ngang khi tràn viền (Scrollable Overflow) & Canh giữa (`centered`)
279
+
280
+ Khi danh sách tab có độ dài lớn vượt quá container, `TabList` sẽ tự động hiển thị 2 nút chevron trái/phải để người dùng cuộn mượt mà:
281
+
282
+ ```tsx
283
+ // Canh giữa các tab trong container
284
+ <Tabs defaultActiveKey="1">
285
+ <TabList centered>
286
+ <Tab value="1" label="Tab 1" />
287
+ <Tab value="2" label="Tab 2" />
288
+ </TabList>
289
+ <TabPanels>...</TabPanels>
290
+ </Tabs>
291
+
292
+ // Tabs fullWidth tràn đều 100%
293
+ <Tabs fullWidth defaultActiveKey="1">
294
+ <TabList>
295
+ <Tab value="1" label="Tab 1" />
296
+ <Tab value="2" label="Tab 2" />
297
+ </TabList>
298
+ <TabPanels>...</TabPanels>
299
+ </Tabs>
300
+ ```
301
+
302
+ ---
303
+
304
+ ### 9. Nội dung mở rộng (`extra`) & Tối ưu DOM (`destroyInactiveTabPane`)
305
+
306
+ ```tsx
307
+ <Tabs defaultActiveKey="1" destroyInactiveTabPane>
308
+ <TabList
309
+ extra={
310
+ <button className="px-3 py-1.5 text-xs bg-primary-50 text-primary-700 rounded-md font-medium hover:bg-primary-100">
311
+ + Thêm mới
312
+ </button>
313
+ }
314
+ >
315
+ <Tab value="1" label="Tab 1" />
316
+ <Tab value="2" label="Tab 2" />
317
+ </TabList>
318
+ <TabPanels>
319
+ {/* Khi tab không active, DOM của TabPanel sẽ được unmount hoàn toàn để tiết kiệm bộ nhớ */}
320
+ <TabPanel value="1">Nội dung 1</TabPanel>
321
+ <TabPanel value="2">Nội dung 2</TabPanel>
322
+ </TabPanels>
323
+ </Tabs>
324
+ ```
325
+
326
+ ---
327
+
328
+ ## 🛠️ API Reference
329
+
330
+ ### 1. `TabsProps`
331
+
332
+ Kế thừa các thuộc tính HTML `HTMLAttributes<HTMLDivElement>` ngoại trừ `onChange`:
333
+
334
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
335
+ | :--- | :--- | :--- | :--- |
336
+ | `activeKey` | `string \| number` | `undefined` | Khóa của tab đang active (Controlled mode). |
337
+ | `defaultActiveKey` | `string \| number` | `undefined` | Khóa của tab active mặc định ban đầu (Uncontrolled mode). |
338
+ | `onChange` | `(key: string \| number) => void` | `undefined` | Callback kích hoạt khi thay đổi tab được chọn. |
339
+ | `onClose` | `(key: string \| number) => void` | `undefined` | Callback kích hoạt khi người dùng bấm nút đóng tab (`closable`). |
340
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Kích thước giao diện chung cho các tab con. |
341
+ | `variant` | `"line" \| "solid" \| "bordered" \| "flat" \| "other"` | `"line"` | Biến thể kiểu dáng thanh tab & indicator. |
342
+ | `color` | `"primary" \| "secondary" \| "neutral" \| "error" \| "success" \| "warning" \| "info"` | `"primary"` | Chủ đề màu sắc theo Design System. |
343
+ | `radius` | `"none" \| "sm" \| "md" \| "lg" \| "xl" \| "full"` | `"md"` | Độ bo góc của tab và sliding indicator. |
344
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Chiều hiển thị danh sách tabs (ngang hoặc dọc). |
345
+ | `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Vị trí đặt `TabList` so với `TabPanels`. |
346
+ | `fullWidth` | `boolean` | `false` | Tự động giãn đều chiều rộng các tab vừa khớp 100% container. |
347
+ | `disabled` | `boolean` | `false` | Vô hiệu hóa toàn bộ tabs trong nhóm. |
348
+ | `destroyInactiveTabPane` | `boolean` | `false` | Tự động unmount nội dung khỏi DOM khi tab không active. |
349
+ | `children` | `ReactNode` | `undefined` | Các component con (`<TabList>`, `<TabPanels>`). |
350
+ | `className` | `string` | `""` | Class CSS tùy biến cho container bao ngoài. |
351
+ | `ref` | `Ref<HTMLDivElement>` | `undefined` | Ref chuyển tiếp đến container Tabs. |
352
+
353
+ ---
354
+
355
+ ### 2. `TabListProps`
356
+
357
+ Kế thừa `HTMLAttributes<HTMLDivElement>`:
358
+
359
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
360
+ | :--- | :--- | :--- | :--- |
361
+ | `children` | `ReactNode` | `undefined` | Danh sách các phần tử `<Tab>` con. |
362
+ | `extra` | `ReactNode` | `undefined` | Nội dung hoặc nút hành động phụ đặt ở góc thanh tab list. |
363
+ | `centered` | `boolean` | `false` | Canh giữa danh sách tabs trong container (`orientation="horizontal"`). |
364
+ | `className` | `string` | `""` | Class CSS tùy biến bổ sung cho TabList. |
365
+ | `ref` | `Ref<HTMLDivElement>` | `undefined` | Ref chuyển tiếp đến thẻ tablist. |
366
+
367
+ ---
368
+
369
+ ### 3. `TabProps`
370
+
371
+ Kế thừa `ButtonHTMLAttributes<HTMLButtonElement>` ngoại trừ `value`:
372
+
373
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
374
+ | :--- | :--- | :--- | :--- |
375
+ | `value` | `string \| number` | *(Bắt buộc)* | Khóa định danh duy nhất của tab để liên kết với `TabPanel`. |
376
+ | `label` | `ReactNode` | `undefined` | Tiêu đề hiển thị của tab. |
377
+ | `startIcon` | `ReactNode` | `undefined` | Icon hiển thị trước tiêu đề. |
378
+ | `endIcon` | `ReactNode` | `undefined` | Icon hiển thị sau tiêu đề. |
379
+ | `badge` | `ReactNode` | `undefined` | Huy hiệu hoặc số lượng hiển thị trên tab. |
380
+ | `disabled` | `boolean` | `false` | Vô hiệu hóa riêng tab này. |
381
+ | `closable` | `boolean` | `false` | Cho phép hiển thị nút đóng tab (hỗ trợ phím Delete/Backspace). |
382
+ | `onClose` | `(e: MouseEvent) => void` | `undefined` | Callback khi bấm nút đóng trên tab này. |
383
+ | `children` | `ReactNode` | `undefined` | Nội dung tùy biến thay thế cho prop `label`. |
384
+ | `className` | `string` | `""` | Class CSS tùy biến cho nút tab. |
385
+ | `ref` | `Ref<HTMLButtonElement>` | `undefined` | Ref chuyển tiếp đến thẻ `<button>` của tab. |
386
+
387
+ ---
388
+
389
+ ### 4. `TabPanelsProps` & `TabPanelProps`
390
+
391
+ #### `TabPanelsProps`
392
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
393
+ | :--- | :--- | :--- | :--- |
394
+ | `children` | `ReactNode` | `undefined` | Danh sách các component `<TabPanel>` con. |
395
+ | `className` | `string` | `""` | Class CSS tùy biến cho khung chứa TabPanels. |
396
+ | `ref` | `Ref<HTMLDivElement>` | `undefined` | Ref chuyển tiếp đến container TabPanels. |
397
+
398
+ #### `TabPanelProps`
399
+ | Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
400
+ | :--- | :--- | :--- | :--- |
401
+ | `value` | `string \| number` | *(Bắt buộc)* | Khóa liên kết trực tiếp với `value` của `<Tab>` tương ứng. |
402
+ | `destroyInactiveTabPane` | `boolean` | `undefined` | Ghi đè cấu hình unmount khỏi DOM khi tab không active riêng cho panel này. |
403
+ | `children` | `ReactNode` | `undefined` | Nội dung hiển thị khi tab tương ứng đang active. |
404
+ | `className` | `string` | `""` | Class CSS tùy biến cho panel nội dung. |
405
+ | `ref` | `Ref<HTMLDivElement>` | `undefined` | Ref chuyển tiếp đến thẻ tabpanel. |
406
+
407
+ ---
408
+
409
+ ### 5. Hook `useTabIndicator`
410
+
411
+ ```tsx
412
+ import { useTabIndicator } from "@owa/ui";
413
+
414
+ const {
415
+ listRef,
416
+ indicatorStyle,
417
+ canScrollLeft,
418
+ canScrollRight,
419
+ checkScroll,
420
+ updateIndicator,
421
+ handleScrollLeft,
422
+ handleScrollRight,
423
+ handleKeyDown,
424
+ } = useTabIndicator({
425
+ activeKey,
426
+ orientation,
427
+ placement,
428
+ variant,
429
+ color,
430
+ radius,
431
+ });
432
+ ```
@@ -0,0 +1,193 @@
1
+ # 📝 TextArea Component (`@owa/ui`)
2
+
3
+ Component **TextArea** đa năng, hỗ trợ **tự động co giãn chiều cao (AutoResize)** bằng thư viện `react-textarea-autosize`, tích hợp **Safe Config Fallback**, **quản lý Ref tối ưu** qua `@floating-ui/react`, **bộ đếm ký tự (Character Counter)** và tuân thủ đầy đủ tiêu chuẩn **WAI-ARIA Accessibility**.
4
+
5
+ ---
6
+
7
+ ## 🌟 Điểm nổi bật
8
+
9
+ - **Tích hợp `react-textarea-autosize`**: Tự động tăng giảm chiều cao mượt mà theo nội dung nhập mà không làm giật khung hình (`layout shift`). Hỗ trợ cấu hình `minRows`, `maxRows`, `cacheMeasurements` và callback `onHeightChange`.
10
+ - **Tùy chọn chuyển đổi linh hoạt**: Dễ dàng tắt chế độ co giãn tự động bằng `autoResize={false}` để chuyển về thẻ `<textarea>` truyền thống.
11
+ - **5 Kích thước tiêu chuẩn (`size`)**: `xs`, `sm`, `md` (*mặc định*), `lg`, `xl` với font chữ, padding, kích thước nhãn và thông báo phụ được đồng bộ tỉ lệ.
12
+ - **3 Biến thể giao diện (`variant`)**:
13
+ - `outline` *(mặc định)*: Viền nét rõ ràng quanh khung soạn thảo, hover/focus đổi màu viền chủ đề.
14
+ - `filled`: Nền pastel nhạt (`bg-{color}-50/60`), có viền bao quanh.
15
+ - `ghost`: Nền trong suốt, chỉ nổi bật khi hover hoặc focus.
16
+ - `other`: Bỏ qua các class màu mặc định, tự do áp dụng custom style qua `textareaWrapperClassName`.
17
+ - **7 Chủ đề màu sắc (`color`)**: `primary`, `secondary`, `error`, `success`, `warning`, `info`, `neutral`.
18
+ - **6 Mức độ bo góc (`radius`)**: `none`, `sm`, `md` (*mặc định*), `lg`, `xl`, `full`.
19
+ - **3 Vị trí đặt nhãn (`labelPlacement`)**:
20
+ - `top` *(mặc định)*: Nhãn nằm phía trên ô textarea.
21
+ - `left`: Nhãn nằm ngang bên trái ô textarea (tự động căn chỉnh theo dạng flex-row).
22
+ - `floating`: Nhãn nổi bật vắt ngang viền trên của khung textarea.
23
+ - **Trạng thái Loading & Xoay Spinner (`isLoading` & `showSpinner`)**:
24
+ - `isLoading={true}`: Tự động vô hiệu hóa ô nhập liệu (`disabled`), kích hoạt `aria-busy="true"` và `aria-disabled="true"`.
25
+ - `showSpinner`: Mặc định là `false`. Đặt `showSpinner={true}` khi muốn hiển thị icon xoay vòng ở góc phải trên.
26
+ - **Nút xóa nhanh (`isClearable` & `onClear`)**: Hiển thị nút bấm xóa sạch nội dung ở góc phải trên khi ô có văn bản.
27
+ - **Bộ đếm số lượng ký tự (`showCount` & `maxLength`)**:
28
+ - Hiển thị số lượng ký tự trực tiếp ở góc dưới bên phải (ví dụ: `45/500`).
29
+ - Tự động chặn nhập vượt quá số ký tự cho phép cả khi gõ và khi dán (paste).
30
+ - **Quản lý Ref nâng cao**: Sử dụng `useMergeRefs` từ `@floating-ui/react` giúp gộp và chuyển tiếp `ref` mượt mà trong React 19.
31
+ - **Safe Config Fallback**: Tích hợp hàm `getSafeConfig` đảm bảo component hoạt động an toàn, không bị crash kể cả khi truyền prop không hợp lệ.
32
+
33
+ ---
34
+
35
+ ## 🚀 Cài đặt & Import
36
+
37
+ ```tsx
38
+ import { TextArea } from "@owa/ui";
39
+ import type {
40
+ TextAreaProps,
41
+ TextAreaConfig,
42
+ TextAreaSize,
43
+ TextAreaVariant,
44
+ TextAreaColor,
45
+ TextAreaRadius,
46
+ TextAreaLabelPlacement,
47
+ TextAreaResize,
48
+ } from "@owa/ui";
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 📖 Hướng dẫn sử dụng
54
+
55
+ ### 1. Cách sử dụng cơ bản
56
+
57
+ ```tsx
58
+ import { TextArea } from "@owa/ui";
59
+
60
+ export function BasicTextAreaExample() {
61
+ return (
62
+ <div className="flex flex-col gap-4 max-w-md">
63
+ <TextArea
64
+ label="Mô tả chi tiết"
65
+ placeholder="Nhập mô tả sản phẩm của bạn..."
66
+ />
67
+ </div>
68
+ );
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ### 2. Tự động co giãn chiều cao (AutoResize)
75
+
76
+ Mặc định `autoResize={true}` với số dòng tối thiểu `minRows={3}`:
77
+
78
+ ```tsx
79
+ // Co giãn tự do từ 3 dòng trở lên
80
+ <TextArea
81
+ label="Phản hồi ý kiến"
82
+ autoResize={true}
83
+ minRows={3}
84
+ maxRows={8}
85
+ onHeightChange={(height) => console.log("Chiều cao hiện tại:", height)}
86
+ />
87
+
88
+ // Tắt autoResize để dùng chiều cao cố định
89
+ <TextArea
90
+ label="Ghi chú cố định"
91
+ autoResize={false}
92
+ rows={4}
93
+ resize="vertical"
94
+ />
95
+ ```
96
+
97
+ ---
98
+
99
+ ### 3. Vị trí đặt nhãn (`labelPlacement`)
100
+
101
+ ```tsx
102
+ // 1. Top (Mặc định)
103
+ <TextArea label="Nhãn phía trên" labelPlacement="top" placeholder="Nhập văn bản..." />
104
+
105
+ // 2. Left (Ngang bên trái)
106
+ <TextArea label="Nhãn bên trái" labelPlacement="left" placeholder="Nhập văn bản..." />
107
+
108
+ // 3. Floating (Nổi trên viền)
109
+ <TextArea label="Nhãn nổi viền" labelPlacement="floating" placeholder="Nhập văn bản..." />
110
+ ```
111
+
112
+ ---
113
+
114
+ ### 4. Giới hạn ký tự & Bộ đếm (`showCount` & `maxLength`)
115
+
116
+ ```tsx
117
+ <TextArea
118
+ label="Đánh giá sản phẩm"
119
+ maxLength={200}
120
+ showCount={true}
121
+ placeholder="Tối đa 200 ký tự (tự động chặn khi đạt giới hạn)..."
122
+ />
123
+ ```
124
+
125
+ ---
126
+
127
+ ### 5. Xóa nhanh & Trạng thái Loading
128
+
129
+ ```tsx
130
+ <TextArea
131
+ label="Tìm kiếm nội dung"
132
+ isClearable={true}
133
+ onClear={() => console.log("Đã xóa sạch nội dung")}
134
+ defaultValue="Nội dung ban đầu"
135
+ />
136
+
137
+ <TextArea
138
+ label="Đang đồng bộ dữ liệu"
139
+ isLoading={true}
140
+ showSpinner={true}
141
+ defaultValue="Vui lòng đợi..."
142
+ />
143
+ ```
144
+
145
+ ---
146
+
147
+ ### 6. Trạng thái Báo lỗi & Hướng dẫn (`errorMessage` & `helperText`)
148
+
149
+ ```tsx
150
+ <TextArea
151
+ label="Địa chỉ giao hàng"
152
+ isRequired={true}
153
+ errorMessage="Địa chỉ không được để trống!"
154
+ isInvalid={true}
155
+ />
156
+
157
+ <TextArea
158
+ label="Tiểu sử bản thân"
159
+ helperText="Hãy viết ngắn gọn 1-2 câu về kinh nghiệm của bạn."
160
+ />
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 🛠 Bảng thông số Props (`TextAreaProps`)
166
+
167
+ | Tên Prop | Kiểu dữ liệu | Giá trị mặc định | Mô tả |
168
+ | :--- | :--- | :---: | :--- |
169
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Kích cỡ giao diện (font chữ, padding, kích thước nhãn). |
170
+ | `variant` | `'outline' \| 'filled' \| 'ghost' \| 'other'` | `'outline'` | Biến thể hiển thị giao diện. |
171
+ | `color` | `'primary' \| 'secondary' \| 'error' \| 'success' \| 'warning' \| 'info' \| 'neutral'` | `'primary'` | Chủ đề màu sắc theo Design System. |
172
+ | `radius` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Độ bo góc của khung viền textarea. |
173
+ | `label` | `ReactNode` | — | Nhãn tiêu đề hiển thị cho ô nhập liệu. |
174
+ | `labelPlacement` | `'top' \| 'left' \| 'floating'` | `'top'` | Vị trí hiển thị của nhãn. |
175
+ | `config` | `TextAreaConfig` | — | Cấu hình tập trung các cờ trạng thái / tính năng (`isRequired`, `isInvalid`, `isLoading`, `showSpinner`, `isClearable`, `autoResize`, `showCount`, `isFullWidth`). |
176
+ | `isRequired` | `boolean` | `false` | Hiển thị dấu sao đỏ `*` và đánh dấu `aria-required="true"`. |
177
+ | `helperText` | `ReactNode` | — | Đoạn văn bản hướng dẫn/trợ giúp bên dưới ô. |
178
+ | `errorMessage` | `ReactNode` | — | Thông báo lỗi khi nhập sai (tự kích hoạt trạng thái báo lỗi). |
179
+ | `isInvalid` | `boolean` | `false` | Bật trạng thái viền đỏ báo lỗi và `aria-invalid="true"`. |
180
+ | `isLoading` | `boolean` | `false` | Khóa tương tác, bật `aria-busy="true"` và `aria-disabled="true"`. |
181
+ | `showSpinner` | `boolean` | `false` | Hiển thị biểu tượng xoay spinner khi `isLoading={true}`. |
182
+ | `isClearable` | `boolean` | `false` | Hiển thị nút xóa nhanh nội dung khi có văn bản. |
183
+ | `onClear` | `() => void` | — | Callback được gọi khi bấm nút xóa nhanh. |
184
+ | `autoResize` | `boolean` | `true` | Tự động co giãn chiều cao theo nội dung nhập. |
185
+ | `minRows` | `number` | `3` | Số dòng hiển thị tối thiểu khi `autoResize` bật. |
186
+ | `maxRows` | `number` | — | Số dòng hiển thị tối đa trước khi xuất hiện thanh cuộn. |
187
+ | `onHeightChange` | `(height: number, meta: { rowHeight: number }) => void` | — | Callback khi chiều cao thay đổi do autoResize. |
188
+ | `cacheMeasurements` | `boolean` | — | Bật bộ nhớ đệm kết quả đo chiều cao để tối ưu render. |
189
+ | `showCount` | `boolean` | `false` | Hiển thị bộ đếm số lượng ký tự ở góc phải dưới. |
190
+ | `maxLength` | `number` | — | Số ký tự tối đa cho phép nhập (chặn tự động khi đạt giới hạn). |
191
+ | `resize` | `'none' \| 'vertical' \| 'horizontal' \| 'both'` | `'none'` | Tùy chọn kéo giãn khung thủ công. |
192
+ | `isFullWidth` | `boolean` | `false` | Mở rộng chiếm toàn bộ 100% chiều ngang container cha. |
193
+ | `ref` | `Ref<HTMLTextAreaElement>` | — | Ref chuyển tiếp đến thẻ `<textarea>` HTML bên dưới. |