@openway/ui 1.0.2 → 1.0.3
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.
- package/LICENSE +21 -21
- package/dist/chunk-XM7AABEU.js +3 -0
- package/dist/chunk-XM7AABEU.js.map +1 -0
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -74
- package/dist/index.d.ts +158 -74
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +13 -5
- package/dist/query.d.ts +13 -5
- package/dist/query.js +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.css +424 -0
- package/dist/{useInfiniteScroll-D9AW6cQV.d.cts → useInfiniteScroll-C5t7bvLu.d.cts} +40 -10
- package/dist/{useInfiniteScroll-D9AW6cQV.d.ts → useInfiniteScroll-C5t7bvLu.d.ts} +40 -10
- package/docs/AGENTS.md +0 -2
- package/docs/README.md +0 -1
- package/docs/components/carousel.md +5 -5
- package/docs/components/input.md +0 -6
- package/docs/components/select.md +2 -2
- package/docs/components/slider.md +2 -2
- package/docs/components/table.md +114 -1
- package/docs/hooks/useInfiniteScroll.md +1 -1
- package/docs/hooks/useMutationApp.md +242 -242
- package/docs/hooks/useTableQuery.md +5 -0
- package/package.json +3 -2
- package/dist/chunk-X4LIYOS5.js +0 -3
- package/dist/chunk-X4LIYOS5.js.map +0 -1
package/dist/styles.css
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--color-primary-50: #f4f8fb;
|
|
5
|
+
--color-primary-100: #e9f0f5;
|
|
6
|
+
--color-primary-200: #cedfe9;
|
|
7
|
+
--color-primary-300: #a2c4d7;
|
|
8
|
+
--color-primary-400: #6ba1bd;
|
|
9
|
+
--color-primary-500: #4e8aa9;
|
|
10
|
+
--color-primary-600: #3c6f8d;
|
|
11
|
+
--color-primary-700: #325972;
|
|
12
|
+
--color-primary-800: #2c4c60;
|
|
13
|
+
--color-primary-900: #294151;
|
|
14
|
+
--color-primary-950: #1b2a36;
|
|
15
|
+
|
|
16
|
+
--color-secondary-50: #faf6ec;
|
|
17
|
+
--color-secondary-100: #f4e8cd;
|
|
18
|
+
--color-secondary-200: #ead09e;
|
|
19
|
+
--color-secondary-300: #deb066;
|
|
20
|
+
--color-secondary-400: #d4943e;
|
|
21
|
+
--color-secondary-500: #c47e2e;
|
|
22
|
+
--color-secondary-600: #a96125;
|
|
23
|
+
--color-secondary-700: #874621;
|
|
24
|
+
--color-secondary-800: #713a22;
|
|
25
|
+
--color-secondary-900: #613222;
|
|
26
|
+
--color-secondary-950: #381810;
|
|
27
|
+
|
|
28
|
+
--color-neutral-white: #fcfdfd;
|
|
29
|
+
--color-neutral-50: #f9fbfb;
|
|
30
|
+
--color-neutral-100: #f3f5f6;
|
|
31
|
+
--color-neutral-200: #e5e9eb;
|
|
32
|
+
--color-neutral-300: #d2d8da;
|
|
33
|
+
--color-neutral-400: #9daaae;
|
|
34
|
+
--color-neutral-500: #7d8b90;
|
|
35
|
+
--color-neutral-600: #4c5e62;
|
|
36
|
+
--color-neutral-700: #394a4f;
|
|
37
|
+
--color-neutral-800: #203236;
|
|
38
|
+
--color-neutral-900: #122026;
|
|
39
|
+
--color-neutral-950: #040d11;
|
|
40
|
+
--color-neutral-black: #020709;
|
|
41
|
+
|
|
42
|
+
--color-error-50: #fef2f2;
|
|
43
|
+
--color-error-100: #fee2e2;
|
|
44
|
+
--color-error-200: #fecaca;
|
|
45
|
+
--color-error-300: #fca5a5;
|
|
46
|
+
--color-error-400: #f87171;
|
|
47
|
+
--color-error-500: #ef4444;
|
|
48
|
+
--color-error-600: #dc2626;
|
|
49
|
+
--color-error-700: #b91c1c;
|
|
50
|
+
--color-error-800: #991b1b;
|
|
51
|
+
--color-error-900: #7f1d1d;
|
|
52
|
+
--color-error-950: #450a0a;
|
|
53
|
+
|
|
54
|
+
--color-success-50: #ecfdf6;
|
|
55
|
+
--color-success-100: #d2f9e6;
|
|
56
|
+
--color-success-200: #a9f1d2;
|
|
57
|
+
--color-success-300: #71e4ba;
|
|
58
|
+
--color-success-400: #38cf9d;
|
|
59
|
+
--color-success-500: #12a176;
|
|
60
|
+
--color-success-600: #09926c;
|
|
61
|
+
--color-success-700: #077559;
|
|
62
|
+
--color-success-800: #085d48;
|
|
63
|
+
--color-success-900: #084c3c;
|
|
64
|
+
--color-success-950: #032b23;
|
|
65
|
+
|
|
66
|
+
--color-warning-50: #fefce8;
|
|
67
|
+
--color-warning-100: #fef9c3;
|
|
68
|
+
--color-warning-200: #fef08a;
|
|
69
|
+
--color-warning-300: #fde047;
|
|
70
|
+
--color-warning-400: #facc15;
|
|
71
|
+
--color-warning-500: #eab308;
|
|
72
|
+
--color-warning-600: #ca8a04;
|
|
73
|
+
--color-warning-700: #a16207;
|
|
74
|
+
--color-warning-800: #854d0e;
|
|
75
|
+
--color-warning-900: #713f12;
|
|
76
|
+
--color-warning-950: #422006;
|
|
77
|
+
|
|
78
|
+
--color-info-50: #f0fafb;
|
|
79
|
+
--color-info-100: #d9f1f4;
|
|
80
|
+
--color-info-200: #b7e3ea;
|
|
81
|
+
--color-info-300: #85cedb;
|
|
82
|
+
--color-info-400: #4cafc4;
|
|
83
|
+
--color-info-500: #349eb6;
|
|
84
|
+
--color-info-600: #2b788f;
|
|
85
|
+
--color-info-700: #296275;
|
|
86
|
+
--color-info-800: #295161;
|
|
87
|
+
--color-info-900: #264553;
|
|
88
|
+
--color-info-950: #142c38;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keyframes errorSlideIn {
|
|
92
|
+
0% {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
transform: translateY(-4px);
|
|
95
|
+
}
|
|
96
|
+
100% {
|
|
97
|
+
opacity: 1;
|
|
98
|
+
transform: translateY(0);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@keyframes errorSlideOut {
|
|
103
|
+
0% {
|
|
104
|
+
opacity: 1;
|
|
105
|
+
transform: translateY(0);
|
|
106
|
+
}
|
|
107
|
+
100% {
|
|
108
|
+
opacity: 0;
|
|
109
|
+
transform: translateY(-4px);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.animate-error-in {
|
|
114
|
+
animation: errorSlideIn 0.35s cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.animate-error-out {
|
|
118
|
+
animation: errorSlideOut 0.25s cubic-bezier(0.7, 0, 0.84, 0) forwards;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@keyframes alertSlideInDown {
|
|
122
|
+
0% {
|
|
123
|
+
opacity: 0;
|
|
124
|
+
transform: translateY(-16px) scale(0.96);
|
|
125
|
+
}
|
|
126
|
+
100% {
|
|
127
|
+
opacity: 1;
|
|
128
|
+
transform: translateY(0) scale(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@keyframes alertSlideInUp {
|
|
133
|
+
0% {
|
|
134
|
+
opacity: 0;
|
|
135
|
+
transform: translateY(16px) scale(0.96);
|
|
136
|
+
}
|
|
137
|
+
100% {
|
|
138
|
+
opacity: 1;
|
|
139
|
+
transform: translateY(0) scale(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@keyframes alertSlideOut {
|
|
144
|
+
0% {
|
|
145
|
+
opacity: 1;
|
|
146
|
+
transform: scale(1);
|
|
147
|
+
}
|
|
148
|
+
100% {
|
|
149
|
+
opacity: 0;
|
|
150
|
+
transform: scale(0.95);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@keyframes alertSlideOutTop {
|
|
155
|
+
0% {
|
|
156
|
+
opacity: 1;
|
|
157
|
+
transform: translateY(0) scale(1);
|
|
158
|
+
max-height: 200px;
|
|
159
|
+
margin-bottom: 8px;
|
|
160
|
+
}
|
|
161
|
+
100% {
|
|
162
|
+
opacity: 0;
|
|
163
|
+
transform: translateY(-16px) scale(0.96);
|
|
164
|
+
max-height: 0;
|
|
165
|
+
margin-bottom: 0;
|
|
166
|
+
padding-top: 0;
|
|
167
|
+
padding-bottom: 0;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@keyframes alertSlideOutBottom {
|
|
172
|
+
0% {
|
|
173
|
+
opacity: 1;
|
|
174
|
+
transform: translateY(0) scale(1);
|
|
175
|
+
max-height: 200px;
|
|
176
|
+
margin-bottom: 8px;
|
|
177
|
+
}
|
|
178
|
+
100% {
|
|
179
|
+
opacity: 0;
|
|
180
|
+
transform: translateY(16px) scale(0.96);
|
|
181
|
+
max-height: 0;
|
|
182
|
+
margin-bottom: 0;
|
|
183
|
+
padding-top: 0;
|
|
184
|
+
padding-bottom: 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.animate-alert-in-top {
|
|
189
|
+
animation: alertSlideInDown 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.animate-alert-in-bottom {
|
|
193
|
+
animation: alertSlideInUp 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.animate-alert-out-top {
|
|
197
|
+
animation: alertSlideOutTop 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.animate-alert-out-bottom {
|
|
201
|
+
animation: alertSlideOutBottom 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.animate-alert-out {
|
|
205
|
+
animation: alertSlideOutTop 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Confirm Dialog Animations */
|
|
209
|
+
@keyframes confirmBackdropFadeIn {
|
|
210
|
+
0% {
|
|
211
|
+
opacity: 0;
|
|
212
|
+
}
|
|
213
|
+
100% {
|
|
214
|
+
opacity: 1;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@keyframes confirmBackdropFadeOut {
|
|
219
|
+
0% {
|
|
220
|
+
opacity: 1;
|
|
221
|
+
}
|
|
222
|
+
100% {
|
|
223
|
+
opacity: 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@keyframes confirmZoomIn {
|
|
228
|
+
0% {
|
|
229
|
+
opacity: 0;
|
|
230
|
+
transform: scale(0.94) translateY(6px);
|
|
231
|
+
}
|
|
232
|
+
100% {
|
|
233
|
+
opacity: 1;
|
|
234
|
+
transform: scale(1) translateY(0);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@keyframes confirmZoomOut {
|
|
239
|
+
0% {
|
|
240
|
+
opacity: 1;
|
|
241
|
+
transform: scale(1) translateY(0);
|
|
242
|
+
}
|
|
243
|
+
100% {
|
|
244
|
+
opacity: 0;
|
|
245
|
+
transform: scale(0.94) translateY(6px);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.animate-confirm-backdrop-in {
|
|
250
|
+
animation: confirmBackdropFadeIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.animate-confirm-backdrop-out {
|
|
254
|
+
animation: confirmBackdropFadeOut 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.animate-confirm-in {
|
|
258
|
+
animation: confirmZoomIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.animate-confirm-out {
|
|
262
|
+
animation: confirmZoomOut 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Modal Dialog Animations */
|
|
266
|
+
@keyframes modalBackdropFadeIn {
|
|
267
|
+
0% {
|
|
268
|
+
opacity: 0;
|
|
269
|
+
}
|
|
270
|
+
100% {
|
|
271
|
+
opacity: 1;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@keyframes modalBackdropFadeOut {
|
|
276
|
+
0% {
|
|
277
|
+
opacity: 1;
|
|
278
|
+
}
|
|
279
|
+
100% {
|
|
280
|
+
opacity: 0;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@keyframes modalZoomIn {
|
|
285
|
+
0% {
|
|
286
|
+
opacity: 0;
|
|
287
|
+
transform: scale(0.95) translateY(6px);
|
|
288
|
+
}
|
|
289
|
+
100% {
|
|
290
|
+
opacity: 1;
|
|
291
|
+
transform: scale(1) translateY(0);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@keyframes modalZoomOut {
|
|
296
|
+
0% {
|
|
297
|
+
opacity: 1;
|
|
298
|
+
transform: scale(1) translateY(0);
|
|
299
|
+
}
|
|
300
|
+
100% {
|
|
301
|
+
opacity: 0;
|
|
302
|
+
transform: scale(0.95) translateY(6px);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.animate-modal-backdrop-in {
|
|
307
|
+
animation: modalBackdropFadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.animate-modal-backdrop-out {
|
|
311
|
+
animation: modalBackdropFadeOut 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.animate-modal-in {
|
|
315
|
+
animation: modalZoomIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.animate-modal-out {
|
|
319
|
+
animation: modalZoomOut 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Reset native HTML dialog backdrop to prevent browser grey flash */
|
|
323
|
+
dialog::backdrop {
|
|
324
|
+
background: transparent;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Custom Thin Modern Scrollbars for UI components */
|
|
328
|
+
.ui-scrollbar {
|
|
329
|
+
scrollbar-width: thin;
|
|
330
|
+
scrollbar-color: rgba(156, 163, 175, 0.4) transparent;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.ui-scrollbar::-webkit-scrollbar {
|
|
334
|
+
width: 5px;
|
|
335
|
+
height: 5px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.ui-scrollbar::-webkit-scrollbar-track {
|
|
339
|
+
background: transparent;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.ui-scrollbar::-webkit-scrollbar-thumb {
|
|
343
|
+
background-color: rgba(156, 163, 175, 0.4);
|
|
344
|
+
border-radius: 9999px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.ui-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
348
|
+
background-color: rgba(107, 114, 128, 0.7);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* ==================== Skeleton Wave Animation ==================== */
|
|
352
|
+
@keyframes skeleton-shimmer {
|
|
353
|
+
0% {
|
|
354
|
+
background-position: -200% -200%;
|
|
355
|
+
}
|
|
356
|
+
100% {
|
|
357
|
+
background-position: 200% 200%;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.skeleton-wave {
|
|
362
|
+
background: linear-gradient(
|
|
363
|
+
135deg,
|
|
364
|
+
var(--color-neutral-200) 25%,
|
|
365
|
+
var(--color-neutral-100) 50%,
|
|
366
|
+
var(--color-neutral-200) 75%
|
|
367
|
+
);
|
|
368
|
+
background-size: 200% 200%;
|
|
369
|
+
animation: skeleton-shimmer 2.2s ease-in-out infinite;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* ==================== Table Expanding Animations ==================== */
|
|
373
|
+
@keyframes tableRowExpand {
|
|
374
|
+
0% {
|
|
375
|
+
grid-template-rows: 0fr;
|
|
376
|
+
opacity: 0;
|
|
377
|
+
}
|
|
378
|
+
100% {
|
|
379
|
+
grid-template-rows: 1fr;
|
|
380
|
+
opacity: 1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
@keyframes tableRowContentSlideDown {
|
|
385
|
+
0% {
|
|
386
|
+
opacity: 0;
|
|
387
|
+
transform: translateY(-8px);
|
|
388
|
+
}
|
|
389
|
+
100% {
|
|
390
|
+
opacity: 1;
|
|
391
|
+
transform: translateY(0);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.animate-table-expand-wrapper {
|
|
396
|
+
display: grid;
|
|
397
|
+
grid-template-rows: 1fr;
|
|
398
|
+
animation: tableRowExpand 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.animate-table-expand-content {
|
|
402
|
+
overflow: hidden;
|
|
403
|
+
animation: tableRowContentSlideDown 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@keyframes tableSubrowFadeIn {
|
|
407
|
+
0% {
|
|
408
|
+
opacity: 0;
|
|
409
|
+
transform: translateY(-4px);
|
|
410
|
+
background-color: var(--color-primary-50);
|
|
411
|
+
}
|
|
412
|
+
50% {
|
|
413
|
+
opacity: 1;
|
|
414
|
+
background-color: var(--color-primary-50);
|
|
415
|
+
}
|
|
416
|
+
100% {
|
|
417
|
+
transform: translateY(0);
|
|
418
|
+
background-color: transparent;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.animate-table-subrow-in {
|
|
423
|
+
animation: tableSubrowFadeIn 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
424
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes, Ref, ReactNode, RefObject } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ToasterProps as ToasterProps$1, ToastT } from 'sonner';
|
|
3
3
|
import { Placement } from '@floating-ui/react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -394,9 +394,43 @@ interface SelectFilterField<TValue = unknown> {
|
|
|
394
394
|
onChange: (val: TValue) => void;
|
|
395
395
|
}) => ReactNode;
|
|
396
396
|
}
|
|
397
|
+
interface SelectConfig {
|
|
398
|
+
/**
|
|
399
|
+
* Đánh dấu trường bắt buộc nhập (hiển thị dấu * đỏ cạnh label)
|
|
400
|
+
* @default false
|
|
401
|
+
*/
|
|
402
|
+
isRequired?: boolean;
|
|
403
|
+
/**
|
|
404
|
+
* Trạng thái báo lỗi (viền đỏ)
|
|
405
|
+
* @default false
|
|
406
|
+
*/
|
|
407
|
+
isInvalid?: boolean;
|
|
408
|
+
/**
|
|
409
|
+
* Trạng thái đang tải dữ liệu
|
|
410
|
+
* @default false
|
|
411
|
+
*/
|
|
412
|
+
isLoading?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Hiển thị biểu tượng xoay spinner khi đang ở trạng thái loading
|
|
415
|
+
* @default false
|
|
416
|
+
*/
|
|
417
|
+
showSpinner?: boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Hiển thị nút xóa nhanh nội dung khi select có giá trị
|
|
420
|
+
* @default false
|
|
421
|
+
*/
|
|
422
|
+
isClearable?: boolean;
|
|
423
|
+
/**
|
|
424
|
+
* Mở rộng chiều rộng 100% của container chứa
|
|
425
|
+
* @default true
|
|
426
|
+
*/
|
|
427
|
+
isFullWidth?: boolean;
|
|
428
|
+
}
|
|
397
429
|
interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unknown> = Record<string, unknown>> {
|
|
398
430
|
/** Ref chuyển tiếp tới root wrapper element */
|
|
399
431
|
ref?: Ref<HTMLDivElement>;
|
|
432
|
+
/** Cấu hình tập trung các cờ trạng thái / tính năng */
|
|
433
|
+
config?: SelectConfig;
|
|
400
434
|
/** Kích thước: xs, sm, md, lg, xl. Mặc định 'md' */
|
|
401
435
|
size?: SelectSize;
|
|
402
436
|
/** Biến thể giao diện: outline, filled, ghost, other. Mặc định 'outline' */
|
|
@@ -411,7 +445,7 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
411
445
|
id?: string;
|
|
412
446
|
/** Nhãn tiêu đề của Select */
|
|
413
447
|
label?: ReactNode;
|
|
414
|
-
/** Vị trí nhãn: top, left, floating. Mặc định '
|
|
448
|
+
/** Vị trí nhãn: top, left, floating. Mặc định 'floating' */
|
|
415
449
|
labelPlacement?: LabelPlacement;
|
|
416
450
|
/** ClassName tùy biến cho nhãn */
|
|
417
451
|
labelClassName?: string;
|
|
@@ -421,10 +455,6 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
421
455
|
helperText?: ReactNode;
|
|
422
456
|
/** Thông báo lỗi khi không hợp lệ */
|
|
423
457
|
errorMessage?: ReactNode;
|
|
424
|
-
/** Trạng thái không hợp lệ */
|
|
425
|
-
isInvalid?: boolean;
|
|
426
|
-
/** Bắt buộc chọn */
|
|
427
|
-
isRequired?: boolean;
|
|
428
458
|
/** Vô hiệu hóa Select */
|
|
429
459
|
isDisabled?: boolean;
|
|
430
460
|
/** Chỉ đọc, không cho phép thay đổi */
|
|
@@ -471,16 +501,14 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
471
501
|
debounceMs?: number;
|
|
472
502
|
/** Hàm custom predicate lọc options ở chế độ client */
|
|
473
503
|
filterFn?: (option: SelectOptionItem<TData>, query: string, filters: TFilters) => boolean;
|
|
474
|
-
/** Trạng thái đang tải dữ liệu */
|
|
475
|
-
isLoading?: boolean;
|
|
476
504
|
/** Văn bản hiển thị khi không có dữ liệu */
|
|
477
505
|
emptyText?: ReactNode;
|
|
478
506
|
/** Props tùy biến chuyển tiếp đến component Empty */
|
|
479
507
|
emptyProps?: Partial<EmptyProps>;
|
|
480
|
-
/** Hiển thị nút xoá nhanh toàn bộ giá trị đã chọn */
|
|
481
|
-
clearable?: boolean;
|
|
482
508
|
/** Render menu qua Portal để tránh bị che bởi overflow hidden. Mặc định true */
|
|
483
509
|
portal?: boolean;
|
|
510
|
+
/** Container để gắn portal menu vào (mặc định document.body, tự động nhận diện `<dialog>` nếu Select nằm trong Modal/Confirm) */
|
|
511
|
+
portalRoot?: HTMLElement | null | React.RefObject<HTMLElement | null>;
|
|
484
512
|
/** Vị trí hiển thị của popup dropdown. Mặc định 'bottom-start' */
|
|
485
513
|
placement?: Placement;
|
|
486
514
|
/** Chiều cao tối đa của vùng cuộn menu. Mặc định 280px */
|
|
@@ -504,6 +532,8 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
504
532
|
triggerClassName?: string;
|
|
505
533
|
/** ClassName cho popup dropdown menu */
|
|
506
534
|
menuClassName?: string;
|
|
535
|
+
/** ClassName cho phần thông báo helper/error text */
|
|
536
|
+
helperClassName?: string;
|
|
507
537
|
}
|
|
508
538
|
/** Props cho component Select (Single Select) */
|
|
509
539
|
interface SelectProps<TData = unknown, TFilters extends Record<string, unknown> = Record<string, unknown>> extends BaseSelectProps<TData, TFilters> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes, Ref, ReactNode, RefObject } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ToasterProps as ToasterProps$1, ToastT } from 'sonner';
|
|
3
3
|
import { Placement } from '@floating-ui/react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -394,9 +394,43 @@ interface SelectFilterField<TValue = unknown> {
|
|
|
394
394
|
onChange: (val: TValue) => void;
|
|
395
395
|
}) => ReactNode;
|
|
396
396
|
}
|
|
397
|
+
interface SelectConfig {
|
|
398
|
+
/**
|
|
399
|
+
* Đánh dấu trường bắt buộc nhập (hiển thị dấu * đỏ cạnh label)
|
|
400
|
+
* @default false
|
|
401
|
+
*/
|
|
402
|
+
isRequired?: boolean;
|
|
403
|
+
/**
|
|
404
|
+
* Trạng thái báo lỗi (viền đỏ)
|
|
405
|
+
* @default false
|
|
406
|
+
*/
|
|
407
|
+
isInvalid?: boolean;
|
|
408
|
+
/**
|
|
409
|
+
* Trạng thái đang tải dữ liệu
|
|
410
|
+
* @default false
|
|
411
|
+
*/
|
|
412
|
+
isLoading?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Hiển thị biểu tượng xoay spinner khi đang ở trạng thái loading
|
|
415
|
+
* @default false
|
|
416
|
+
*/
|
|
417
|
+
showSpinner?: boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Hiển thị nút xóa nhanh nội dung khi select có giá trị
|
|
420
|
+
* @default false
|
|
421
|
+
*/
|
|
422
|
+
isClearable?: boolean;
|
|
423
|
+
/**
|
|
424
|
+
* Mở rộng chiều rộng 100% của container chứa
|
|
425
|
+
* @default true
|
|
426
|
+
*/
|
|
427
|
+
isFullWidth?: boolean;
|
|
428
|
+
}
|
|
397
429
|
interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unknown> = Record<string, unknown>> {
|
|
398
430
|
/** Ref chuyển tiếp tới root wrapper element */
|
|
399
431
|
ref?: Ref<HTMLDivElement>;
|
|
432
|
+
/** Cấu hình tập trung các cờ trạng thái / tính năng */
|
|
433
|
+
config?: SelectConfig;
|
|
400
434
|
/** Kích thước: xs, sm, md, lg, xl. Mặc định 'md' */
|
|
401
435
|
size?: SelectSize;
|
|
402
436
|
/** Biến thể giao diện: outline, filled, ghost, other. Mặc định 'outline' */
|
|
@@ -411,7 +445,7 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
411
445
|
id?: string;
|
|
412
446
|
/** Nhãn tiêu đề của Select */
|
|
413
447
|
label?: ReactNode;
|
|
414
|
-
/** Vị trí nhãn: top, left, floating. Mặc định '
|
|
448
|
+
/** Vị trí nhãn: top, left, floating. Mặc định 'floating' */
|
|
415
449
|
labelPlacement?: LabelPlacement;
|
|
416
450
|
/** ClassName tùy biến cho nhãn */
|
|
417
451
|
labelClassName?: string;
|
|
@@ -421,10 +455,6 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
421
455
|
helperText?: ReactNode;
|
|
422
456
|
/** Thông báo lỗi khi không hợp lệ */
|
|
423
457
|
errorMessage?: ReactNode;
|
|
424
|
-
/** Trạng thái không hợp lệ */
|
|
425
|
-
isInvalid?: boolean;
|
|
426
|
-
/** Bắt buộc chọn */
|
|
427
|
-
isRequired?: boolean;
|
|
428
458
|
/** Vô hiệu hóa Select */
|
|
429
459
|
isDisabled?: boolean;
|
|
430
460
|
/** Chỉ đọc, không cho phép thay đổi */
|
|
@@ -471,16 +501,14 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
471
501
|
debounceMs?: number;
|
|
472
502
|
/** Hàm custom predicate lọc options ở chế độ client */
|
|
473
503
|
filterFn?: (option: SelectOptionItem<TData>, query: string, filters: TFilters) => boolean;
|
|
474
|
-
/** Trạng thái đang tải dữ liệu */
|
|
475
|
-
isLoading?: boolean;
|
|
476
504
|
/** Văn bản hiển thị khi không có dữ liệu */
|
|
477
505
|
emptyText?: ReactNode;
|
|
478
506
|
/** Props tùy biến chuyển tiếp đến component Empty */
|
|
479
507
|
emptyProps?: Partial<EmptyProps>;
|
|
480
|
-
/** Hiển thị nút xoá nhanh toàn bộ giá trị đã chọn */
|
|
481
|
-
clearable?: boolean;
|
|
482
508
|
/** Render menu qua Portal để tránh bị che bởi overflow hidden. Mặc định true */
|
|
483
509
|
portal?: boolean;
|
|
510
|
+
/** Container để gắn portal menu vào (mặc định document.body, tự động nhận diện `<dialog>` nếu Select nằm trong Modal/Confirm) */
|
|
511
|
+
portalRoot?: HTMLElement | null | React.RefObject<HTMLElement | null>;
|
|
484
512
|
/** Vị trí hiển thị của popup dropdown. Mặc định 'bottom-start' */
|
|
485
513
|
placement?: Placement;
|
|
486
514
|
/** Chiều cao tối đa của vùng cuộn menu. Mặc định 280px */
|
|
@@ -504,6 +532,8 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
504
532
|
triggerClassName?: string;
|
|
505
533
|
/** ClassName cho popup dropdown menu */
|
|
506
534
|
menuClassName?: string;
|
|
535
|
+
/** ClassName cho phần thông báo helper/error text */
|
|
536
|
+
helperClassName?: string;
|
|
507
537
|
}
|
|
508
538
|
/** Props cho component Select (Single Select) */
|
|
509
539
|
interface SelectProps<TData = unknown, TFilters extends Record<string, unknown> = Record<string, unknown>> extends BaseSelectProps<TData, TFilters> {
|
package/docs/AGENTS.md
CHANGED
|
@@ -131,8 +131,6 @@ Tất cả tài liệu chi tiết của từng component được lưu trữ t
|
|
|
131
131
|
- `xs` (24px), `sm` (32px), `md` (40px - chuẩn cho form input/nút), `lg` (48px), `xl` (56px).
|
|
132
132
|
3. **Bo góc (`radius`)**:
|
|
133
133
|
- `none` (0px), `sm` (4px), `md` (6px), `lg` (8px - chuẩn mặc định), `xl` (12px), `full` (tròn).
|
|
134
|
-
4. **Hỗ trợ chế độ Tối (Dark Mode)**:
|
|
135
|
-
- Tất cả component tự động thích ứng qua class Tailwind `dark:`.
|
|
136
134
|
|
|
137
135
|
---
|
|
138
136
|
|
package/docs/README.md
CHANGED
|
@@ -147,4 +147,3 @@ Tài liệu chi tiết của các hooks được lưu trữ tại thư mục [`h
|
|
|
147
147
|
1. **Zero `any`**: 100% các component và hook đều được khai báo TypeScript chặt chẽ, type-safe.
|
|
148
148
|
2. **5 Kích thước Chuẩn (`size`)**: `xs` (24px), `sm` (32px), `md` (40px - chuẩn form), `lg` (48px), `xl` (56px).
|
|
149
149
|
3. **Độ Bo Góc Chuẩn (`radius`)**: `none` (0px), `sm` (4px), `md` (6px), `lg` (8px - chuẩn mặc định), `xl` (12px), `full` (tròn).
|
|
150
|
-
4. **Dark Mode**: Toàn bộ component hỗ trợ tự động thích ứng qua selector `dark:`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# 🎠 Carousel Component (`@owa/ui`)
|
|
2
2
|
|
|
3
3
|
Component **Carousel** (băng chuyền / slider) hiện đại, hiệu năng cao, thiết kế chuẩn **Compound Components Pattern** (`<Carousel>`, `<CarouselContent>`, `<CarouselSlide>`, `<CarouselPrevious>`, `<CarouselNext>`, `<CarouselPagination>`), hỗ trợ **Pointer Drag/Touch Gestures**, **Autoplay thông minh**, **Infinite Looping**, **Glassmorphism Design**, **Dynamic Slide Registration**, **Safe Config Fallback** (`getSafeConfig`) và tuân thủ đầy đủ tiêu chuẩn **WAI-ARIA Accessibility**.
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ Component **Carousel** (băng chuyền / slider) hiện đại, hiệu năng cao
|
|
|
12
12
|
- **Autoplay thông minh**: Tự động chuyển slide theo chu kỳ `interval`, tự động tạm dừng khi rê chuột (`pauseOnHover`), khi focus (`pauseOnFocus`), hoặc khi đang kéo chuột/vuốt màn hình.
|
|
13
13
|
- **Vòng lặp vô hạn (Infinite Loop)**: Tự động tính toán chuyển động vòng tròn liền mạch giữa slide đầu và slide cuối.
|
|
14
14
|
- **Hiển thị nhiều slide cùng lúc (Multi-slides)**: Hỗ trợ `slidesToShow`, `slidesToScroll`, và khoảng cách gap `spacing` linh hoạt (nhận `number` theo px hoặc chuỗi CSS).
|
|
15
|
-
- **Thiết kế Kính Mờ (Frosted Glass / Glassmorphism)**: Phong cách nút và thanh phân trang kính mờ hiện đại với `backdrop-blur-md
|
|
15
|
+
- **Thiết kế Kính Mờ (Frosted Glass / Glassmorphism)**: Phong cách nút và thanh phân trang kính mờ hiện đại với `backdrop-blur-md`.
|
|
16
16
|
- **Định vị mặc định thông minh**:
|
|
17
17
|
- `CarouselPrevious`: Nút lùi kính mờ đặt sát mép trái (`absolute left-2 top-1/2 -translate-y-1/2`).
|
|
18
18
|
- `CarouselNext`: Nút tiến kính mờ đặt sát mép phải (`absolute right-2 top-1/2 -translate-y-1/2`).
|
|
@@ -151,9 +151,9 @@ export function BasicCarousel() {
|
|
|
151
151
|
<CarouselContent>
|
|
152
152
|
{products.map((product) => (
|
|
153
153
|
<CarouselSlide key={product.id}>
|
|
154
|
-
<div className="p-4 border border-neutral-200
|
|
154
|
+
<div className="p-4 border border-neutral-200 rounded-xl bg-white shadow-sm">
|
|
155
155
|
<img src={product.image} alt={product.name} className="h-40 w-full object-cover rounded-lg" />
|
|
156
|
-
<h3 className="mt-2 font-semibold text-neutral-900
|
|
156
|
+
<h3 className="mt-2 font-semibold text-neutral-900">{product.name}</h3>
|
|
157
157
|
<p className="text-primary-600 font-bold">{product.price}</p>
|
|
158
158
|
</div>
|
|
159
159
|
</CarouselSlide>
|
|
@@ -205,7 +205,7 @@ Hỗ trợ 4 biến thể giao diện: `glass` (*mặc định*), `filled`, `out
|
|
|
205
205
|
Bạn có thể dễ dàng đặt nút điều hướng và phân trang vào trong một thanh công cụ tùy biến dưới đáy bằng cách thêm `className="static"` hoặc class định vị mong muốn:
|
|
206
206
|
|
|
207
207
|
```tsx
|
|
208
|
-
<Carousel defaultIndex={1} className="w-full max-w-md border border-neutral-200
|
|
208
|
+
<Carousel defaultIndex={1} className="w-full max-w-md border border-neutral-200 p-2 rounded-xl">
|
|
209
209
|
<CarouselContent>
|
|
210
210
|
<CarouselSlide><div className="h-36 bg-linear-to-r from-primary-600 to-primary-800 text-white rounded-lg flex items-center justify-center font-semibold">Slide 1</div></CarouselSlide>
|
|
211
211
|
<CarouselSlide><div className="h-36 bg-linear-to-r from-primary-700 to-primary-900 text-white rounded-lg flex items-center justify-center font-semibold">Slide 2</div></CarouselSlide>
|
package/docs/components/input.md
CHANGED
|
@@ -162,19 +162,13 @@ export function BasicInputExample() {
|
|
|
162
162
|
| `label` | `ReactNode` | — | Nhãn tiêu đề hiển thị cho ô nhập liệu. |
|
|
163
163
|
| `labelPlacement` | `'floating' \| 'top' \| 'left'` | `'floating'` | Vị trí hiển thị của nhãn. |
|
|
164
164
|
| `config` | `InputConfig` | — | Cấu hình tập trung các cờ trạng thái / tính năng (`isRequired`, `isInvalid`, `isLoading`, `showSpinner`, `isClearable`, `isFullWidth`). |
|
|
165
|
-
| `isRequired` | `boolean` | `false` | Hiển thị dấu sao đỏ `*` và đánh dấu `aria-required="true"`. |
|
|
166
165
|
| `helperText` | `ReactNode` | — | Đoạn văn bản hướng dẫn/trợ giúp bên dưới ô. |
|
|
167
166
|
| `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). |
|
|
168
|
-
| `isInvalid` | `boolean` | `false` | Bật trạng thái viền đỏ báo lỗi và `aria-invalid="true"`. |
|
|
169
|
-
| `isLoading` | `boolean` | `false` | Khóa tương tác, bật `aria-busy="true"` và `aria-disabled="true"`. |
|
|
170
|
-
| `showSpinner` | `boolean` | `false` | Hiển thị biểu tượng xoay spinner khi `isLoading={true}`. |
|
|
171
167
|
| `leftIcon` | `ReactNode` | — | Icon hiển thị ở đầu ô input. |
|
|
172
168
|
| `rightIcon` | `ReactNode` | — | Icon hiển thị ở cuối ô input. |
|
|
173
169
|
| `leftAddon` | `ReactNode` | — | Addon / prefix cố định ở đầu ô input (vd: `'https://'`). |
|
|
174
170
|
| `rightAddon` | `ReactNode` | — | Addon / suffix cố định ở cuối ô input (vd: `'.com'`). |
|
|
175
|
-
| `isClearable` | `boolean` | `false` | Hiển thị nút xóa nhanh nội dung khi có văn bản. |
|
|
176
171
|
| `onClear` | `() => void` | — | Callback được gọi khi bấm nút xóa nhanh. |
|
|
177
|
-
| `isFullWidth` | `boolean` | `false` | Mở rộng chiếm toàn bộ 100% chiều ngang container cha. |
|
|
178
172
|
| `ref` | `Ref<HTMLInputElement>` | — | Ref chuyển tiếp đến thẻ `<input>` HTML bên dưới. |
|
|
179
173
|
|
|
180
174
|
---
|