gladvn 0.2.36 → 0.2.37
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/README.md +1 -1
- package/package.json +1 -1
- package/scripts/strip-comments.ts +79 -0
- package/src/blocks/auth-form.tsx +34 -30
- package/src/blocks/auth-recovery.tsx +9 -13
- package/src/blocks/auth-split.tsx +46 -36
- package/src/blocks/settings.tsx +84 -117
- package/src/components/macro/alert-preset.tsx +3 -1
- package/src/components/macro/carousel-preset.tsx +0 -1
- package/src/components/macro/checkbox-preset.test.tsx +2 -2
- package/src/components/macro/checkbox-preset.tsx +1 -2
- package/src/components/macro/command-preset.tsx +1 -1
- package/src/components/macro/date-picker.tsx +5 -18
- package/src/components/macro/dialog-preset.tsx +5 -3
- package/src/components/macro/input-preset.tsx +0 -1
- package/src/components/macro/select-preset.tsx +1 -1
- package/src/components/macro/switch-preset.test.tsx +1 -1
- package/src/components/micro/accordion.test.tsx +6 -12
- package/src/components/micro/alert-dialog.tsx +0 -2
- package/src/components/micro/alert.test.tsx +5 -8
- package/src/components/micro/alert.tsx +0 -2
- package/src/components/micro/badge.test.tsx +5 -7
- package/src/components/micro/badge.tsx +1 -3
- package/src/components/micro/button.test.tsx +19 -26
- package/src/components/micro/button.tsx +3 -11
- package/src/components/micro/calendar.tsx +12 -13
- package/src/components/micro/card.test.tsx +3 -5
- package/src/components/micro/carousel.tsx +0 -1
- package/src/components/micro/chart.tsx +3 -2
- package/src/components/micro/checkbox.test.tsx +9 -18
- package/src/components/micro/combobox.tsx +1 -3
- package/src/components/micro/command.tsx +23 -11
- package/src/components/micro/context-menu.tsx +0 -2
- package/src/components/micro/dialog.test.tsx +6 -14
- package/src/components/micro/dialog.tsx +0 -2
- package/src/components/micro/drawer.tsx +0 -3
- package/src/components/micro/dropdown-menu.test.tsx +8 -14
- package/src/components/micro/dropdown-menu.tsx +0 -2
- package/src/components/micro/field.tsx +3 -3
- package/src/components/micro/hover-card.tsx +0 -2
- package/src/components/micro/input-group.tsx +9 -42
- package/src/components/micro/input.test.tsx +15 -24
- package/src/components/micro/navigation-menu.tsx +0 -2
- package/src/components/micro/popover.test.tsx +5 -9
- package/src/components/micro/progress.tsx +5 -1
- package/src/components/micro/select.test.tsx +5 -18
- package/src/components/micro/select.tsx +3 -5
- package/src/components/micro/separator.tsx +1 -1
- package/src/components/micro/sheet.test.tsx +6 -14
- package/src/components/micro/sheet.tsx +0 -2
- package/src/components/micro/sidebar.tsx +5 -6
- package/src/components/micro/sonner.tsx +0 -1
- package/src/components/micro/switch.test.tsx +9 -18
- package/src/components/micro/switch.tsx +1 -1
- package/src/components/micro/tabs.test.tsx +5 -11
- package/src/components/micro/theme-provider.tsx +0 -12
- package/src/components/micro/toggle-group.tsx +1 -12
- package/src/components/micro/tooltip.test.tsx +7 -12
- package/src/dev/App.tsx +6 -6
- package/src/dev/components/BlockViewer.tsx +15 -4
- package/src/dev/components/Container.tsx +20 -0
- package/src/dev/components/GladvnLogo.tsx +3 -9
- package/src/dev/components/code-highlighter.tsx +5 -3
- package/src/dev/components/showcase.tsx +116 -168
- package/src/dev/sections/buttons.tsx +4 -9
- package/src/dev/sections/display.tsx +68 -72
- package/src/dev/sections/feedback.tsx +304 -314
- package/src/dev/sections/forms.tsx +10 -14
- package/src/dev/sections/interactive.tsx +32 -32
- package/src/dev/sections/overview.tsx +2 -10
- package/src/dev/showcase/accordion.tsx +512 -554
- package/src/dev/showcase/alert-dialog.tsx +525 -565
- package/src/dev/showcase/alert.tsx +352 -402
- package/src/dev/showcase/aspect-ratio.tsx +167 -193
- package/src/dev/showcase/avatar.tsx +231 -269
- package/src/dev/showcase/badge.tsx +92 -140
- package/src/dev/showcase/breadcrumb.tsx +113 -135
- package/src/dev/showcase/button.tsx +140 -173
- package/src/dev/showcase/calendar.tsx +187 -237
- package/src/dev/showcase/card.tsx +229 -261
- package/src/dev/showcase/carousel.tsx +288 -315
- package/src/dev/showcase/chart.tsx +44 -50
- package/src/dev/showcase/checkbox.tsx +286 -330
- package/src/dev/showcase/collapsible.tsx +143 -160
- package/src/dev/showcase/combobox.tsx +327 -369
- package/src/dev/showcase/command.tsx +106 -120
- package/src/dev/showcase/context-menu.tsx +93 -107
- package/src/dev/showcase/dialog.tsx +585 -630
- package/src/dev/showcase/drawer.tsx +176 -196
- package/src/dev/showcase/dropdown-menu.tsx +183 -201
- package/src/dev/showcase/empty.tsx +232 -274
- package/src/dev/showcase/hover-card.tsx +153 -180
- package/src/dev/showcase/input-group.tsx +221 -253
- package/src/dev/showcase/input-otp.tsx +204 -250
- package/src/dev/showcase/input.tsx +246 -325
- package/src/dev/showcase/item.tsx +134 -152
- package/src/dev/showcase/kbd.tsx +149 -172
- package/src/dev/showcase/label.tsx +50 -68
- package/src/dev/showcase/menubar.tsx +194 -213
- package/src/dev/showcase/navigation-menu.tsx +119 -133
- package/src/dev/showcase/overview-components/ArchitectureSection.tsx +126 -0
- package/src/dev/showcase/overview-components/CodeBlock.tsx +50 -0
- package/src/dev/showcase/overview-components/ColorPaletteSection.tsx +44 -0
- package/src/dev/showcase/overview-components/DashboardPreview.tsx +159 -0
- package/src/dev/showcase/overview-components/FeatureGridSection.tsx +535 -0
- package/src/dev/showcase/overview-components/FooterCTA.tsx +137 -0
- package/src/dev/showcase/overview-components/HeroSection.tsx +65 -0
- package/src/dev/showcase/overview-components/PrinciplesSection.tsx +128 -0
- package/src/dev/showcase/overview.tsx +21 -1163
- package/src/dev/showcase/pagination.tsx +144 -190
- package/src/dev/showcase/popover.tsx +181 -196
- package/src/dev/showcase/progress.tsx +141 -189
- package/src/dev/showcase/radio-group.tsx +272 -332
- package/src/dev/showcase/resizable.tsx +119 -141
- package/src/dev/showcase/scroll-area.tsx +21 -36
- package/src/dev/showcase/select.tsx +262 -322
- package/src/dev/showcase/separator.tsx +75 -73
- package/src/dev/showcase/sheet.tsx +333 -361
- package/src/dev/showcase/sidebar.tsx +53 -59
- package/src/dev/showcase/skeleton.tsx +93 -112
- package/src/dev/showcase/slider.tsx +244 -296
- package/src/dev/showcase/sonner.tsx +251 -258
- package/src/dev/showcase/spinner.tsx +46 -67
- package/src/dev/showcase/switch.tsx +298 -362
- package/src/dev/showcase/table.tsx +76 -80
- package/src/dev/showcase/tabs.tsx +302 -355
- package/src/dev/showcase/textarea.tsx +240 -293
- package/src/dev/showcase/toast.tsx +48 -54
- package/src/dev/showcase/toggle-group.tsx +104 -129
- package/src/dev/showcase/toggle.tsx +82 -102
- package/src/dev/showcase/tooltip.tsx +173 -192
- package/src/dev/showcase/why.tsx +6 -12
- package/src/hooks/use-mobile.test.ts +3 -2
- package/src/lib/utils.test.ts +5 -4
package/src/blocks/settings.tsx
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { InfoIcon, TrashIcon } from "lucide-react";
|
|
2
|
+
import { InputPreset } from "../components/macro/input-preset";
|
|
3
|
+
import { RadioGroupPreset } from "../components/macro/radio-group-preset";
|
|
4
|
+
import { SelectPreset } from "../components/macro/select-preset";
|
|
5
|
+
import { SliderPreset } from "../components/macro/slider-preset";
|
|
6
|
+
import { SwitchPreset } from "../components/macro/switch-preset";
|
|
7
|
+
import { TextareaPreset } from "../components/macro/textarea-preset";
|
|
2
8
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../components/micro/accordion";
|
|
3
9
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "../components/micro/alert-dialog";
|
|
4
10
|
import { Button } from "../components/micro/button";
|
|
5
|
-
import { Input } from "../components/micro/input";
|
|
6
|
-
import { Label } from "../components/micro/label";
|
|
7
|
-
import { RadioGroup, RadioGroupItem } from "../components/micro/radio-group";
|
|
8
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/micro/select";
|
|
9
11
|
import { Separator } from "../components/micro/separator";
|
|
10
|
-
import { Slider, SliderControl, SliderIndicator, SliderThumb, SliderTrack } from "../components/micro/slider";
|
|
11
|
-
import { Switch, SwitchThumb } from "../components/micro/switch";
|
|
12
12
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/micro/tabs";
|
|
13
|
-
import { Textarea } from "../components/micro/textarea";
|
|
14
13
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../components/micro/tooltip";
|
|
15
14
|
|
|
16
15
|
export default function SettingsBlock() {
|
|
17
16
|
return (
|
|
18
17
|
<div className="container max-w-4xl py-10 px-4 md:px-6 mx-auto">
|
|
19
18
|
<div className="space-y-0.5 mb-6">
|
|
20
|
-
<h2 className="text-3xl font-bold tracking-tight">
|
|
19
|
+
<h2 className="text-3xl font-bold tracking-tight">Cài đặt</h2>
|
|
21
20
|
<p className="text-muted-foreground">
|
|
22
|
-
|
|
21
|
+
Quản lý tài khoản và thiết lập các tuỳ chọn nhận email.
|
|
23
22
|
</p>
|
|
24
23
|
</div>
|
|
25
24
|
<Separator className="my-6" />
|
|
@@ -27,120 +26,94 @@ export default function SettingsBlock() {
|
|
|
27
26
|
<Tabs orientation="vertical" defaultValue="profile" className="flex flex-col md:flex-row gap-8">
|
|
28
27
|
<aside className="w-full md:w-64 shrink-0">
|
|
29
28
|
<TabsList className="flex md:flex-col justify-start h-auto bg-transparent p-0 gap-2 w-full">
|
|
30
|
-
<TabsTrigger value="profile" className="w-full justify-start">
|
|
31
|
-
<TabsTrigger value="appearance" className="w-full justify-start">
|
|
32
|
-
<TabsTrigger value="notifications" className="w-full justify-start">
|
|
33
|
-
<TabsTrigger value="advanced" className="w-full justify-start">
|
|
29
|
+
<TabsTrigger value="profile" className="w-full justify-start">Hồ sơ</TabsTrigger>
|
|
30
|
+
<TabsTrigger value="appearance" className="w-full justify-start">Giao diện</TabsTrigger>
|
|
31
|
+
<TabsTrigger value="notifications" className="w-full justify-start">Thông báo</TabsTrigger>
|
|
32
|
+
<TabsTrigger value="advanced" className="w-full justify-start">Nâng cao</TabsTrigger>
|
|
34
33
|
</TabsList>
|
|
35
34
|
</aside>
|
|
36
35
|
|
|
37
36
|
<div className="flex-1">
|
|
38
|
-
{/* PROFILE TAB */}
|
|
39
37
|
<TabsContent value="profile" className="mt-0">
|
|
40
38
|
<div className="space-y-4">
|
|
41
|
-
<h3 className="text-xl font-medium">
|
|
42
|
-
<p className="text-sm text-muted-foreground"
|
|
39
|
+
<h3 className="text-xl font-medium">Hồ sơ cá nhân</h3>
|
|
40
|
+
<p className="text-sm text-muted-foreground">Đây là cách người khác sẽ nhìn thấy bạn trên app.</p>
|
|
43
41
|
</div>
|
|
44
42
|
<Separator className="my-6" />
|
|
45
43
|
|
|
46
44
|
<div className="flex flex-col gap-8">
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
className="min-h-[100px]"
|
|
79
|
-
/>
|
|
80
|
-
<p className="text-[13px] text-muted-foreground">
|
|
81
|
-
You can @mention other users and organizations to link to them.
|
|
82
|
-
</p>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<Button className="w-fit">Update profile</Button>
|
|
45
|
+
<InputPreset
|
|
46
|
+
id="username"
|
|
47
|
+
label="Tên người dùng"
|
|
48
|
+
placeholder="gladvn"
|
|
49
|
+
defaultValue="johndoe"
|
|
50
|
+
description="Tên hiển thị công khai. Tên thật hay biệt danh chém gió gì cũng được."
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
<SelectPreset
|
|
54
|
+
id="email"
|
|
55
|
+
label="Email"
|
|
56
|
+
defaultValue="m@example.com"
|
|
57
|
+
placeholder="Chọn email đã xác thực để hiển thị"
|
|
58
|
+
description="Bro có thể quản lý các email này trong phần cài đặt tài khoản."
|
|
59
|
+
options={[
|
|
60
|
+
{ value: "m@example.com", label: "m@example.com" },
|
|
61
|
+
{ value: "m@google.com", label: "m@google.com" },
|
|
62
|
+
{ value: "m@support.com", label: "m@support.com" },
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<TextareaPreset
|
|
67
|
+
id="bio"
|
|
68
|
+
label="Giới thiệu bản thân"
|
|
69
|
+
placeholder="Kể một chút về bro đi..."
|
|
70
|
+
defaultValue="Mình là dev chill chill."
|
|
71
|
+
className="min-h-[100px]"
|
|
72
|
+
description="Có thể @mention người dùng hoặc tổ chức để tạo liên kết nha."
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
<Button className="w-fit">Cập nhật hồ sơ</Button>
|
|
86
76
|
</div>
|
|
87
77
|
</TabsContent>
|
|
88
78
|
|
|
89
|
-
{/* APPEARANCE TAB */}
|
|
90
79
|
<TabsContent value="appearance" className="mt-0">
|
|
91
80
|
<div className="space-y-4">
|
|
92
|
-
<h3 className="text-xl font-medium">
|
|
93
|
-
<p className="text-sm text-muted-foreground">
|
|
81
|
+
<h3 className="text-xl font-medium">Giao diện</h3>
|
|
82
|
+
<p className="text-sm text-muted-foreground">Tuỳ biến giao diện sáng tối sao cho vừa mắt nhất.</p>
|
|
94
83
|
</div>
|
|
95
84
|
<Separator className="my-6" />
|
|
96
85
|
|
|
97
86
|
<div className="flex flex-col gap-8">
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
<RadioGroupPreset
|
|
88
|
+
label="Giao diện mặc định"
|
|
89
|
+
defaultValue="light"
|
|
90
|
+
options={[
|
|
91
|
+
{ value: "light", label: "Giao diện sáng" },
|
|
92
|
+
{ value: "dark", label: "Giao diện tối" },
|
|
93
|
+
{ value: "system", label: "Theo hệ thống" },
|
|
94
|
+
]}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<SliderPreset
|
|
98
|
+
defaultValue={[50]}
|
|
99
|
+
label={
|
|
109
100
|
<div className="flex items-center gap-2">
|
|
110
|
-
<
|
|
111
|
-
<
|
|
101
|
+
<span>Kích thước giao diện</span>
|
|
102
|
+
<TooltipProvider>
|
|
103
|
+
<Tooltip>
|
|
104
|
+
<TooltipTrigger render={<InfoIcon className="size-4 text-muted-foreground" />} />
|
|
105
|
+
<TooltipContent>Điều chỉnh độ to nhỏ tổng thể của UI</TooltipContent>
|
|
106
|
+
</Tooltip>
|
|
107
|
+
</TooltipProvider>
|
|
112
108
|
</div>
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
}
|
|
110
|
+
description={<div className="text-right">Vừa phải (100%)</div>}
|
|
111
|
+
/>
|
|
115
112
|
|
|
116
|
-
<
|
|
117
|
-
<div className="flex items-center gap-2">
|
|
118
|
-
<Label>Interface Scale</Label>
|
|
119
|
-
<TooltipProvider>
|
|
120
|
-
<Tooltip>
|
|
121
|
-
<TooltipTrigger render={<InfoIcon className="size-4 text-muted-foreground" />} />
|
|
122
|
-
<TooltipContent>Adjusts the overall size of UI elements</TooltipContent>
|
|
123
|
-
</Tooltip>
|
|
124
|
-
</TooltipProvider>
|
|
125
|
-
</div>
|
|
126
|
-
<div className="py-2 px-1">
|
|
127
|
-
<Slider defaultValue={50}>
|
|
128
|
-
<SliderControl>
|
|
129
|
-
<SliderTrack>
|
|
130
|
-
<SliderIndicator />
|
|
131
|
-
</SliderTrack>
|
|
132
|
-
<SliderThumb />
|
|
133
|
-
</SliderControl>
|
|
134
|
-
</Slider>
|
|
135
|
-
</div>
|
|
136
|
-
<p className="text-[13px] text-muted-foreground text-right">Medium (100%)</p>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<Button className="w-fit">Save preferences</Button>
|
|
113
|
+
<Button className="w-fit">Lưu tuỳ chọn</Button>
|
|
140
114
|
</div>
|
|
141
115
|
</TabsContent>
|
|
142
116
|
|
|
143
|
-
{/* NOTIFICATIONS TAB */}
|
|
144
117
|
<TabsContent value="notifications" className="mt-0">
|
|
145
118
|
<div className="space-y-4">
|
|
146
119
|
<h3 className="text-xl font-medium">Notifications</h3>
|
|
@@ -149,28 +122,22 @@ export default function SettingsBlock() {
|
|
|
149
122
|
<Separator className="my-6" />
|
|
150
123
|
|
|
151
124
|
<div className="flex flex-col gap-4">
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
</div>
|
|
166
|
-
<Switch id="security-emails" defaultChecked>
|
|
167
|
-
<SwitchThumb />
|
|
168
|
-
</Switch>
|
|
169
|
-
</div>
|
|
125
|
+
<SwitchPreset
|
|
126
|
+
id="marketing-emails"
|
|
127
|
+
label={<span className="text-base cursor-pointer">Marketing emails</span>}
|
|
128
|
+
description="Receive emails about new products, features, and more."
|
|
129
|
+
className="items-center justify-between rounded-xl border border-border p-4"
|
|
130
|
+
/>
|
|
131
|
+
<SwitchPreset
|
|
132
|
+
id="security-emails"
|
|
133
|
+
defaultChecked
|
|
134
|
+
label={<span className="text-base cursor-pointer">Security emails</span>}
|
|
135
|
+
description="Receive emails about your account activity and security."
|
|
136
|
+
className="items-center justify-between rounded-xl border border-border p-4"
|
|
137
|
+
/>
|
|
170
138
|
</div>
|
|
171
139
|
</TabsContent>
|
|
172
140
|
|
|
173
|
-
{/* ADVANCED TAB */}
|
|
174
141
|
<TabsContent value="advanced" className="mt-0">
|
|
175
142
|
<div className="space-y-4">
|
|
176
143
|
<h3 className="text-xl font-medium text-destructive">Advanced</h3>
|
|
@@ -57,7 +57,9 @@ const AlertPreset = React.forwardRef<HTMLDivElement, AlertPresetProps>(
|
|
|
57
57
|
color={color}
|
|
58
58
|
className={cn(
|
|
59
59
|
"flex w-full flex-col gap-3 @sm/alert:flex-row @sm/alert:items-start",
|
|
60
|
-
|
|
60
|
+
{
|
|
61
|
+
"pr-8 data-[size=sm]:pr-7 data-[size=lg]:pr-10": dismissible,
|
|
62
|
+
},
|
|
61
63
|
className,
|
|
62
64
|
)}
|
|
63
65
|
{...props}
|
|
@@ -33,7 +33,7 @@ describe("CheckboxPreset", () => {
|
|
|
33
33
|
|
|
34
34
|
it("renders the check indicator", () => {
|
|
35
35
|
const { container } = render(<CheckboxPreset />);
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
const checkbox = container.querySelector("[data-slot='checkbox']");
|
|
38
38
|
expect(checkbox).toBeInTheDocument();
|
|
39
39
|
});
|
|
@@ -44,7 +44,7 @@ describe("CheckboxPreset", () => {
|
|
|
44
44
|
const label = screen.getByText("Agree");
|
|
45
45
|
expect(checkbox).toBeInTheDocument();
|
|
46
46
|
expect(label).toBeInTheDocument();
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
expect(label.getAttribute("for")).toBeTruthy();
|
|
49
49
|
});
|
|
50
50
|
});
|
|
@@ -18,7 +18,7 @@ export type CheckboxPresetProps = Omit<
|
|
|
18
18
|
React.ComponentProps<typeof Checkbox>,
|
|
19
19
|
"className" | "size"
|
|
20
20
|
> & {
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
className?: string;
|
|
23
23
|
label?: React.ReactNode;
|
|
24
24
|
description?: React.ReactNode;
|
|
@@ -52,7 +52,6 @@ const CheckboxPreset = React.forwardRef<
|
|
|
52
52
|
className={cn("flex flex-row items-start gap-3", className)}
|
|
53
53
|
data-size={size}
|
|
54
54
|
>
|
|
55
|
-
{/* Zero-width space hack to perfectly align the Checkbox with the first line of the Label */}
|
|
56
55
|
<div className="flex items-center text-sm leading-snug group-data-[size=sm]/field:text-xs group-data-[size=lg]/field:text-base">
|
|
57
56
|
​
|
|
58
57
|
<Checkbox
|
|
@@ -18,10 +18,6 @@ import { type Size } from "../../lib/types";
|
|
|
18
18
|
import { cn } from "../../lib/utils";
|
|
19
19
|
import { FieldPreset } from "./field-preset";
|
|
20
20
|
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
// Types
|
|
23
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
21
|
type DatePickerBaseProps = {
|
|
26
22
|
/** Visual mode. "single" selects one date, "range" selects a start–end span. */
|
|
27
23
|
mode?: "single" | "range";
|
|
@@ -43,7 +39,7 @@ type DatePickerBaseProps = {
|
|
|
43
39
|
id?: string;
|
|
44
40
|
/** className applied to the outermost FieldPreset wrapper. */
|
|
45
41
|
className?: string;
|
|
46
|
-
|
|
42
|
+
|
|
47
43
|
/** Number of calendar months to display. */
|
|
48
44
|
numberOfMonths?: number;
|
|
49
45
|
/** Navigation control style. */
|
|
@@ -82,10 +78,6 @@ type DatePickerRangeProps = DatePickerBaseProps & {
|
|
|
82
78
|
|
|
83
79
|
type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
|
|
84
80
|
|
|
85
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
86
|
-
// Trigger styles — native <button>, no micro/button dependency
|
|
87
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
81
|
const triggerVariants = cva(
|
|
90
82
|
"inline-flex w-full items-center justify-start rounded-lg border border-input bg-transparent font-normal whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:focus-visible:ring-3 aria-invalid:focus-visible:ring-destructive/50 dark:bg-input/30 [&>svg:not([class*='size-'])]:size-[1.1em] [&>svg:not([class*='size-'])]:shrink-0",
|
|
91
83
|
{
|
|
@@ -98,10 +90,6 @@ const triggerVariants = cva(
|
|
|
98
90
|
size: "md" } },
|
|
99
91
|
);
|
|
100
92
|
|
|
101
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
102
|
-
// Helpers
|
|
103
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
93
|
const fmt = (
|
|
106
94
|
date: Date,
|
|
107
95
|
opts: Intl.DateTimeFormatOptions,
|
|
@@ -188,7 +176,7 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
|
|
188
176
|
}, [open, value, rangeValue]);
|
|
189
177
|
|
|
190
178
|
const defaultPlaceholder =
|
|
191
|
-
placeholder ?? (mode === "range" ? "
|
|
179
|
+
placeholder ?? (mode === "range" ? "Chọn khoảng thời gian" : "Chọn một ngày");
|
|
192
180
|
|
|
193
181
|
const triggerLabel = formatTriggerLabel(
|
|
194
182
|
mode,
|
|
@@ -218,10 +206,9 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
|
|
218
206
|
id={triggerId}
|
|
219
207
|
disabled={disabled}
|
|
220
208
|
aria-invalid={isInvalid || undefined}
|
|
221
|
-
className={cn(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)}
|
|
209
|
+
className={cn(triggerVariants({ size }), {
|
|
210
|
+
"text-muted-foreground": !hasValue,
|
|
211
|
+
})}
|
|
225
212
|
>
|
|
226
213
|
<CalendarIcon />
|
|
227
214
|
<span className="flex-1 truncate text-left min-w-0">
|
|
@@ -53,10 +53,12 @@ const DialogPreset = React.forwardRef<HTMLDivElement, DialogPresetProps>(
|
|
|
53
53
|
<DialogContent
|
|
54
54
|
ref={ref}
|
|
55
55
|
className={cn(
|
|
56
|
-
size === "sm" && "sm:max-w-md",
|
|
57
|
-
size === "md" && "sm:max-w-lg",
|
|
58
|
-
size === "lg" && "sm:max-w-xl",
|
|
59
56
|
"flex flex-col gap-0 p-0 overflow-hidden",
|
|
57
|
+
{
|
|
58
|
+
"sm:max-w-md": size === "sm",
|
|
59
|
+
"sm:max-w-lg": size === "md",
|
|
60
|
+
"sm:max-w-xl": size === "lg",
|
|
61
|
+
}
|
|
60
62
|
)}
|
|
61
63
|
>
|
|
62
64
|
{(title || description) && (
|
|
@@ -45,7 +45,6 @@ export const InputPreset = React.forwardRef<HTMLInputElement, InputPresetProps>(
|
|
|
45
45
|
const inputId = id || generatedId;
|
|
46
46
|
const isInvalid = !!errorMessage;
|
|
47
47
|
|
|
48
|
-
// Auto password toggle logic
|
|
49
48
|
const isPasswordType = type === "password";
|
|
50
49
|
const [showPassword, setShowPassword] = useState(false);
|
|
51
50
|
const actualType = isPasswordType
|
|
@@ -48,7 +48,7 @@ describe("SwitchPreset", () => {
|
|
|
48
48
|
const label = screen.getByText("Dark mode");
|
|
49
49
|
expect(switchEl).toBeInTheDocument();
|
|
50
50
|
expect(label).toBeInTheDocument();
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
expect(label.getAttribute("for")).toBeTruthy();
|
|
53
53
|
});
|
|
54
54
|
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
describe("Accordion", () => {
|
|
14
14
|
it("[3C.13-01] [P1] renders correctly and is closed by default", () => {
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
render(
|
|
17
17
|
<Accordion>
|
|
18
18
|
<AccordionItem value="item-1">
|
|
@@ -23,11 +23,10 @@ describe("Accordion", () => {
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
const trigger = screen.getByRole("button", { name: "Section 1" });
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
expect(trigger).toBeInTheDocument();
|
|
28
28
|
expect(trigger).toHaveAttribute("aria-expanded", "false");
|
|
29
29
|
|
|
30
|
-
// Content is either not in document or hidden based on Base UI implementation
|
|
31
30
|
const content = screen.queryByText("Content 1");
|
|
32
31
|
if (content) {
|
|
33
32
|
expect(content).not.toBeVisible();
|
|
@@ -35,7 +34,7 @@ describe("Accordion", () => {
|
|
|
35
34
|
});
|
|
36
35
|
|
|
37
36
|
it("[3C.13-02] [P1] expands the content when trigger is clicked", async () => {
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
const user = userEvent.setup();
|
|
40
39
|
render(
|
|
41
40
|
<Accordion>
|
|
@@ -47,10 +46,9 @@ describe("Accordion", () => {
|
|
|
47
46
|
);
|
|
48
47
|
|
|
49
48
|
const trigger = screen.getByRole("button", { name: "Section 1" });
|
|
50
|
-
|
|
49
|
+
|
|
51
50
|
await user.click(trigger);
|
|
52
51
|
|
|
53
|
-
// Then
|
|
54
52
|
await waitFor(() => {
|
|
55
53
|
expect(trigger).toHaveAttribute("aria-expanded", "true");
|
|
56
54
|
expect(screen.getByText("Content 1")).toBeVisible();
|
|
@@ -58,7 +56,7 @@ describe("Accordion", () => {
|
|
|
58
56
|
});
|
|
59
57
|
|
|
60
58
|
it("[3C.13-03] [P1] supports keyboard navigation", async () => {
|
|
61
|
-
|
|
59
|
+
|
|
62
60
|
const user = userEvent.setup();
|
|
63
61
|
render(
|
|
64
62
|
<Accordion>
|
|
@@ -76,17 +74,13 @@ describe("Accordion", () => {
|
|
|
76
74
|
const trigger1 = screen.getByRole("button", { name: "Section 1" });
|
|
77
75
|
const trigger2 = screen.getByRole("button", { name: "Section 2" });
|
|
78
76
|
|
|
79
|
-
// Focus first
|
|
80
|
-
// When
|
|
81
77
|
await user.tab();
|
|
82
|
-
|
|
78
|
+
|
|
83
79
|
expect(trigger1).toHaveFocus();
|
|
84
80
|
|
|
85
|
-
// Tab to move to next item
|
|
86
81
|
await user.tab();
|
|
87
82
|
expect(trigger2).toHaveFocus();
|
|
88
83
|
|
|
89
|
-
// Enter to expand
|
|
90
84
|
await user.keyboard("[Enter]");
|
|
91
85
|
await waitFor(() => {
|
|
92
86
|
expect(trigger2).toHaveAttribute("aria-expanded", "true");
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
describe("Alert", () => {
|
|
15
15
|
it("[3C.12-01] [P1] renders all alert parts correctly", () => {
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
render(
|
|
18
18
|
<Alert data-testid="alert">
|
|
19
19
|
<AlertIcon render={<InfoIcon />} data-testid="alert-icon" />
|
|
@@ -23,7 +23,6 @@ describe("Alert", () => {
|
|
|
23
23
|
</Alert>,
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
// Then
|
|
27
26
|
expect(screen.getByTestId("alert")).toBeInTheDocument();
|
|
28
27
|
expect(screen.getByTestId("alert-icon")).toBeInTheDocument();
|
|
29
28
|
expect(screen.getByText("Alert Title")).toBeInTheDocument();
|
|
@@ -32,19 +31,18 @@ describe("Alert", () => {
|
|
|
32
31
|
});
|
|
33
32
|
|
|
34
33
|
it("[3C.12-02] [P1] applies the color variants correctly", () => {
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
const { rerender } = render(
|
|
37
36
|
<Alert color="destructive" data-testid="alert-destructive">
|
|
38
37
|
Error
|
|
39
38
|
</Alert>,
|
|
40
39
|
);
|
|
41
|
-
|
|
40
|
+
|
|
42
41
|
expect(screen.getByTestId("alert-destructive")).toHaveAttribute(
|
|
43
42
|
"data-color",
|
|
44
43
|
"destructive",
|
|
45
44
|
);
|
|
46
45
|
|
|
47
|
-
// When
|
|
48
46
|
rerender(
|
|
49
47
|
<Alert color="success" data-testid="alert-success">
|
|
50
48
|
Success
|
|
@@ -57,16 +55,15 @@ describe("Alert", () => {
|
|
|
57
55
|
});
|
|
58
56
|
|
|
59
57
|
it("[3C.12-03] [P1] applies the size variants correctly", () => {
|
|
60
|
-
|
|
58
|
+
|
|
61
59
|
const { rerender } = render(
|
|
62
60
|
<Alert size="sm" data-testid="alert-sm">
|
|
63
61
|
Small
|
|
64
62
|
</Alert>,
|
|
65
63
|
);
|
|
66
|
-
|
|
64
|
+
|
|
67
65
|
expect(screen.getByTestId("alert-sm")).toHaveAttribute("data-size", "sm");
|
|
68
66
|
|
|
69
|
-
// When
|
|
70
67
|
rerender(
|
|
71
68
|
<Alert size="lg" data-testid="alert-lg">
|
|
72
69
|
Large
|
|
@@ -13,8 +13,6 @@ import { type VariantProps, cva } from "class-variance-authority";
|
|
|
13
13
|
|
|
14
14
|
import { cn } from "../../lib/utils";
|
|
15
15
|
|
|
16
|
-
// CSS Delegated Logic:
|
|
17
|
-
// - KHÔNG CÒN MAGIC CSS: Mọi logic layout (như khoảng cách icon, padding action) đều được uỷ quyền cho Macro xử lý.
|
|
18
16
|
const alertVariants = cva(
|
|
19
17
|
"group/alert relative rounded-lg border border-border text-left bg-card text-card-foreground",
|
|
20
18
|
{
|
|
@@ -6,17 +6,16 @@ import { Badge } from "./badge";
|
|
|
6
6
|
|
|
7
7
|
describe("Badge", () => {
|
|
8
8
|
it("[3C.10-01] [P1] renders correctly with default styles", () => {
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
render(<Badge data-testid="badge">Hello</Badge>);
|
|
11
11
|
const badge = screen.getByTestId("badge");
|
|
12
12
|
|
|
13
|
-
// Then
|
|
14
13
|
expect(badge).toBeInTheDocument();
|
|
15
14
|
expect(badge).toHaveTextContent("Hello");
|
|
16
15
|
});
|
|
17
16
|
|
|
18
17
|
it("[3C.10-02] [P1] applies variant and color correctly", () => {
|
|
19
|
-
|
|
18
|
+
|
|
20
19
|
const { rerender } = render(
|
|
21
20
|
<Badge
|
|
22
21
|
variant="outline"
|
|
@@ -27,10 +26,9 @@ describe("Badge", () => {
|
|
|
27
26
|
</Badge>,
|
|
28
27
|
);
|
|
29
28
|
const outlineBadge = screen.getByTestId("badge-outline-destructive");
|
|
30
|
-
|
|
29
|
+
|
|
31
30
|
expect(outlineBadge).toHaveClass("border-destructive", "text-destructive");
|
|
32
31
|
|
|
33
|
-
// When
|
|
34
32
|
rerender(
|
|
35
33
|
<Badge variant="soft" color="success" data-testid="badge-soft-success">
|
|
36
34
|
Success
|
|
@@ -41,7 +39,7 @@ describe("Badge", () => {
|
|
|
41
39
|
});
|
|
42
40
|
|
|
43
41
|
it("[3C.10-03] [P1] supports polymorphism via render prop", () => {
|
|
44
|
-
|
|
42
|
+
|
|
45
43
|
render(
|
|
46
44
|
<Badge render={<a href="/test" data-testid="badge-link" />}>
|
|
47
45
|
Link Badge
|
|
@@ -49,7 +47,7 @@ describe("Badge", () => {
|
|
|
49
47
|
);
|
|
50
48
|
|
|
51
49
|
const link = screen.getByTestId("badge-link");
|
|
52
|
-
|
|
50
|
+
|
|
53
51
|
expect(link.tagName).toBe("A");
|
|
54
52
|
expect(link).toHaveAttribute("href", "/test");
|
|
55
53
|
expect(link).toHaveTextContent("Link Badge");
|