@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
package/README.md CHANGED
@@ -1,181 +1,203 @@
1
- # @openway/ui
2
-
3
- Bộ thư viện UI components dùng chung, tối ưu hóa cho React 19, Next.js 16+ và Tailwind CSS (hỗ trợ đầy đủ Tailwind CSS v4 & v3).
4
-
5
- Thư viện được thiết kế theo triết lý **Theme-agnostic (Tùy biến bảng màu linh hoạt)**: Thư viện chỉ cung cấp bộ khung layout và logic WAI-ARIA, còn bảng màu (`primary`, `secondary`, `neutral`...) hoàn toàn do dự án của bạn quyết định thông qua file CSS toàn cục (`globals.css`).
6
-
7
- ---
8
-
9
- ## 1. Cài đặt
10
-
11
- ```bash
12
- pnpm add @openway/ui
13
- # hoặc
14
- npm install @openway/ui
15
- # hoặc
16
- yarn add @openway/ui
17
- ```
18
-
19
- ---
20
-
21
- ## 2. Cấu hình bảng màu trong dự án của bạn
22
-
23
- Bạn hoàn toàn tự do lựa chọn bất kỳ mã màu thương hiệu nào bạn thích (xanh lam, tím, cam, đen trắng...).
24
-
25
- ### Cách cấu hình với Tailwind CSS v4 (Khuyên dùng)
26
-
27
- Trong file `app/globals.css` (hoặc file CSS chính của bạn):
28
-
29
- ```css
30
- @import "tailwindcss";
31
-
32
- /* 1. Chỉ định Tailwind quét các components của @openway/ui */
33
- @source "../node_modules/@openway/ui";
34
-
35
- /* 2. Tự cung cấp bảng màu bạn thích */
36
- @theme {
37
- /* Màu chủ đạo (Primary) */
38
- --color-primary-50: #f0f7ff;
39
- --color-primary-100: #e0effe;
40
- --color-primary-200: #bae0fd;
41
- --color-primary-300: #7cc5fb;
42
- --color-primary-400: #36a4f6;
43
- --color-primary-500: #0284c7; /* Màu chính của nút, checkbox, active tab... */
44
- --color-primary-600: #0369a1; /* Màu khi hover nút, focus... */
45
- --color-primary-700: #075985;
46
- --color-primary-800: #0c4a6e;
47
- --color-primary-900: #0a3d5b;
48
- --color-primary-950: #06273c;
49
-
50
- /* Màu phụ (Secondary) */
51
- --color-secondary-50: #faf5ff;
52
- --color-secondary-100: #f3e8ff;
53
- --color-secondary-500: #a855f7;
54
- --color-secondary-600: #9333ea;
55
- --color-secondary-700: #7e22ce;
56
-
57
- /* Màu trung tính cho nền, viền, chữ (Neutral) */
58
- --color-neutral-white: #ffffff;
59
- --color-neutral-50: #f8fafc;
60
- --color-neutral-100: #f1f5f9;
61
- --color-neutral-200: #e2e8f0;
62
- --color-neutral-300: #cbd5e1;
63
- --color-neutral-400: #94a3b8;
64
- --color-neutral-500: #64748b;
65
- --color-neutral-600: #475569;
66
- --color-neutral-700: #334155;
67
- --color-neutral-800: #1e293b;
68
- --color-neutral-900: #0f172a;
69
- --color-neutral-950: #020617;
70
- --color-neutral-black: #000000;
71
-
72
- /* Màu thông báo trạng thái (Error, Warning, Success, Info) */
73
- --color-error-500: #ef4444;
74
- --color-error-600: #dc2626;
75
-
76
- --color-warning-500: #f59e0b;
77
- --color-warning-600: #d97706;
78
-
79
- --color-success-500: #10b981;
80
- --color-success-600: #059669;
81
-
82
- --color-info-500: #3b82f6;
83
- --color-info-600: #2563eb;
84
- }
85
- ```
86
-
87
- ---
88
-
89
- ### Cách cấu hình với Tailwind CSS v3
90
-
91
- Nếu dự án của bạn đang dùng `tailwind.config.js`:
92
-
93
- ```js
94
- // tailwind.config.js
95
- module.exports = {
96
- content: [
97
- "./app/**/*.{js,ts,jsx,tsx}",
98
- "./components/**/*.{js,ts,jsx,tsx}",
99
- // Thêm dòng này để quét class của @openway/ui:
100
- "./node_modules/@openway/ui/**/*.{js,cjs,ts,tsx}",
101
- ],
102
- theme: {
103
- extend: {
104
- colors: {
105
- primary: {
106
- 50: "#f0f7ff",
107
- 100: "#e0effe",
108
- 500: "#0284c7",
109
- 600: "#0369a1",
110
- 700: "#075985",
111
- },
112
- neutral: {
113
- white: "#ffffff",
114
- 50: "#f8fafc",
115
- 100: "#f1f5f9",
116
- 200: "#e2e8f0",
117
- 800: "#1e293b",
118
- 900: "#0f172a",
119
- },
120
- // Tương tự cho secondary, error, warning, success...
121
- },
122
- },
123
- },
124
- };
125
- ```
126
-
127
- ---
128
-
129
- ## 3. Cách sử dụng
130
-
131
- Trong `app/layout.tsx`:
132
- ```tsx
133
- import "./globals.css"; // Chỉ cần import file CSS của dự án bạn
134
-
135
- export default function RootLayout({ children }: { children: React.ReactNode }) {
136
- return (
137
- <html lang="vi">
138
- <body>{children}</body>
139
- </html>
140
- );
141
- }
142
- ```
143
-
144
- Trong bất kỳ Component / Trang nào:
145
- ```tsx
146
- import { Button, Input, DatePicker } from "@openway/ui";
147
-
148
- export default function Page() {
149
- return (
150
- <div className="p-6 space-y-4">
151
- <Input label="Họ và tên" placeholder="Nhập họ và tên..." />
152
-
153
- <DatePicker label="Ngày sinh" placeholder="Chọn ngày sinh" />
154
-
155
- <Button color="primary" variant="filled">
156
- Lưu thông tin
157
- </Button>
158
- </div>
159
- );
160
- }
161
- ```
162
-
163
- ---
164
-
165
- ## 4. Danh sách các Token màu sắc được hỗ trợ
166
-
167
- | Tên Token | tả | Các sắc độ khuyên dùng |
168
- | :--- | :--- | :--- |
169
- | `primary` | Màu nhận diện thương hiệu chủ đạo (Nút, Tab đang chọn, Viền focus, Checkbox, Radio...) | `50` $\rightarrow$ `950` (Tối thiểu cần `500`, `600`) |
170
- | `secondary` | Màu phụ, nút thứ cấp | `50` $\rightarrow$ `950` (Tối thiểu cần `500`, `600`) |
171
- | `neutral` | Màu khung viền, nền xám, nền input, màu chữ tiêu đề và mô tả | `white`, `50` $\rightarrow$ `950`, `black` |
172
- | `error` | Trạng thái lỗi, thông báo lỗi form, nút xóa nguy hiểm | `50`, `100`, `500`, `600` |
173
- | `warning` | Cảnh báo, trạng thái chờ | `50`, `100`, `500`, `600` |
174
- | `success` | Thành công, hoàn thành | `50`, `100`, `500`, `600` |
175
- | `info` | Thông tin hướng dẫn | `50`, `100`, `500`, `600` |
176
-
177
- ---
178
-
179
- ## 5. Giấy phép
180
-
181
- Phát hành dưới giấy phép [MIT](LICENSE).
1
+ # @openway/ui
2
+
3
+ Bộ thư viện UI components dùng chung, tối ưu hóa cho React 19, Next.js 16+ và Tailwind CSS (hỗ trợ đầy đủ Tailwind CSS v4 & v3).
4
+
5
+ Thư viện được thiết kế theo triết lý **Theme-agnostic (Tùy biến bảng màu linh hoạt)**: Thư viện chỉ cung cấp bộ khung layout và logic WAI-ARIA, còn bảng màu (`primary`, `secondary`, `neutral`...) hoàn toàn do dự án của bạn quyết định thông qua file CSS toàn cục (`globals.css`).
6
+
7
+ ---
8
+
9
+ ## 1. Cài đặt
10
+
11
+ ```bash
12
+ pnpm add @openway/ui
13
+ # hoặc
14
+ npm install @openway/ui
15
+ # hoặc
16
+ yarn add @openway/ui
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 2. Cấu hình bảng màu trong dự án của bạn
22
+
23
+ Bạn hoàn toàn tự do lựa chọn bất kỳ mã màu thương hiệu nào bạn thích (xanh lam, tím, cam, đen trắng...).
24
+
25
+ ### Cách cấu hình với Tailwind CSS v4 (Khuyên dùng)
26
+
27
+ Trong file `app/globals.css` (hoặc file CSS chính của bạn):
28
+
29
+ ```css
30
+ @import "tailwindcss";
31
+
32
+ /* 1. Chỉ định Tailwind quét các components của @openway/ui */
33
+ @source "../node_modules/@openway/ui";
34
+
35
+ /* 2. Tự cung cấp bảng màu bạn thích */
36
+ @theme {
37
+ /* Màu chủ đạo (Primary) */
38
+ --color-primary-50: #f0f7ff;
39
+ --color-primary-100: #e0effe;
40
+ --color-primary-200: #bae0fd;
41
+ --color-primary-300: #7cc5fb;
42
+ --color-primary-400: #36a4f6;
43
+ --color-primary-500: #0284c7; /* Màu chính của nút, checkbox, active tab... */
44
+ --color-primary-600: #0369a1; /* Màu khi hover nút, focus... */
45
+ --color-primary-700: #075985;
46
+ --color-primary-800: #0c4a6e;
47
+ --color-primary-900: #0a3d5b;
48
+ --color-primary-950: #06273c;
49
+
50
+ /* Màu phụ (Secondary) */
51
+ --color-secondary-50: #faf5ff;
52
+ --color-secondary-100: #f3e8ff;
53
+ --color-secondary-500: #a855f7;
54
+ --color-secondary-600: #9333ea;
55
+ --color-secondary-700: #7e22ce;
56
+
57
+ /* Màu trung tính cho nền, viền, chữ (Neutral) */
58
+ --color-neutral-white: #ffffff;
59
+ --color-neutral-50: #f8fafc;
60
+ --color-neutral-100: #f1f5f9;
61
+ --color-neutral-200: #e2e8f0;
62
+ --color-neutral-300: #cbd5e1;
63
+ --color-neutral-400: #94a3b8;
64
+ --color-neutral-500: #64748b;
65
+ --color-neutral-600: #475569;
66
+ --color-neutral-700: #334155;
67
+ --color-neutral-800: #1e293b;
68
+ --color-neutral-900: #0f172a;
69
+ --color-neutral-950: #020617;
70
+ --color-neutral-black: #000000;
71
+
72
+ /* Màu thông báo trạng thái (Error, Warning, Success, Info) */
73
+ --color-error-500: #ef4444;
74
+ --color-error-600: #dc2626;
75
+
76
+ --color-warning-500: #f59e0b;
77
+ --color-warning-600: #d97706;
78
+
79
+ --color-success-500: #10b981;
80
+ --color-success-600: #059669;
81
+
82
+ --color-info-500: #3b82f6;
83
+ --color-info-600: #2563eb;
84
+ }
85
+ ```
86
+
87
+ ---
88
+
89
+ ### Cách cấu hình với Tailwind CSS v3
90
+
91
+ Nếu dự án của bạn đang dùng `tailwind.config.js`:
92
+
93
+ ```js
94
+ // tailwind.config.js
95
+ module.exports = {
96
+ content: [
97
+ "./app/**/*.{js,ts,jsx,tsx}",
98
+ "./components/**/*.{js,ts,jsx,tsx}",
99
+ // Thêm dòng này để quét class của @openway/ui:
100
+ "./node_modules/@openway/ui/**/*.{js,cjs,ts,tsx}",
101
+ ],
102
+ theme: {
103
+ extend: {
104
+ colors: {
105
+ primary: {
106
+ 50: "#f0f7ff",
107
+ 100: "#e0effe",
108
+ 500: "#0284c7",
109
+ 600: "#0369a1",
110
+ 700: "#075985",
111
+ },
112
+ neutral: {
113
+ white: "#ffffff",
114
+ 50: "#f8fafc",
115
+ 100: "#f1f5f9",
116
+ 200: "#e2e8f0",
117
+ 800: "#1e293b",
118
+ 900: "#0f172a",
119
+ },
120
+ // Tương tự cho secondary, error, warning, success...
121
+ },
122
+ },
123
+ },
124
+ };
125
+ ```
126
+
127
+ ---
128
+
129
+ ## 3. Cách sử dụng
130
+
131
+ Trong `app/layout.tsx`:
132
+ ```tsx
133
+ import "./globals.css"; // Chỉ cần import file CSS của dự án bạn
134
+
135
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
136
+ return (
137
+ <html lang="vi">
138
+ <body>{children}</body>
139
+ </html>
140
+ );
141
+ }
142
+ ```
143
+
144
+ Trong bất kỳ Component / Trang nào:
145
+ ```tsx
146
+ import { Button, Input, DatePicker } from "@openway/ui";
147
+
148
+ export default function Page() {
149
+ return (
150
+ <div className="p-6 space-y-4">
151
+ <Input label="Họ và tên" placeholder="Nhập họ và tên..." />
152
+
153
+ <DatePicker label="Ngày sinh" placeholder="Chọn ngày sinh" />
154
+
155
+ <Button color="primary" variant="filled">
156
+ Lưu thông tin
157
+ </Button>
158
+ </div>
159
+ );
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 4. Tài liệu Chi tiết & Tích hợp Antigravity AI (`docs/`)
166
+
167
+ Toàn bộ tài liệu chi tiết của 32 components các hooks được đóng gói sẵn trong thư mục `node_modules/@openway/ui/docs/`:
168
+
169
+ - **[Tài liệu Trung tâm (Master Guide)](./docs/README.md)**: Danh mục toàn bộ components hướng dẫn chi tiết.
170
+ - **[Antigravity Agent Guide](./docs/AGENTS.md)**: Chỉ dẫn chuyên biệt cho Antigravity AI.
171
+ - **[Antigravity Skill](./docs/SKILL.md)**: Định dạng Skill chuẩn của Antigravity (`openway-ui`).
172
+ - **[Thư mục Components](./docs/components/)**: 32 tài liệu hướng dẫn bảng props chi tiết của từng component.
173
+ - **[Thư mục Hooks](./docs/hooks/)**: Tài liệu `useInfiniteScroll`, `useTableQuery`, `useSelectInfiniteQuery`, `useDebounce`.
174
+
175
+ ---
176
+
177
+ ## 5. Tích hợp TanStack Query v5 (`@openway/ui/query`)
178
+
179
+ Dành cho các tính năng phân trang máy chủ (Server-side Pagination), sắp xếp, lọc và cuộn vô tận:
180
+
181
+ ```tsx
182
+ import { useTableQuery, useSelectInfiniteQuery } from "@openway/ui/query";
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 6. Danh sách các Token màu sắc được hỗ trợ
188
+
189
+ | Tên Token | Mô tả | Các sắc độ khuyên dùng |
190
+ | :--- | :--- | :--- |
191
+ | `primary` | Màu nhận diện thương hiệu chủ đạo (Nút, Tab đang chọn, Viền focus, Checkbox, Radio...) | `50` $\rightarrow$ `950` (Tối thiểu cần `500`, `600`) |
192
+ | `secondary` | Màu phụ, nút thứ cấp | `50` $\rightarrow$ `950` (Tối thiểu cần `500`, `600`) |
193
+ | `neutral` | Màu khung viền, nền xám, nền input, màu chữ tiêu đề và mô tả | `white`, `50` $\rightarrow$ `950`, `black` |
194
+ | `error` | Trạng thái lỗi, thông báo lỗi form, nút xóa nguy hiểm | `50`, `100`, `500`, `600` |
195
+ | `warning` | Cảnh báo, trạng thái chờ | `50`, `100`, `500`, `600` |
196
+ | `success` | Thành công, hoàn thành | `50`, `100`, `500`, `600` |
197
+ | `info` | Thông tin hướng dẫn | `50`, `100`, `500`, `600` |
198
+
199
+ ---
200
+
201
+ ## 7. Giấy phép
202
+
203
+ Phát hành dưới giấy phép [MIT](LICENSE).
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ import{rankItem as me,compareItems as xe}from"@tanstack/match-sorter-utils";function p(e,r,o){return e!=null&&Object.prototype.hasOwnProperty.call(r,e)?r[e]:r[o]}function ze(e,r,o){let t=r.trim();if(!t||!e.length)return e;let c=o?Array.isArray(o)?o:[o]:[],l=[];if(c.length>0)for(let n of c)typeof n=="function"?l.push(i=>{let a=n(i);return a!=null?String(a):""}):l.push(i=>{let a=i,u=a[n];if(u!=null)return String(u);if(a.data&&typeof a.data=="object"){let d=a.data[n];if(d!=null)return String(d)}return""});else l.push(n=>{if(n&&typeof n=="object"){let i=n;return i.label!=null?String(i.label):i.value!=null?String(i.value):""}return n!=null?String(n):""});let s=[];for(let n of e){let i=me(n,t,{accessors:l});i.passed&&s.push({item:n,info:i})}return s.sort((n,i)=>xe(n.info,i.info)),s.map(n=>n.item)}import{jsx as F,jsxs as ye}from"react/jsx-runtime";function P({width:e=16,height:r=16,className:o="",...t}){return ye("svg",{width:e,height:r,className:`animate-[spin_1s_steps(8)_infinite] ${o}`,viewBox:"0 0 25 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",...t,children:[F("path",{d:"M0 0h25v24H0z",fill:"none"}),F("path",{fill:"currentColor",d:"M4.818 6.664h-.001a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.277.541h-.029zm-2.97 7.182h-.001a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.278.541h-.031h.002zM12 3.692a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.277.541zM4.818 21.029h-.001a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.276.541h-.031zM19.182 7.125a2.308 2.308 0 1 1 0-4.615a2.308 2.308 0 0 1 0 4.615M12 24a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.277.541zm10.154-9.231h-.048c-.75 0-1.428-.309-1.913-.807l-.001-.001c-.499-.503-.808-1.196-.808-1.961s.308-1.458.808-1.962a2.66 2.66 0 0 1 1.914-.808h.05h-.003h.048c.75 0 1.427.309 1.913.807l.001.001c.499.503.808 1.196.808 1.961s-.308 1.458-.808 1.962a2.66 2.66 0 0 1-1.915.809h-.049zm-2.971 7.643h-.05a3.1 3.1 0 0 1-2.236-.951l-.001-.001c-.584-.584-.945-1.391-.945-2.283s.361-1.698.945-2.283a3.1 3.1 0 0 1 2.234-.945h.054h-.003h.042c.877 0 1.67.362 2.237.944l.001.001c.588.582.952 1.39.952 2.283s-.364 1.7-.952 2.282a3.1 3.1 0 0 1-2.24.953h-.04z"})]})}import{jsx as U}from"react/jsx-runtime";function z({width:e=14,height:r=14,className:o="",...t}){return U("svg",{width:e,height:r,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",className:o,...t,children:U("path",{d:"M18 6 6 18M6 6l12 12"})})}import{jsx as V,jsxs as he}from"react/jsx-runtime";function R({width:e=20,height:r=20,className:o="",...t}){return he("svg",{width:e,height:r,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:o,...t,children:[V("circle",{cx:"12",cy:"12",r:"10"}),V("path",{d:"M12 16v-4"}),V("path",{d:"M12 8h.01"})]})}import{jsx as K,jsxs as we}from"react/jsx-runtime";function E({width:e=20,height:r=20,className:o="",...t}){return we("svg",{width:e,height:r,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:o,...t,children:[K("circle",{cx:"12",cy:"12",r:"10"}),K("path",{d:"m9 12 2 2 4-4"})]})}import{jsx as j,jsxs as Te}from"react/jsx-runtime";function G({width:e=20,height:r=20,className:o="",...t}){return Te("svg",{width:e,height:r,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:o,...t,children:[j("path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"}),j("path",{d:"M12 9v4"}),j("path",{d:"M12 17h.01"})]})}import{jsx as L,jsxs as ve}from"react/jsx-runtime";function M({width:e=20,height:r=20,className:o="",...t}){return ve("svg",{width:e,height:r,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:o,...t,children:[L("circle",{cx:"12",cy:"12",r:"10"}),L("path",{d:"M12 8v4"}),L("path",{d:"M12 16h.01"})]})}var H={xs:{container:"p-2 gap-2 text-xs",title:"text-xs font-semibold leading-tight",description:"text-[11px] leading-tight",icon:"size-3.5 mt-0.5",closeButton:"size-4 p-0.5 -mr-0.5",closeIcon:"size-3",action:"gap-1.5 text-xs"},sm:{container:"p-2.5 gap-2.5 text-xs",title:"text-sm font-semibold leading-tight",description:"text-xs leading-normal",icon:"size-4 mt-0.5",closeButton:"size-5 p-0.5 -mr-0.5",closeIcon:"size-3.5",action:"gap-1.5 text-xs"},md:{container:"p-3.5 gap-3 text-sm",title:"text-sm font-semibold leading-tight",description:"text-sm leading-normal",icon:"size-5 mt-0.5",closeButton:"size-6 p-1 -mr-1",closeIcon:"size-4",action:"gap-2 text-sm"},lg:{container:"p-4 gap-3.5 text-base",title:"text-base font-semibold leading-snug",description:"text-sm leading-normal",icon:"size-6 mt-0.5",closeButton:"size-7 p-1.5 -mr-1.5",closeIcon:"size-4.5",action:"gap-2.5 text-sm"},xl:{container:"p-5 gap-4 text-lg",title:"text-lg font-semibold leading-snug",description:"text-base leading-normal",icon:"size-7 mt-0.5",closeButton:"size-8 p-1.5 -mr-1.5",closeIcon:"size-5",action:"gap-3 text-base"}},W={none:"rounded-none",sm:"rounded-sm",md:"rounded-md",lg:"rounded-lg",xl:"rounded-xl",full:"rounded-2xl"},Z={soft:{primary:"bg-primary-50 text-primary-900 border-2 border-primary-200",secondary:"bg-secondary-50 text-secondary-900 border-2 border-secondary-200",neutral:"bg-neutral-100 text-neutral-900 border-2 border-neutral-200",error:"bg-error-50 text-error-900 border-2 border-error-200",success:"bg-success-50 text-success-900 border-2 border-success-200",warning:"bg-warning-50 text-warning-900 border-2 border-warning-200",info:"bg-info-50 text-info-900 border-2 border-info-200"},filled:{primary:"bg-primary-600 text-white border-2 border-primary-600",secondary:"bg-secondary-600 text-white border-2 border-secondary-600",neutral:"bg-neutral-700 text-white border-2 border-neutral-700",error:"bg-error-600 text-white border-2 border-error-600",success:"bg-success-600 text-white border-2 border-success-600",warning:"bg-warning-500 text-neutral-950 border-2 border-warning-500",info:"bg-info-600 text-white border-2 border-info-600"},outline:{primary:"bg-neutral-white text-primary-700 border-2 border-primary-400",secondary:"bg-neutral-white text-secondary-700 border-2 border-secondary-400",neutral:"bg-neutral-white text-neutral-700 border-2 border-neutral-300",error:"bg-neutral-white text-error-700 border-2 border-error-500",success:"bg-neutral-white text-success-700 border-2 border-success-400",warning:"bg-neutral-white text-warning-800 border-2 border-warning-400",info:"bg-neutral-white text-info-700 border-2 border-info-400"},"accent-left":{primary:"bg-primary-50/70 text-primary-900 border-2 border-primary-200 border-l-4 border-l-primary-500",secondary:"bg-secondary-50/70 text-secondary-900 border-2 border-secondary-200 border-l-4 border-l-secondary-500",neutral:"bg-neutral-50/70 text-neutral-900 border-2 border-neutral-200 border-l-4 border-l-neutral-500",error:"bg-error-50/70 text-error-900 border-2 border-error-200 border-l-4 border-l-error-500",success:"bg-success-50/70 text-success-900 border-2 border-success-200 border-l-4 border-l-success-500",warning:"bg-warning-50/70 text-warning-900 border-2 border-warning-200 border-l-4 border-l-warning-500",info:"bg-info-50/70 text-info-900 border-2 border-info-200 border-l-4 border-l-info-500"},ghost:{primary:"bg-transparent text-primary-800 border-2 border-transparent",secondary:"bg-transparent text-secondary-800 border-2 border-transparent",neutral:"bg-transparent text-neutral-800 border-2 border-transparent",error:"bg-transparent text-error-700 border-2 border-transparent",success:"bg-transparent text-success-700 border-2 border-transparent",warning:"bg-transparent text-warning-800 border-2 border-transparent",info:"bg-transparent text-info-800 border-2 border-transparent"}},q={soft:{primary:"text-primary-600",secondary:"text-secondary-600",neutral:"text-neutral-600",error:"text-error-600",success:"text-success-600",warning:"text-warning-600",info:"text-info-600"},filled:{primary:"text-white",secondary:"text-white",neutral:"text-white",error:"text-white",success:"text-white",warning:"text-neutral-950",info:"text-white"},outline:{primary:"text-primary-600",secondary:"text-secondary-600",neutral:"text-neutral-600",error:"text-error-600",success:"text-success-600",warning:"text-warning-600",info:"text-info-600"},"accent-left":{primary:"text-primary-600",secondary:"text-secondary-600",neutral:"text-neutral-600",error:"text-error-600",success:"text-success-600",warning:"text-warning-600",info:"text-info-600"},ghost:{primary:"text-primary-600",secondary:"text-secondary-600",neutral:"text-neutral-600",error:"text-error-600",success:"text-success-600",warning:"text-warning-600",info:"text-info-600"}},J={primary:R,secondary:R,neutral:R,info:R,success:E,warning:G,error:M};import{jsx as h,jsxs as Q}from"react/jsx-runtime";function I({size:e,variant:r,color:o,radius:t,title:c,description:l,icon:s=!0,action:n,closable:i=!0,onClose:a,closeAriaLabel:u="\u0110\xF3ng c\u1EA3nh b\xE1o",banner:d=!1,titleClassName:m="",descriptionClassName:x="",actionClassName:v="",iconClassName:f="",closeButtonClassName:T="",className:A="",children:N,role:k,ref:ne,...se}){let y=p(e,H,"md"),ie=d?"rounded-none border-x-0 w-full":p(t,W,"lg"),ae=r==="other"?"":p(o,p(r,Z,"soft"),"info"),le=r==="other"?"":p(o,p(r,q,"soft"),"info"),ce=i||!!a,de=O=>{O.stopPropagation(),a?.()},ue=k||(o==="error"||o==="warning"?"alert":"status"),fe=o==="error"||o==="warning"?"assertive":"polite",_=l??N,pe=!!_,ge=()=>{if(s===!1)return null;if(s!==!0&&s!==void 0)return h("span",{className:`inline-flex items-center justify-center shrink-0 leading-none ${y.icon} ${f}`,"aria-hidden":"true",children:s});let O=p(o,J,"info");return h("span",{className:`inline-flex items-center justify-center shrink-0 leading-none ${y.icon} ${le} ${f}`,"aria-hidden":"true",children:h(O,{className:"size-full"})})},be=["relative flex w-full",y.container,ie,ae,A].filter(Boolean).join(" ");return Q("div",{ref:ne,role:ue,"aria-live":fe,className:be,...se,children:[ge(),Q("div",{className:"flex-1 min-w-0 flex flex-col justify-center",children:[c&&h("div",{className:`${y.title} ${m}`,children:c}),pe&&h("div",{className:`${y.description} ${c?"mt-0.5":""} ${x}`,children:_})]}),n&&h("div",{className:`inline-flex items-center shrink-0 ${y.action} ${v}`,children:n}),ce&&h("button",{type:"button",onClick:de,"aria-label":u,className:`inline-flex items-center justify-center shrink-0 rounded-md transition-colors cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-current opacity-70 hover:opacity-100 ${r==="filled"?"hover:bg-white/20 active:bg-white/30":"hover:bg-black/5 active:bg-black/10"} ${y.closeButton} ${T}`,children:h(z,{className:y.closeIcon})})]})}var b=4e3;import{Toaster as Re}from"sonner";import{jsx as Se}from"react/jsx-runtime";function X({position:e="top-right",visibleToasts:r=3,expand:o=!1,duration:t=4e3,closeButton:c=!1,className:l="",toastOptions:s,...n}){return Se(Re,{position:e,visibleToasts:r,expand:o,duration:t,closeButton:c,className:`toaster group ${l}`,toastOptions:{unstyled:!0,className:"w-full max-w-md pointer-events-auto",...s},...n})}import{isValidElement as Ce}from"react";import{toast as S}from"sonner";import{jsx as $}from"react/jsx-runtime";function w(e,r="info",o="soft",t,c={},l){let{duration:s,position:n,onDismiss:i,onAutoClose:a,...u}=c;return S.custom(d=>$(I,{color:r,variant:o,title:e,description:t,closable:u.closable??!0,onClose:()=>{u.onClose?.(),S.dismiss(d)},...u}),{id:l,duration:s??4e3,position:n,onDismiss:i,onAutoClose:a})}function Ae(e){return typeof e=="object"&&e!==null&&!Ce(e)&&!Array.isArray(e)&&("title"in e||"description"in e||"color"in e||"variant"in e)}function B(e,r,o){return Ae(e)?{title:e.title??"",description:e.description,color:e.color??r,variant:e.variant??o}:{title:e,description:void 0,color:r,variant:o}}var g=((e,r,o)=>{let t=o||{};return w(e,t.color||"info",t.variant||"soft",r,t)});g.custom=(e,r={})=>{if(typeof e=="function")return S.custom(n=>e(n),{duration:r.duration??4e3,position:r.position,onDismiss:r.onDismiss,onAutoClose:r.onAutoClose});let{duration:o,position:t,onDismiss:c,onAutoClose:l,...s}=e;return S.custom(n=>$(I,{closable:s.closable??!0,onClose:()=>{s.onClose?.(),S.dismiss(n)},...s}),{duration:o??4e3,position:t,onDismiss:c,onAutoClose:l})};g.success=(e,r,o)=>{let t=o||{};return w(e,t.color||"success",t.variant||"soft",r,t)};g.error=(e,r,o)=>{let t=o||{};return w(e,t.color||"error",t.variant||"soft",r,t)};g.warning=(e,r,o)=>{let t=o||{};return w(e,t.color||"warning",t.variant||"soft",r,t)};g.info=(e,r,o)=>{let t=o||{};return w(e,t.color||"info",t.variant||"soft",r,t)};g.loading=(e,r,o)=>{let t=o||{};return w(e,t.color||"info",t.variant||"soft",r,{...t,icon:t.icon??$(P,{className:"size-full animate-spin"}),duration:t.duration??1/0,closable:t.closable??!1})};g.promise=async(e,r)=>{let o=typeof e=="function"?e():e,{title:t,description:c}=B(r.loading,"info",r.variant||"soft"),l=g.loading(t,c,{size:r.size,variant:r.variant,radius:r.radius});try{let s=await o,n=typeof r.success=="function"?r.success(s):r.success,{title:i,description:a,color:u,variant:d}=B(n,"success",r.variant||"soft");return w(i,u,d,a,{duration:r.duration??4e3,size:r.size,radius:r.radius},l),s}catch(s){let n=typeof r.error=="function"?r.error(s):r.error,{title:i,description:a,color:u,variant:d}=B(n,"error",r.variant||"soft");throw w(i,u,d,a,{duration:r.duration??4e3,size:r.size,radius:r.radius},l),s}finally{r.finally&&await r.finally()}};g.dismiss=e=>S.dismiss(e);var Ne=g;import{useCallback as Y,useEffect as ee,useRef as ke,useState as re}from"react";function Tr({onLoadMore:e,hasMore:r=!0,isLoading:o=!1,disabled:t=!1,rootMargin:c="100px",threshold:l=0,root:s=null}){let[n,i]=re(!1),[a,u]=re(null),d=ke(!1),m=r&&!o&&!t,x=Y(()=>{if(!(!m||d.current)){d.current=!0;try{let f=e();f&&typeof f.then=="function"?f.finally(()=>{d.current=!1}):d.current=!1}catch{d.current=!1}}},[m,e]),v=Y(f=>{u(f)},[]);return ee(()=>{if(!a||t||!r||typeof window>"u"||!("IntersectionObserver"in window))return;let f=s&&typeof s=="object"&&"current"in s?s.current:s,T=new IntersectionObserver(A=>{let[N]=A;if(!N)return;let k=N.isIntersecting;i(k),k&&x()},{root:f,rootMargin:c,threshold:l});return T.observe(a),()=>{T.disconnect(),i(!1)}},[a,s,c,l,t,r,x]),ee(()=>{m&&n&&x()},[m,n,x]),{sentinelRef:v,isIntersecting:n}}var te={pulse:"animate-pulse",wave:"skeleton-wave",none:""},oe={none:"rounded-none",sm:"rounded-sm",md:"rounded-md",lg:"rounded-lg",xl:"rounded-xl",full:"rounded-full"},Rr={cover:"object-cover",contain:"object-contain",fill:"object-fill",none:"object-none","scale-down":"object-scale-down"};var C=e=>e,Cr=e=>e?typeof e=="string"?e:typeof File<"u"&&e instanceof File?`${e.name}-${e.size}-${e.lastModified}`:typeof Blob<"u"&&e instanceof Blob?`${e.size}-${e.type}`:typeof e=="object"&&"src"in e&&typeof e.src=="string"?e.src:e:"";import{jsx as D}from"react/jsx-runtime";function Ie({ref:e,variant:r="pulse",shape:o="rectangle",radius:t="md",width:c,height:l="1rem",lines:s=1,gap:n="0.5rem",className:i="",style:a,...u}){let d=p(r,te,"pulse"),m=o==="circle"?"rounded-full":p(t,oe,"md"),x={width:C(c),height:C(l),...a},v=["bg-neutral-200",d,m].filter(Boolean).join(" ");return s>1?D("div",{ref:e,role:"status","aria-label":"Loading...",className:["flex flex-col",i].filter(Boolean).join(" "),style:{gap:C(n),...a},...u,children:Array.from({length:s}).map((f,T)=>{let A=T===s-1;return D("div",{className:v,style:{width:A?"60%":C(c)??"100%",height:C(l)}},T)})}):D("div",{ref:e,role:"status","aria-label":"Loading...",className:[v,i].filter(Boolean).join(" "),style:x,...u})}var zr={sm:{head:"px-3 py-2 text-xs font-semibold",cell:"px-3 py-2 text-xs",text:"text-xs",icon:"w-3.5 h-3.5"},md:{head:"px-4 py-3 text-sm font-semibold",cell:"px-4 py-3 text-sm",text:"text-sm",icon:"w-4 h-4"},lg:{head:"px-5 py-4 text-base font-semibold",cell:"px-5 py-4 text-base",text:"text-base",icon:"w-5 h-5"}},Vr={default:{container:"border border-neutral-200 shadow-xs",table:"border-collapse",head:"bg-neutral-50/80 border-b border-neutral-200 text-neutral-600",row:"border-b border-neutral-200/80 hover:bg-neutral-50/50 transition-colors",cell:"text-neutral-700"},striped:{container:"border border-neutral-200 shadow-xs",table:"border-collapse",head:"bg-neutral-100/80 border-b border-neutral-200 text-neutral-700",row:"border-b border-neutral-200/80 even:bg-neutral-50/60 hover:bg-neutral-100/40 transition-colors",cell:"text-neutral-700"},bordered:{container:"border border-neutral-200 shadow-xs",table:"border-collapse border border-neutral-200",head:"bg-neutral-50 border border-neutral-200 text-neutral-700",row:"border-b border-neutral-200 hover:bg-neutral-50/50 transition-colors",cell:"border border-neutral-200 text-neutral-700"}},Er={left:"text-left justify-start",center:"text-center justify-center",right:"text-right justify-end"},jr=[10,20,50,100],Gr=10;export{p as a,ze as b,P as c,z as d,R as e,E as f,G as g,M as h,I as i,b as j,X as k,Ne as l,Tr as m,oe as n,Rr as o,Cr as p,Ie as q,zr as r,Vr as s,Er as t,jr as u,Gr as v};
3
+ //# sourceMappingURL=chunk-X4LIYOS5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/function.ts","../src/components/icons/Spinner.tsx","../src/components/icons/CloseIcon.tsx","../src/components/icons/InfoCircleIcon.tsx","../src/components/icons/CheckCircleIcon.tsx","../src/components/icons/AlertTriangleIcon.tsx","../src/components/icons/AlertCircleIcon.tsx","../src/components/alert/constants.ts","../src/components/alert/Alert.tsx","../src/components/toast/constants.ts","../src/components/toast/Toaster.tsx","../src/components/toast/toast.tsx","../src/hooks/useInfiniteScroll.ts","../src/components/skeleton/constants.ts","../src/components/skeleton/utils.ts","../src/components/skeleton/Skeleton.tsx","../src/components/table/constants.ts"],"sourcesContent":["import { rankItem, compareItems } from \"@tanstack/match-sorter-utils\";\n\n/**\n * Lấy cấu hình tương ứng theo `key` từ đối tượng `configMap` (sizeConfig, radiusConfig, variantConfig, colorConfig...),\n * nếu `key` không hợp lệ hoặc không tồn tại trong `configMap`, sẽ trả về giá trị an toàn của `fallbackKey`.\n *\n * @param key - Khóa được truyền vào (size, radius, variant... có thể undefined, null hoặc chuỗi bất kỳ)\n * @param configMap - Đối tượng ánh xạ cấu hình\n * @param fallbackKey - Khóa dự phòng khi không tìm thấy (fallback an toàn)\n * @returns Cấu hình tương ứng của khóa đó\n */\nexport function getSafeConfig<TConfig, TKey extends PropertyKey = string>(\n key: TKey | undefined | null,\n configMap: Record<TKey, TConfig>,\n fallbackKey: TKey\n): TConfig {\n if (key != null && Object.prototype.hasOwnProperty.call(configMap, key)) {\n return configMap[key];\n }\n return configMap[fallbackKey];\n}\n\n/**\n * Lọc và xếp hạng danh sách phần tử theo từ khóa tìm kiếm (Fuzzy Search qua @tanstack/match-sorter-utils).\n *\n * @param items - Danh sách các phần tử cần lọc\n * @param query - Từ khóa tìm kiếm\n * @param fields - Một trường hoặc danh sách các trường (hoặc hàm accessor) để so khớp\n * @returns Danh sách phần tử khớp, đã được sắp xếp theo độ tương quan cao nhất\n */\nexport function rankAndFilterItems<T>(\n items: T[],\n query: string,\n fields?: (keyof T | string | ((item: T) => string | undefined | null))[] | (keyof T | string)\n): T[] {\n const keyword = query.trim();\n if (!keyword || !items.length) {\n return items;\n }\n\n const fieldsArray = fields ? (Array.isArray(fields) ? fields : [fields]) : [];\n const accessors: ((item: T) => string)[] = [];\n\n if (fieldsArray.length > 0) {\n for (const field of fieldsArray) {\n if (typeof field === \"function\") {\n accessors.push((item: T) => {\n const res = field(item);\n return res != null ? String(res) : \"\";\n });\n } else {\n accessors.push((item: T) => {\n const record = item as Record<string, unknown>;\n const val = record[field as string];\n if (val != null) return String(val);\n if (record.data && typeof record.data === \"object\") {\n const dataVal = (record.data as Record<string, unknown>)[field as string];\n if (dataVal != null) return String(dataVal);\n }\n return \"\";\n });\n }\n }\n } else {\n // Mặc định: kiểm tra trường 'label', 'value' nếu là object, hoặc ép kiểu string\n accessors.push((item: T) => {\n if (item && typeof item === \"object\") {\n const record = item as Record<string, unknown>;\n return record.label != null\n ? String(record.label)\n : record.value != null\n ? String(record.value)\n : \"\";\n }\n return item != null ? String(item) : \"\";\n });\n }\n\n const ranked: { item: T; info: ReturnType<typeof rankItem> }[] = [];\n for (const item of items) {\n const info = rankItem(item, keyword, { accessors });\n if (info.passed) {\n ranked.push({ item, info });\n }\n }\n\n ranked.sort((a, b) => compareItems(a.info, b.info));\n return ranked.map((r) => r.item);\n}\n","import type { SVGProps } from \"react\";\n\nexport default function Spinner({ width = 16, height = 16, className = \"\", ...props }: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width={width}\n height={height}\n className={`animate-[spin_1s_steps(8)_infinite] ${className}`}\n viewBox=\"0 0 25 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path d=\"M0 0h25v24H0z\" fill=\"none\" />\n <path\n fill=\"currentColor\"\n d=\"M4.818 6.664h-.001a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.277.541h-.029zm-2.97 7.182h-.001a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.278.541h-.031h.002zM12 3.692a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.277.541zM4.818 21.029h-.001a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.276.541h-.031zM19.182 7.125a2.308 2.308 0 1 1 0-4.615a2.308 2.308 0 0 1 0 4.615M12 24a1.847 1.847 0 1 1 1.306-.541a1.77 1.77 0 0 1-1.277.541zm10.154-9.231h-.048c-.75 0-1.428-.309-1.913-.807l-.001-.001c-.499-.503-.808-1.196-.808-1.961s.308-1.458.808-1.962a2.66 2.66 0 0 1 1.914-.808h.05h-.003h.048c.75 0 1.427.309 1.913.807l.001.001c.499.503.808 1.196.808 1.961s-.308 1.458-.808 1.962a2.66 2.66 0 0 1-1.915.809h-.049zm-2.971 7.643h-.05a3.1 3.1 0 0 1-2.236-.951l-.001-.001c-.584-.584-.945-1.391-.945-2.283s.361-1.698.945-2.283a3.1 3.1 0 0 1 2.234-.945h.054h-.003h.042c.877 0 1.67.362 2.237.944l.001.001c.588.582.952 1.39.952 2.283s-.364 1.7-.952 2.282a3.1 3.1 0 0 1-2.24.953h-.04z\"\n />\n </svg>\n );\n}\n","import type { SVGProps } from \"react\";\n\nexport default function CloseIcon({ width = 14, height = 14, className = \"\", ...props }: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width={width}\n height={height}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n {...props}\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n","import type { SVGProps } from \"react\";\n\nexport default function InfoCircleIcon({ width = 20, height = 20, className = \"\", ...props }: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width={width}\n height={height}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n </svg>\n );\n}\n","import type { SVGProps } from \"react\";\n\nexport default function CheckCircleIcon({\n width = 20,\n height = 20,\n className = \"\",\n ...props\n}: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width={width}\n height={height}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m9 12 2 2 4-4\" />\n </svg>\n );\n}\n","import type { SVGProps } from \"react\";\n\nexport default function AlertTriangleIcon({\n width = 20,\n height = 20,\n className = \"\",\n ...props\n}: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width={width}\n height={height}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n {...props}\n >\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n </svg>\n );\n}\n","import type { SVGProps } from \"react\";\n\nexport default function AlertCircleIcon({\n width = 20,\n height = 20,\n className = \"\",\n ...props\n}: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width={width}\n height={height}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n </svg>\n );\n}\n","import React from \"react\";\nimport { AlertColor, AlertRadius, AlertSize, AlertVariant } from \"./types\";\nimport InfoCircleIcon from \"../icons/InfoCircleIcon\";\nimport CheckCircleIcon from \"../icons/CheckCircleIcon\";\nimport AlertTriangleIcon from \"../icons/AlertTriangleIcon\";\nimport AlertCircleIcon from \"../icons/AlertCircleIcon\";\n\n\nexport const sizeConfig: Record<\n AlertSize,\n {\n container: string;\n title: string;\n description: string;\n icon: string;\n closeButton: string;\n closeIcon: string;\n action: string;\n }\n> = {\n xs: {\n container: \"p-2 gap-2 text-xs\",\n title: \"text-xs font-semibold leading-tight\",\n description: \"text-[11px] leading-tight\",\n icon: \"size-3.5 mt-0.5\",\n closeButton: \"size-4 p-0.5 -mr-0.5\",\n closeIcon: \"size-3\",\n action: \"gap-1.5 text-xs\",\n },\n sm: {\n container: \"p-2.5 gap-2.5 text-xs\",\n title: \"text-sm font-semibold leading-tight\",\n description: \"text-xs leading-normal\",\n icon: \"size-4 mt-0.5\",\n closeButton: \"size-5 p-0.5 -mr-0.5\",\n closeIcon: \"size-3.5\",\n action: \"gap-1.5 text-xs\",\n },\n md: {\n container: \"p-3.5 gap-3 text-sm\",\n title: \"text-sm font-semibold leading-tight\",\n description: \"text-sm leading-normal\",\n icon: \"size-5 mt-0.5\",\n closeButton: \"size-6 p-1 -mr-1\",\n closeIcon: \"size-4\",\n action: \"gap-2 text-sm\",\n },\n lg: {\n container: \"p-4 gap-3.5 text-base\",\n title: \"text-base font-semibold leading-snug\",\n description: \"text-sm leading-normal\",\n icon: \"size-6 mt-0.5\",\n closeButton: \"size-7 p-1.5 -mr-1.5\",\n closeIcon: \"size-4.5\",\n action: \"gap-2.5 text-sm\",\n },\n xl: {\n container: \"p-5 gap-4 text-lg\",\n title: \"text-lg font-semibold leading-snug\",\n description: \"text-base leading-normal\",\n icon: \"size-7 mt-0.5\",\n closeButton: \"size-8 p-1.5 -mr-1.5\",\n closeIcon: \"size-5\",\n action: \"gap-3 text-base\",\n },\n};\n\nexport const radiusConfig: Record<AlertRadius, string> = {\n none: \"rounded-none\",\n sm: \"rounded-sm\",\n md: \"rounded-md\",\n lg: \"rounded-lg\",\n xl: \"rounded-xl\",\n full: \"rounded-2xl\",\n};\n\nexport const variantColorConfig: Record<Exclude<AlertVariant, \"other\">, Record<AlertColor, string>> = {\n soft: {\n primary: \"bg-primary-50 text-primary-900 border-2 border-primary-200\",\n secondary: \"bg-secondary-50 text-secondary-900 border-2 border-secondary-200\",\n neutral: \"bg-neutral-100 text-neutral-900 border-2 border-neutral-200\",\n error: \"bg-error-50 text-error-900 border-2 border-error-200\",\n success: \"bg-success-50 text-success-900 border-2 border-success-200\",\n warning: \"bg-warning-50 text-warning-900 border-2 border-warning-200\",\n info: \"bg-info-50 text-info-900 border-2 border-info-200\",\n },\n filled: {\n primary: \"bg-primary-600 text-white border-2 border-primary-600\",\n secondary: \"bg-secondary-600 text-white border-2 border-secondary-600\",\n neutral: \"bg-neutral-700 text-white border-2 border-neutral-700\",\n error: \"bg-error-600 text-white border-2 border-error-600\",\n success: \"bg-success-600 text-white border-2 border-success-600\",\n warning: \"bg-warning-500 text-neutral-950 border-2 border-warning-500\",\n info: \"bg-info-600 text-white border-2 border-info-600\",\n },\n outline: {\n primary: \"bg-neutral-white text-primary-700 border-2 border-primary-400\",\n secondary: \"bg-neutral-white text-secondary-700 border-2 border-secondary-400\",\n neutral: \"bg-neutral-white text-neutral-700 border-2 border-neutral-300\",\n error: \"bg-neutral-white text-error-700 border-2 border-error-500\",\n success: \"bg-neutral-white text-success-700 border-2 border-success-400\",\n warning: \"bg-neutral-white text-warning-800 border-2 border-warning-400\",\n info: \"bg-neutral-white text-info-700 border-2 border-info-400\",\n },\n \"accent-left\": {\n primary: \"bg-primary-50/70 text-primary-900 border-2 border-primary-200 border-l-4 border-l-primary-500\",\n secondary: \"bg-secondary-50/70 text-secondary-900 border-2 border-secondary-200 border-l-4 border-l-secondary-500\",\n neutral: \"bg-neutral-50/70 text-neutral-900 border-2 border-neutral-200 border-l-4 border-l-neutral-500\",\n error: \"bg-error-50/70 text-error-900 border-2 border-error-200 border-l-4 border-l-error-500\",\n success: \"bg-success-50/70 text-success-900 border-2 border-success-200 border-l-4 border-l-success-500\",\n warning: \"bg-warning-50/70 text-warning-900 border-2 border-warning-200 border-l-4 border-l-warning-500\",\n info: \"bg-info-50/70 text-info-900 border-2 border-info-200 border-l-4 border-l-info-500\",\n },\n ghost: {\n primary: \"bg-transparent text-primary-800 border-2 border-transparent\",\n secondary: \"bg-transparent text-secondary-800 border-2 border-transparent\",\n neutral: \"bg-transparent text-neutral-800 border-2 border-transparent\",\n error: \"bg-transparent text-error-700 border-2 border-transparent\",\n success: \"bg-transparent text-success-700 border-2 border-transparent\",\n warning: \"bg-transparent text-warning-800 border-2 border-transparent\",\n info: \"bg-transparent text-info-800 border-2 border-transparent\",\n },\n};\n\nexport const iconColorConfig: Record<Exclude<AlertVariant, \"other\">, Record<AlertColor, string>> = {\n soft: {\n primary: \"text-primary-600\",\n secondary: \"text-secondary-600\",\n neutral: \"text-neutral-600\",\n error: \"text-error-600\",\n success: \"text-success-600\",\n warning: \"text-warning-600\",\n info: \"text-info-600\",\n },\n filled: {\n primary: \"text-white\",\n secondary: \"text-white\",\n neutral: \"text-white\",\n error: \"text-white\",\n success: \"text-white\",\n warning: \"text-neutral-950\",\n info: \"text-white\",\n },\n outline: {\n primary: \"text-primary-600\",\n secondary: \"text-secondary-600\",\n neutral: \"text-neutral-600\",\n error: \"text-error-600\",\n success: \"text-success-600\",\n warning: \"text-warning-600\",\n info: \"text-info-600\",\n },\n \"accent-left\": {\n primary: \"text-primary-600\",\n secondary: \"text-secondary-600\",\n neutral: \"text-neutral-600\",\n error: \"text-error-600\",\n success: \"text-success-600\",\n warning: \"text-warning-600\",\n info: \"text-info-600\",\n },\n ghost: {\n primary: \"text-primary-600\",\n secondary: \"text-secondary-600\",\n neutral: \"text-neutral-600\",\n error: \"text-error-600\",\n success: \"text-success-600\",\n warning: \"text-warning-600\",\n info: \"text-info-600\",\n },\n};\n\nexport const defaultIcons: Record<AlertColor, React.ComponentType<{ className?: string }>> = {\n primary: InfoCircleIcon,\n secondary: InfoCircleIcon,\n neutral: InfoCircleIcon,\n info: InfoCircleIcon,\n success: CheckCircleIcon,\n warning: AlertTriangleIcon,\n error: AlertCircleIcon,\n};","import { MouseEvent } from \"react\";\nimport { AlertProps } from \"./types\";\nimport { sizeConfig, radiusConfig, variantColorConfig, iconColorConfig, defaultIcons } from \"./constants\";\nimport CloseIcon from \"../icons/CloseIcon\";\nimport { getSafeConfig } from \"@/utils/function\";\n\nexport default function Alert({\n size,\n variant,\n color,\n radius,\n title,\n description,\n icon = true,\n action,\n closable = true,\n onClose,\n closeAriaLabel = \"Đóng cảnh báo\",\n banner = false,\n titleClassName = \"\",\n descriptionClassName = \"\",\n actionClassName = \"\",\n iconClassName = \"\",\n closeButtonClassName = \"\",\n className = \"\",\n children,\n role,\n ref,\n ...props\n}: AlertProps) {\n\n const currentSize = getSafeConfig(size, sizeConfig, \"md\");\n\n const roundedClass = banner ? \"rounded-none border-x-0 w-full\" : getSafeConfig(radius, radiusConfig, \"lg\");\n\n const variantStyles = variant === \"other\" ? \"\" : getSafeConfig(color, getSafeConfig(variant, variantColorConfig, \"soft\"), \"info\");\n\n const iconColorStyle = variant === \"other\" ? \"\" : getSafeConfig(color, getSafeConfig(variant, iconColorConfig, \"soft\"), \"info\");\n\n const isClosable = closable || Boolean(onClose);\n\n const handleClose = (e: MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n onClose?.();\n };\n\n const defaultRole = color === \"error\" || color === \"warning\" ? \"alert\" : \"status\";\n const activeRole = role || defaultRole;\n const ariaLive = color === \"error\" || color === \"warning\" ? \"assertive\" : \"polite\";\n\n const bodyContent = description ?? children;\n const hasBody = Boolean(bodyContent);\n\n const renderIcon = () => {\n if (icon === false) return null;\n\n if (icon !== true && icon !== undefined) {\n return (\n <span\n className={`inline-flex items-center justify-center shrink-0 leading-none ${currentSize.icon} ${iconClassName}`}\n aria-hidden=\"true\"\n >\n {icon}\n </span>\n );\n }\n\n const IconComponent = getSafeConfig(color, defaultIcons, \"info\");\n return (\n <span\n className={`inline-flex items-center justify-center shrink-0 leading-none ${currentSize.icon} ${iconColorStyle} ${iconClassName}`}\n aria-hidden=\"true\"\n >\n <IconComponent className=\"size-full\" />\n </span>\n );\n };\n\n const containerClasses = [\n \"relative flex w-full\",\n currentSize.container,\n roundedClass,\n variantStyles,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <div ref={ref} role={activeRole} aria-live={ariaLive} className={containerClasses} {...props}>\n {renderIcon()}\n\n <div className=\"flex-1 min-w-0 flex flex-col justify-center\">\n {title && <div className={`${currentSize.title} ${titleClassName}`}>{title}</div>}\n {hasBody && (\n <div className={`${currentSize.description} ${title ? \"mt-0.5\" : \"\"} ${descriptionClassName}`}>\n {bodyContent}\n </div>\n )}\n </div>\n\n {action && (\n <div className={`inline-flex items-center shrink-0 ${currentSize.action} ${actionClassName}`}>{action}</div>\n )}\n\n {isClosable && (\n <button\n type=\"button\"\n onClick={handleClose}\n aria-label={closeAriaLabel}\n className={`inline-flex items-center justify-center shrink-0 rounded-md transition-colors cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-current opacity-70 hover:opacity-100 ${\n variant === \"filled\" ? \"hover:bg-white/20 active:bg-white/30\" : \"hover:bg-black/5 active:bg-black/10\"\n } ${currentSize.closeButton} ${closeButtonClassName}`}\n >\n <CloseIcon className={currentSize.closeIcon} />\n </button>\n )}\n </div>\n );\n}\n","/**\n * Thời gian hiển thị mặc định của thông báo Toast trước khi tự động đóng (miliseconds).\n */\nexport const DEFAULT_TOAST_DURATION = 4000;\n","import { Toaster as SonnerToaster } from \"sonner\";\nimport { ToasterProps } from \"./types\";\nimport { DEFAULT_TOAST_DURATION } from \"./constants\";\n\nexport default function Toaster({\n position = \"top-right\",\n visibleToasts = 3,\n expand = false,\n duration = DEFAULT_TOAST_DURATION,\n closeButton = false,\n className = \"\",\n toastOptions,\n ...props\n}: ToasterProps) {\n return (\n <SonnerToaster\n position={position}\n visibleToasts={visibleToasts}\n expand={expand}\n duration={duration}\n closeButton={closeButton}\n className={`toaster group ${className}`}\n toastOptions={{\n unstyled: true,\n className: \"w-full max-w-md pointer-events-auto\",\n ...toastOptions,\n }}\n {...props}\n />\n );\n}\n","import { isValidElement, ReactElement, ReactNode } from \"react\";\nimport { toast as sonnerToast } from \"sonner\";\nimport Alert from \"../alert/Alert\";\nimport { AlertColor, AlertProps, AlertVariant } from \"../alert/types\";\nimport { ToastMessageObject, ToastMessageResult, ToastOptions, ToastPromiseOptions } from \"./types\";\nimport { DEFAULT_TOAST_DURATION } from \"./constants\";\nimport Spinner from \"../icons/Spinner\";\n\nfunction showToast(\n title: ReactNode,\n color: AlertColor = \"info\",\n variant: AlertVariant = \"soft\",\n description?: ReactNode,\n options: ToastOptions = {},\n customId?: string | number\n): string | number {\n const { duration, position, onDismiss, onAutoClose, ...alertProps } = options;\n\n return sonnerToast.custom(\n (id) => (\n <Alert\n color={color}\n variant={variant}\n title={title}\n description={description}\n closable={alertProps.closable ?? true}\n onClose={() => {\n alertProps.onClose?.();\n sonnerToast.dismiss(id);\n }}\n {...alertProps}\n />\n ),\n {\n id: customId,\n duration: duration ?? DEFAULT_TOAST_DURATION,\n position,\n onDismiss,\n onAutoClose,\n }\n );\n}\n\nfunction isToastMessageObject(value: unknown): value is ToastMessageObject {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !isValidElement(value) &&\n !Array.isArray(value) &&\n (\"title\" in value || \"description\" in value || \"color\" in value || \"variant\" in value)\n );\n}\n\nfunction extractMessageDetails(\n result: ToastMessageResult,\n fallbackColor: AlertColor,\n fallbackVariant: AlertVariant\n): { title: ReactNode; description?: ReactNode; color: AlertColor; variant: AlertVariant } {\n if (isToastMessageObject(result)) {\n return {\n title: result.title ?? \"\",\n description: result.description,\n color: result.color ?? fallbackColor,\n variant: result.variant ?? fallbackVariant,\n };\n }\n\n return {\n title: result,\n description: undefined,\n color: fallbackColor,\n variant: fallbackVariant,\n };\n}\n\n/**\n * Interface cho hàm và các phương thức của đối tượng `toast`.\n * Cú pháp chuẩn: `toast.success(title, description?, options?)`\n */\nexport interface ToastFunction {\n /**\n * Kích hoạt một thông báo Toast mặc định (màu info).\n * @param title Tiêu đề của thông báo\n * @param description Nội dung mô tả chi tiết (tùy chọn)\n * @param options Cấu hình phụ như variant, size, duration,... (tùy chọn)\n */\n (title: ReactNode, description?: ReactNode, options?: ToastOptions): string | number;\n\n /**\n * Kích hoạt thông báo tùy biến hoàn toàn (dùng hàm render JSX hoặc truyền AlertProps).\n */\n custom: {\n (jsxFn: (id: string | number) => ReactNode, options?: ToastOptions): string | number;\n (alertProps: AlertProps & ToastOptions): string | number;\n };\n\n /**\n * Kích hoạt thông báo thành công (màu success).\n */\n success: (title: ReactNode, description?: ReactNode, options?: ToastOptions) => string | number;\n\n /**\n * Kích hoạt thông báo lỗi (màu error).\n */\n error: (title: ReactNode, description?: ReactNode, options?: ToastOptions) => string | number;\n\n /**\n * Kích hoạt thông báo cảnh báo (màu warning).\n */\n warning: (title: ReactNode, description?: ReactNode, options?: ToastOptions) => string | number;\n\n /**\n * Kích hoạt thông báo thông tin (màu info).\n */\n info: (title: ReactNode, description?: ReactNode, options?: ToastOptions) => string | number;\n\n /**\n * Kích hoạt thông báo đang tải dữ liệu (Loading spinner, không tự đóng).\n */\n loading: (title: ReactNode, description?: ReactNode, options?: ToastOptions) => string | number;\n\n /**\n * Tự động theo dõi Promise và chuyển trạng thái Toast từ Loading sang Success hoặc Error.\n */\n promise: <T>(\n promise: Promise<T> | (() => Promise<T>),\n options: ToastPromiseOptions<T>\n ) => Promise<T>;\n\n /**\n * Đóng một Toast cụ thể bằng ID hoặc đóng tất cả Toasts nếu không truyền ID.\n */\n dismiss: (id?: string | number) => string | number | undefined;\n}\n\nconst baseToast = ((\n title: ReactNode,\n description?: ReactNode,\n options?: ToastOptions\n): string | number => {\n const opts = options || {};\n return showToast(title, opts.color || \"info\", opts.variant || \"soft\", description, opts);\n}) as ToastFunction;\n\nbaseToast.custom = (\n arg: ((id: string | number) => ReactNode) | (AlertProps & ToastOptions),\n options: ToastOptions = {}\n): string | number => {\n if (typeof arg === \"function\") {\n return sonnerToast.custom(\n (id) => arg(id) as ReactElement,\n {\n duration: options.duration ?? DEFAULT_TOAST_DURATION,\n position: options.position,\n onDismiss: options.onDismiss,\n onAutoClose: options.onAutoClose,\n }\n );\n }\n\n const { duration, position, onDismiss, onAutoClose, ...alertProps } = arg;\n return sonnerToast.custom(\n (id) => (\n <Alert\n closable={alertProps.closable ?? true}\n onClose={() => {\n alertProps.onClose?.();\n sonnerToast.dismiss(id);\n }}\n {...alertProps}\n />\n ),\n {\n duration: duration ?? DEFAULT_TOAST_DURATION,\n position,\n onDismiss,\n onAutoClose,\n }\n );\n};\n\nbaseToast.success = (\n title: ReactNode,\n description?: ReactNode,\n options?: ToastOptions\n): string | number => {\n const opts = options || {};\n return showToast(title, opts.color || \"success\", opts.variant || \"soft\", description, opts);\n};\n\nbaseToast.error = (\n title: ReactNode,\n description?: ReactNode,\n options?: ToastOptions\n): string | number => {\n const opts = options || {};\n return showToast(title, opts.color || \"error\", opts.variant || \"soft\", description, opts);\n};\n\nbaseToast.warning = (\n title: ReactNode,\n description?: ReactNode,\n options?: ToastOptions\n): string | number => {\n const opts = options || {};\n return showToast(title, opts.color || \"warning\", opts.variant || \"soft\", description, opts);\n};\n\nbaseToast.info = (\n title: ReactNode,\n description?: ReactNode,\n options?: ToastOptions\n): string | number => {\n const opts = options || {};\n return showToast(title, opts.color || \"info\", opts.variant || \"soft\", description, opts);\n};\n\nbaseToast.loading = (\n title: ReactNode,\n description?: ReactNode,\n options?: ToastOptions\n): string | number => {\n const opts = options || {};\n return showToast(title, opts.color || \"info\", opts.variant || \"soft\", description, {\n ...opts,\n icon: opts.icon ?? <Spinner className=\"size-full animate-spin\" />,\n duration: opts.duration ?? Infinity,\n closable: opts.closable ?? false,\n });\n};\n\nbaseToast.promise = async <T,>(\n promiseInput: Promise<T> | (() => Promise<T>),\n options: ToastPromiseOptions<T>\n): Promise<T> => {\n const promise = typeof promiseInput === \"function\" ? promiseInput() : promiseInput;\n\n const { title: loadingTitle, description: loadingDesc } = extractMessageDetails(\n options.loading,\n \"info\",\n options.variant || \"soft\"\n );\n\n const toastId = baseToast.loading(loadingTitle, loadingDesc, {\n size: options.size,\n variant: options.variant,\n radius: options.radius,\n });\n\n try {\n const data = await promise;\n\n const rawSuccess = typeof options.success === \"function\" ? options.success(data) : options.success;\n const { title: successTitle, description: successDesc, color: successColor, variant: successVariant } =\n extractMessageDetails(rawSuccess, \"success\", options.variant || \"soft\");\n\n showToast(\n successTitle,\n successColor,\n successVariant,\n successDesc,\n {\n duration: options.duration ?? DEFAULT_TOAST_DURATION,\n size: options.size,\n radius: options.radius,\n },\n toastId\n );\n\n return data;\n } catch (err: unknown) {\n const rawError = typeof options.error === \"function\" ? options.error(err) : options.error;\n const { title: errorTitle, description: errorDesc, color: errorColor, variant: errorVariant } =\n extractMessageDetails(rawError, \"error\", options.variant || \"soft\");\n\n showToast(\n errorTitle,\n errorColor,\n errorVariant,\n errorDesc,\n {\n duration: options.duration ?? DEFAULT_TOAST_DURATION,\n size: options.size,\n radius: options.radius,\n },\n toastId\n );\n\n throw err;\n } finally {\n if (options.finally) {\n await options.finally();\n }\n }\n};\n\nbaseToast.dismiss = (id?: string | number): string | number | undefined => {\n return sonnerToast.dismiss(id);\n};\n\nexport const toast: ToastFunction = baseToast;\n","import { useCallback, useEffect, useRef, useState, type RefObject } from \"react\";\r\n\r\n/**\r\n * Các tùy chọn cấu hình cho hook `useInfiniteScroll`.\r\n */\r\nexport interface UseInfiniteScrollOptions {\r\n /**\r\n * Callback được gọi khi người dùng cuộn đến cuối hoặc phần tử sentinel xuất hiện trong tầm nhìn.\r\n */\r\n onLoadMore: () => void | Promise<void>;\r\n\r\n /**\r\n * Cho biết còn dữ liệu để tải tiếp hay không.\r\n * Nếu là `false`, `onLoadMore` sẽ không được kích hoạt.\r\n * @default true\r\n */\r\n hasMore?: boolean;\r\n\r\n /**\r\n * Trạng thái đang tải dữ liệu của trang hiện tại / tiếp theo.\r\n * Nếu là `true`, `onLoadMore` sẽ tạm hoãn cho đến khi lượt tải trước hoàn tất.\r\n * @default false\r\n */\r\n isLoading?: boolean;\r\n\r\n /**\r\n * Vô hiệu hóa toàn bộ cơ chế theo dõi cuộn vô hạn.\r\n * @default false\r\n */\r\n disabled?: boolean;\r\n\r\n /**\r\n * Khoảng cách biên rootMargin của IntersectionObserver (vd: \"100px\", \"0px 0px 100px 0px\").\r\n * Giúp kích hoạt tải trước khi người dùng chạm tới đáy danh sách để trải nghiệm mượt mà hơn.\r\n * @default \"100px\"\r\n */\r\n rootMargin?: string;\r\n\r\n /**\r\n * Ngưỡng hiển thị threshold của IntersectionObserver (từ 0.0 đến 1.0).\r\n * @default 0\r\n */\r\n threshold?: number | number[];\r\n\r\n /**\r\n * Phần tử gốc hoặc RefObject làm khung cuộn cho IntersectionObserver.\r\n * Nếu là `null` hoặc `undefined`, sẽ dùng viewport của trình duyệt hoặc khung cuộn cha gần nhất.\r\n * @default null\r\n */\r\n root?: Element | null | RefObject<Element | null>;\r\n}\r\n\r\n/**\r\n * Kết quả trả về từ hook `useInfiniteScroll`.\r\n */\r\nexport interface UseInfiniteScrollReturn {\r\n /**\r\n * Ref callback gắn vào phần tử sentinel ở cuối danh sách để theo dõi bằng IntersectionObserver.\r\n */\r\n sentinelRef: (node: HTMLElement | null) => void;\r\n\r\n /**\r\n * Trạng thái phần tử sentinel có đang nằm trong tầm quan sát hay không.\r\n */\r\n isIntersecting: boolean;\r\n}\r\n\r\n/**\r\n * Hook `useInfiniteScroll` độc lập hỗ trợ tải dữ liệu vô tận qua IntersectionObserver.\r\n */\r\nexport function useInfiniteScroll({\r\n onLoadMore,\r\n hasMore = true,\r\n isLoading = false,\r\n disabled = false,\r\n rootMargin = \"100px\",\r\n threshold = 0,\r\n root = null,\r\n}: UseInfiniteScrollOptions): UseInfiniteScrollReturn {\r\n const [isIntersecting, setIsIntersecting] = useState(false);\r\n const [sentinelNode, setSentinelNode] = useState<HTMLElement | null>(null);\r\n\r\n // Chỉ cần 1 ref duy nhất làm mutex lock để tránh kích hoạt 2 lần cùng lúc trong 1 frame\r\n const isTriggeringRef = useRef(false);\r\n\r\n const canTrigger = hasMore && !isLoading && !disabled;\r\n\r\n const triggerLoadMore = useCallback(() => {\r\n if (!canTrigger || isTriggeringRef.current) return;\r\n\r\n isTriggeringRef.current = true;\r\n try {\r\n const result = onLoadMore();\r\n if (result && typeof (result as Promise<void>).then === \"function\") {\r\n (result as Promise<void>).finally(() => {\r\n isTriggeringRef.current = false;\r\n });\r\n } else {\r\n isTriggeringRef.current = false;\r\n }\r\n } catch {\r\n isTriggeringRef.current = false;\r\n }\r\n }, [canTrigger, onLoadMore]);\r\n\r\n // Sentinel ref callback\r\n const sentinelRef = useCallback((node: HTMLElement | null) => {\r\n setSentinelNode(node);\r\n }, []);\r\n\r\n // IntersectionObserver setup\r\n useEffect(() => {\r\n if (!sentinelNode || disabled || !hasMore) {\r\n return;\r\n }\r\n\r\n if (typeof window === \"undefined\" || !(\"IntersectionObserver\" in window)) {\r\n return;\r\n }\r\n\r\n const resolvedRoot =\r\n root && typeof root === \"object\" && \"current\" in root\r\n ? (root as RefObject<Element | null>).current\r\n : (root as Element | null);\r\n\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n const [entry] = entries;\r\n if (!entry) return;\r\n\r\n const intersecting = entry.isIntersecting;\r\n setIsIntersecting(intersecting);\r\n\r\n if (intersecting) {\r\n triggerLoadMore();\r\n }\r\n },\r\n {\r\n root: resolvedRoot,\r\n rootMargin,\r\n threshold,\r\n }\r\n );\r\n\r\n observer.observe(sentinelNode);\r\n\r\n return () => {\r\n observer.disconnect();\r\n setIsIntersecting(false);\r\n };\r\n }, [sentinelNode, root, rootMargin, threshold, disabled, hasMore, triggerLoadMore]);\r\n\r\n // Khi tải xong (isLoading = false), nếu sentinel vẫn đang nằm trong tầm nhìn thì kích hoạt tải tiếp\r\n useEffect(() => {\r\n if (canTrigger && isIntersecting) {\r\n triggerLoadMore();\r\n }\r\n }, [canTrigger, isIntersecting, triggerLoadMore]);\r\n\r\n return {\r\n sentinelRef,\r\n isIntersecting,\r\n };\r\n}\r\n\r\nexport default useInfiniteScroll;\r\n","import { SkeletonObjectFit, SkeletonRadius, SkeletonVariant } from \"./types\";\n\n/**\n * Animation class theo variant.\n * Variant 'wave' dùng class CSS tùy chỉnh `.skeleton-wave` được định nghĩa trong styles.css.\n */\nexport const variantConfig: Record<SkeletonVariant, string> = {\n pulse: \"animate-pulse\",\n wave: \"skeleton-wave\",\n none: \"\",\n};\n\n/**\n * Bo góc theo radius.\n * Chỉ áp dụng khi shape === 'rectangle'.\n */\nexport const radiusConfig: Record<SkeletonRadius, string> = {\n none: \"rounded-none\",\n sm: \"rounded-sm\",\n md: \"rounded-md\",\n lg: \"rounded-lg\",\n xl: \"rounded-xl\",\n full: \"rounded-full\",\n};\n\n/**\n * Kiểu căn chỉnh ảnh (object-fit) cho LoadingImage.\n */\nexport const objectFitConfig: Record<SkeletonObjectFit, string> = {\n cover: \"object-cover\",\n contain: \"object-contain\",\n fill: \"object-fill\",\n none: \"object-none\",\n \"scale-down\": \"object-scale-down\",\n};\n","export const toStyle = (val: string | number | undefined): string | number | undefined =>\r\n typeof val === \"number\" ? val : val;\r\n\r\nexport const getSourceKey = (s: unknown): string | unknown => {\r\n if (!s) return \"\";\r\n if (typeof s === \"string\") return s;\r\n if (typeof File !== \"undefined\" && s instanceof File) {\r\n return `${s.name}-${s.size}-${s.lastModified}`;\r\n }\r\n if (typeof Blob !== \"undefined\" && s instanceof Blob) {\r\n return `${s.size}-${s.type}`;\r\n }\r\n if (typeof s === \"object\" && \"src\" in s && typeof (s as { src: unknown }).src === \"string\") {\r\n return (s as { src: string }).src;\r\n }\r\n return s;\r\n};","import { CSSProperties } from \"react\";\nimport { SkeletonProps } from \"./types\";\nimport { variantConfig, radiusConfig } from \"./constants\";\nimport { getSafeConfig } from \"@/utils/function\";\nimport { toStyle } from \"./utils\";\n\nexport default function Skeleton({\n ref,\n variant = \"pulse\",\n shape = \"rectangle\",\n radius = \"md\",\n width,\n height = \"1rem\",\n lines = 1,\n gap = \"0.5rem\",\n className = \"\",\n style,\n ...props\n}: SkeletonProps) {\n const animationClass = getSafeConfig(variant, variantConfig, \"pulse\");\n\n // Circle: bỏ qua radius prop, luôn full-round\n const roundedClass = shape === \"circle\" ? \"rounded-full\" : getSafeConfig(radius, radiusConfig, \"md\");\n\n const baseStyle: CSSProperties = {\n width: toStyle(width),\n height: toStyle(height),\n ...style,\n };\n\n const itemClass = [\"bg-neutral-200\", animationClass, roundedClass].filter(Boolean).join(\" \");\n\n if (lines > 1) {\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading...\"\n className={[\"flex flex-col\", className].filter(Boolean).join(\" \")}\n style={{ gap: toStyle(gap), ...style }}\n {...props}\n >\n {Array.from({ length: lines }).map((_, i) => {\n const isLast = i === lines - 1;\n return (\n <div\n key={i}\n className={itemClass}\n style={{\n width: isLast ? \"60%\" : (toStyle(width) ?? \"100%\"),\n height: toStyle(height),\n }}\n />\n );\n })}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading...\"\n className={[itemClass, className].filter(Boolean).join(\" \")}\n style={baseStyle}\n {...props}\n />\n );\n}\n","import type { TableAlign, TableSize, TableVariant } from \"./types\";\r\n\r\nexport const tableSizeConfig: Record<\r\n TableSize,\r\n {\r\n head: string;\r\n cell: string;\r\n text: string;\r\n icon: string;\r\n }\r\n> = {\r\n sm: {\r\n head: \"px-3 py-2 text-xs font-semibold\",\r\n cell: \"px-3 py-2 text-xs\",\r\n text: \"text-xs\",\r\n icon: \"w-3.5 h-3.5\",\r\n },\r\n md: {\r\n head: \"px-4 py-3 text-sm font-semibold\",\r\n cell: \"px-4 py-3 text-sm\",\r\n text: \"text-sm\",\r\n icon: \"w-4 h-4\",\r\n },\r\n lg: {\r\n head: \"px-5 py-4 text-base font-semibold\",\r\n cell: \"px-5 py-4 text-base\",\r\n text: \"text-base\",\r\n icon: \"w-5 h-5\",\r\n },\r\n};\r\n\r\nexport const tableVariantConfig: Record<\r\n TableVariant,\r\n {\r\n container: string;\r\n table: string;\r\n head: string;\r\n row: string;\r\n cell: string;\r\n }\r\n> = {\r\n default: {\r\n container: \"border border-neutral-200 shadow-xs\",\r\n table: \"border-collapse\",\r\n head: \"bg-neutral-50/80 border-b border-neutral-200 text-neutral-600\",\r\n row: \"border-b border-neutral-200/80 hover:bg-neutral-50/50 transition-colors\",\r\n cell: \"text-neutral-700\",\r\n },\r\n striped: {\r\n container: \"border border-neutral-200 shadow-xs\",\r\n table: \"border-collapse\",\r\n head: \"bg-neutral-100/80 border-b border-neutral-200 text-neutral-700\",\r\n row: \"border-b border-neutral-200/80 even:bg-neutral-50/60 hover:bg-neutral-100/40 transition-colors\",\r\n cell: \"text-neutral-700\",\r\n },\r\n bordered: {\r\n container: \"border border-neutral-200 shadow-xs\",\r\n table: \"border-collapse border border-neutral-200\",\r\n head: \"bg-neutral-50 border border-neutral-200 text-neutral-700\",\r\n row: \"border-b border-neutral-200 hover:bg-neutral-50/50 transition-colors\",\r\n cell: \"border border-neutral-200 text-neutral-700\",\r\n },\r\n};\r\n\r\nexport const tableAlignConfig: Record<TableAlign, string> = {\r\n left: \"text-left justify-start\",\r\n center: \"text-center justify-center\",\r\n right: \"text-right justify-end\",\r\n};\r\n\r\nexport const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];\r\nexport const DEFAULT_PAGE_SIZE = 10;\r\n"],"mappings":"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","names":["rankItem","compareItems","getSafeConfig","key","configMap","fallbackKey","rankAndFilterItems","items","query","fields","keyword","fieldsArray","accessors","field","item","res","record","val","dataVal","ranked","info","a","b","r","jsx","jsxs","Spinner","width","height","className","props","jsx","CloseIcon","width","height","className","props","jsx","jsxs","InfoCircleIcon","width","height","className","props","jsx","jsxs","CheckCircleIcon","width","height","className","props","jsx","jsxs","AlertTriangleIcon","width","height","className","props","jsx","jsxs","AlertCircleIcon","width","height","className","props","sizeConfig","radiusConfig","variantColorConfig","iconColorConfig","defaultIcons","InfoCircleIcon","CheckCircleIcon","AlertTriangleIcon","AlertCircleIcon","jsx","jsxs","Alert","size","variant","color","radius","title","description","icon","action","closable","onClose","closeAriaLabel","banner","titleClassName","descriptionClassName","actionClassName","iconClassName","closeButtonClassName","className","children","role","ref","props","currentSize","getSafeConfig","sizeConfig","roundedClass","radiusConfig","variantStyles","variantColorConfig","iconColorStyle","iconColorConfig","isClosable","handleClose","e","activeRole","ariaLive","bodyContent","hasBody","renderIcon","IconComponent","defaultIcons","containerClasses","CloseIcon","DEFAULT_TOAST_DURATION","SonnerToaster","jsx","Toaster","position","visibleToasts","expand","duration","closeButton","className","toastOptions","props","SonnerToaster","isValidElement","sonnerToast","jsx","showToast","title","color","variant","description","options","customId","duration","position","onDismiss","onAutoClose","alertProps","sonnerToast","id","Alert","isToastMessageObject","value","isValidElement","extractMessageDetails","result","fallbackColor","fallbackVariant","baseToast","opts","arg","Spinner","promiseInput","promise","loadingTitle","loadingDesc","toastId","data","rawSuccess","successTitle","successDesc","successColor","successVariant","err","rawError","errorTitle","errorDesc","errorColor","errorVariant","toast","useCallback","useEffect","useRef","useState","useInfiniteScroll","onLoadMore","hasMore","isLoading","disabled","rootMargin","threshold","root","isIntersecting","setIsIntersecting","sentinelNode","setSentinelNode","isTriggeringRef","canTrigger","triggerLoadMore","result","sentinelRef","node","resolvedRoot","observer","entries","entry","intersecting","variantConfig","radiusConfig","objectFitConfig","toStyle","val","getSourceKey","s","jsx","Skeleton","ref","variant","shape","radius","width","height","lines","gap","className","style","props","animationClass","getSafeConfig","variantConfig","roundedClass","radiusConfig","baseStyle","toStyle","itemClass","_","i","isLast","tableSizeConfig","tableVariantConfig","tableAlignConfig","DEFAULT_PAGE_SIZE_OPTIONS","DEFAULT_PAGE_SIZE"]}