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.
Files changed (134) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/scripts/strip-comments.ts +79 -0
  4. package/src/blocks/auth-form.tsx +34 -30
  5. package/src/blocks/auth-recovery.tsx +9 -13
  6. package/src/blocks/auth-split.tsx +46 -36
  7. package/src/blocks/settings.tsx +84 -117
  8. package/src/components/macro/alert-preset.tsx +3 -1
  9. package/src/components/macro/carousel-preset.tsx +0 -1
  10. package/src/components/macro/checkbox-preset.test.tsx +2 -2
  11. package/src/components/macro/checkbox-preset.tsx +1 -2
  12. package/src/components/macro/command-preset.tsx +1 -1
  13. package/src/components/macro/date-picker.tsx +5 -18
  14. package/src/components/macro/dialog-preset.tsx +5 -3
  15. package/src/components/macro/input-preset.tsx +0 -1
  16. package/src/components/macro/select-preset.tsx +1 -1
  17. package/src/components/macro/switch-preset.test.tsx +1 -1
  18. package/src/components/micro/accordion.test.tsx +6 -12
  19. package/src/components/micro/alert-dialog.tsx +0 -2
  20. package/src/components/micro/alert.test.tsx +5 -8
  21. package/src/components/micro/alert.tsx +0 -2
  22. package/src/components/micro/badge.test.tsx +5 -7
  23. package/src/components/micro/badge.tsx +1 -3
  24. package/src/components/micro/button.test.tsx +19 -26
  25. package/src/components/micro/button.tsx +3 -11
  26. package/src/components/micro/calendar.tsx +12 -13
  27. package/src/components/micro/card.test.tsx +3 -5
  28. package/src/components/micro/carousel.tsx +0 -1
  29. package/src/components/micro/chart.tsx +3 -2
  30. package/src/components/micro/checkbox.test.tsx +9 -18
  31. package/src/components/micro/combobox.tsx +1 -3
  32. package/src/components/micro/command.tsx +23 -11
  33. package/src/components/micro/context-menu.tsx +0 -2
  34. package/src/components/micro/dialog.test.tsx +6 -14
  35. package/src/components/micro/dialog.tsx +0 -2
  36. package/src/components/micro/drawer.tsx +0 -3
  37. package/src/components/micro/dropdown-menu.test.tsx +8 -14
  38. package/src/components/micro/dropdown-menu.tsx +0 -2
  39. package/src/components/micro/field.tsx +3 -3
  40. package/src/components/micro/hover-card.tsx +0 -2
  41. package/src/components/micro/input-group.tsx +9 -42
  42. package/src/components/micro/input.test.tsx +15 -24
  43. package/src/components/micro/navigation-menu.tsx +0 -2
  44. package/src/components/micro/popover.test.tsx +5 -9
  45. package/src/components/micro/progress.tsx +5 -1
  46. package/src/components/micro/select.test.tsx +5 -18
  47. package/src/components/micro/select.tsx +3 -5
  48. package/src/components/micro/separator.tsx +1 -1
  49. package/src/components/micro/sheet.test.tsx +6 -14
  50. package/src/components/micro/sheet.tsx +0 -2
  51. package/src/components/micro/sidebar.tsx +5 -6
  52. package/src/components/micro/sonner.tsx +0 -1
  53. package/src/components/micro/switch.test.tsx +9 -18
  54. package/src/components/micro/switch.tsx +1 -1
  55. package/src/components/micro/tabs.test.tsx +5 -11
  56. package/src/components/micro/theme-provider.tsx +0 -12
  57. package/src/components/micro/toggle-group.tsx +1 -12
  58. package/src/components/micro/tooltip.test.tsx +7 -12
  59. package/src/dev/App.tsx +6 -6
  60. package/src/dev/components/BlockViewer.tsx +15 -4
  61. package/src/dev/components/Container.tsx +20 -0
  62. package/src/dev/components/GladvnLogo.tsx +3 -9
  63. package/src/dev/components/code-highlighter.tsx +5 -3
  64. package/src/dev/components/showcase.tsx +116 -168
  65. package/src/dev/sections/buttons.tsx +4 -9
  66. package/src/dev/sections/display.tsx +68 -72
  67. package/src/dev/sections/feedback.tsx +304 -314
  68. package/src/dev/sections/forms.tsx +10 -14
  69. package/src/dev/sections/interactive.tsx +32 -32
  70. package/src/dev/sections/overview.tsx +2 -10
  71. package/src/dev/showcase/accordion.tsx +512 -554
  72. package/src/dev/showcase/alert-dialog.tsx +525 -565
  73. package/src/dev/showcase/alert.tsx +352 -402
  74. package/src/dev/showcase/aspect-ratio.tsx +167 -193
  75. package/src/dev/showcase/avatar.tsx +231 -269
  76. package/src/dev/showcase/badge.tsx +92 -140
  77. package/src/dev/showcase/breadcrumb.tsx +113 -135
  78. package/src/dev/showcase/button.tsx +140 -173
  79. package/src/dev/showcase/calendar.tsx +187 -237
  80. package/src/dev/showcase/card.tsx +229 -261
  81. package/src/dev/showcase/carousel.tsx +288 -315
  82. package/src/dev/showcase/chart.tsx +44 -50
  83. package/src/dev/showcase/checkbox.tsx +286 -330
  84. package/src/dev/showcase/collapsible.tsx +143 -160
  85. package/src/dev/showcase/combobox.tsx +327 -369
  86. package/src/dev/showcase/command.tsx +106 -120
  87. package/src/dev/showcase/context-menu.tsx +93 -107
  88. package/src/dev/showcase/dialog.tsx +585 -630
  89. package/src/dev/showcase/drawer.tsx +176 -196
  90. package/src/dev/showcase/dropdown-menu.tsx +183 -201
  91. package/src/dev/showcase/empty.tsx +232 -274
  92. package/src/dev/showcase/hover-card.tsx +153 -180
  93. package/src/dev/showcase/input-group.tsx +221 -253
  94. package/src/dev/showcase/input-otp.tsx +204 -250
  95. package/src/dev/showcase/input.tsx +246 -325
  96. package/src/dev/showcase/item.tsx +134 -152
  97. package/src/dev/showcase/kbd.tsx +149 -172
  98. package/src/dev/showcase/label.tsx +50 -68
  99. package/src/dev/showcase/menubar.tsx +194 -213
  100. package/src/dev/showcase/navigation-menu.tsx +119 -133
  101. package/src/dev/showcase/overview-components/ArchitectureSection.tsx +126 -0
  102. package/src/dev/showcase/overview-components/CodeBlock.tsx +50 -0
  103. package/src/dev/showcase/overview-components/ColorPaletteSection.tsx +44 -0
  104. package/src/dev/showcase/overview-components/DashboardPreview.tsx +159 -0
  105. package/src/dev/showcase/overview-components/FeatureGridSection.tsx +535 -0
  106. package/src/dev/showcase/overview-components/FooterCTA.tsx +137 -0
  107. package/src/dev/showcase/overview-components/HeroSection.tsx +65 -0
  108. package/src/dev/showcase/overview-components/PrinciplesSection.tsx +128 -0
  109. package/src/dev/showcase/overview.tsx +21 -1163
  110. package/src/dev/showcase/pagination.tsx +144 -190
  111. package/src/dev/showcase/popover.tsx +181 -196
  112. package/src/dev/showcase/progress.tsx +141 -189
  113. package/src/dev/showcase/radio-group.tsx +272 -332
  114. package/src/dev/showcase/resizable.tsx +119 -141
  115. package/src/dev/showcase/scroll-area.tsx +21 -36
  116. package/src/dev/showcase/select.tsx +262 -322
  117. package/src/dev/showcase/separator.tsx +75 -73
  118. package/src/dev/showcase/sheet.tsx +333 -361
  119. package/src/dev/showcase/sidebar.tsx +53 -59
  120. package/src/dev/showcase/skeleton.tsx +93 -112
  121. package/src/dev/showcase/slider.tsx +244 -296
  122. package/src/dev/showcase/sonner.tsx +251 -258
  123. package/src/dev/showcase/spinner.tsx +46 -67
  124. package/src/dev/showcase/switch.tsx +298 -362
  125. package/src/dev/showcase/table.tsx +76 -80
  126. package/src/dev/showcase/tabs.tsx +302 -355
  127. package/src/dev/showcase/textarea.tsx +240 -293
  128. package/src/dev/showcase/toast.tsx +48 -54
  129. package/src/dev/showcase/toggle-group.tsx +104 -129
  130. package/src/dev/showcase/toggle.tsx +82 -102
  131. package/src/dev/showcase/tooltip.tsx +173 -192
  132. package/src/dev/showcase/why.tsx +6 -12
  133. package/src/hooks/use-mobile.test.ts +3 -2
  134. package/src/lib/utils.test.ts +5 -4
@@ -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">Settings</h2>
19
+ <h2 className="text-3xl font-bold tracking-tight">Cài đặt</h2>
21
20
  <p className="text-muted-foreground">
22
- Manage your account settings and set e-mail preferences.
21
+ Quản tài khoản 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">Profile</TabsTrigger>
31
- <TabsTrigger value="appearance" className="w-full justify-start">Appearance</TabsTrigger>
32
- <TabsTrigger value="notifications" className="w-full justify-start">Notifications</TabsTrigger>
33
- <TabsTrigger value="advanced" className="w-full justify-start">Advanced</TabsTrigger>
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">Profile</h3>
42
- <p className="text-sm text-muted-foreground">This is how others will see you on the site.</p>
39
+ <h3 className="text-xl font-medium">Hồ sơ cá nhân</h3>
40
+ <p className="text-sm text-muted-foreground">Đây 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
- <div className="flex flex-col gap-2">
48
- <Label htmlFor="username">Username</Label>
49
- <Input id="username" placeholder="gladvn" defaultValue="johndoe" />
50
- <p className="text-[13px] text-muted-foreground">
51
- This is your public display name. It can be your real name or a pseudonym.
52
- </p>
53
- </div>
54
-
55
- <div className="flex flex-col gap-2">
56
- <Label htmlFor="email">Email</Label>
57
- <Select defaultValue="m@example.com">
58
- <SelectTrigger id="email">
59
- <SelectValue placeholder="Select a verified email to display" />
60
- </SelectTrigger>
61
- <SelectContent>
62
- <SelectItem value="m@example.com">m@example.com</SelectItem>
63
- <SelectItem value="m@google.com">m@google.com</SelectItem>
64
- <SelectItem value="m@support.com">m@support.com</SelectItem>
65
- </SelectContent>
66
- </Select>
67
- <p className="text-[13px] text-muted-foreground">
68
- You can manage verified email addresses in your email settings.
69
- </p>
70
- </div>
71
-
72
- <div className="flex flex-col gap-2">
73
- <Label htmlFor="bio">Bio</Label>
74
- <Textarea
75
- id="bio"
76
- placeholder="Tell us a little bit about yourself"
77
- defaultValue="I own a computer."
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 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">Appearance</h3>
93
- <p className="text-sm text-muted-foreground">Customize the look and feel of the application.</p>
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
- <div className="flex flex-col gap-4">
99
- <Label>Theme Preference</Label>
100
- <RadioGroup defaultValue="light" className="flex flex-col gap-3">
101
- <div className="flex items-center gap-2">
102
- <RadioGroupItem value="light" id="theme-light" />
103
- <Label htmlFor="theme-light" className="font-normal cursor-pointer">Light Theme</Label>
104
- </div>
105
- <div className="flex items-center gap-2">
106
- <RadioGroupItem value="dark" id="theme-dark" />
107
- <Label htmlFor="theme-dark" className="font-normal cursor-pointer">Dark Theme</Label>
108
- </div>
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
- <RadioGroupItem value="system" id="theme-system" />
111
- <Label htmlFor="theme-system" className="font-normal cursor-pointer">System Default</Label>
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
- </RadioGroup>
114
- </div>
109
+ }
110
+ description={<div className="text-right">Vừa phải (100%)</div>}
111
+ />
115
112
 
116
- <div className="flex flex-col gap-4">
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
- <div className="flex items-center justify-between rounded-xl border border-border p-4">
153
- <div className="flex flex-col gap-0.5">
154
- <Label htmlFor="marketing-emails" className="text-base cursor-pointer">Marketing emails</Label>
155
- <p className="text-[13px] text-muted-foreground">Receive emails about new products, features, and more.</p>
156
- </div>
157
- <Switch id="marketing-emails">
158
- <SwitchThumb />
159
- </Switch>
160
- </div>
161
- <div className="flex items-center justify-between rounded-xl border border-border p-4">
162
- <div className="flex flex-col gap-0.5">
163
- <Label htmlFor="security-emails" className="text-base cursor-pointer">Security emails</Label>
164
- <p className="text-[13px] text-muted-foreground">Receive emails about your account activity and security.</p>
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
- dismissible && "pr-8 data-[size=sm]:pr-7 data-[size=lg]:pr-10",
60
+ {
61
+ "pr-8 data-[size=sm]:pr-7 data-[size=lg]:pr-10": dismissible,
62
+ },
61
63
  className,
62
64
  )}
63
65
  {...props}
@@ -23,7 +23,6 @@ export type CarouselPresetProps = Omit<
23
23
  itemClassName?: string;
24
24
  contentClassName?: string;
25
25
 
26
- // Controlled & Uncontrolled props
27
26
  index?: number;
28
27
  defaultIndex?: number;
29
28
  onIndexChange?: (index: number) => void;
@@ -33,7 +33,7 @@ describe("CheckboxPreset", () => {
33
33
 
34
34
  it("renders the check indicator", () => {
35
35
  const { container } = render(<CheckboxPreset />);
36
- // CheckboxIndicator may not render a separate data-slot when unchecked
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
- // Label's for attr may point to internal input
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
- // Forward className to the outermost wrapper (FieldPreset) instead of the Checkbox primitive
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
  &#8203;
58
57
  <Checkbox
@@ -61,7 +61,7 @@ function CommandDialog({
61
61
  CommandDialog.displayName = "CommandDialog";
62
62
 
63
63
  export {
64
- // Re-export primitives for convenience
64
+
65
65
  Command,
66
66
  CommandDialog,
67
67
  CommandEmpty,
@@ -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
- // ─── Calendar passthrough ───────────────────────────────────────────────
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" ? "Pick a date range" : "Pick a date");
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
- triggerVariants({ size }),
223
- !hasValue && "text-muted-foreground",
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
@@ -143,7 +143,7 @@ const SelectPreset = React.forwardRef<
143
143
  </SelectGroup>
144
144
  );
145
145
  }
146
- // Items without a group
146
+
147
147
  return opts.map((opt) => (
148
148
  <SelectItem
149
149
  key={opt.value}
@@ -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
- // Label's for attr may point to internal input, not the switch button
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
- // Given
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
- // Then
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
- // Given
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
- // When
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
- // Given
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
- // Then
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");
@@ -37,8 +37,6 @@ function AlertDialogClose({ ...props }: AlertDialogPrimitive.Close.Props) {
37
37
  );
38
38
  }
39
39
 
40
-
41
-
42
40
  function AlertDialogOverlay({
43
41
  className,
44
42
  ...props
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  describe("Alert", () => {
15
15
  it("[3C.12-01] [P1] renders all alert parts correctly", () => {
16
- // Given
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
- // Given
34
+
36
35
  const { rerender } = render(
37
36
  <Alert color="destructive" data-testid="alert-destructive">
38
37
  Error
39
38
  </Alert>,
40
39
  );
41
- // Then
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
- // Given
58
+
61
59
  const { rerender } = render(
62
60
  <Alert size="sm" data-testid="alert-sm">
63
61
  Small
64
62
  </Alert>,
65
63
  );
66
- // Then
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
- // Given
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
- // Given
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
- // Then
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
- // Given
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
- // Then
50
+
53
51
  expect(link.tagName).toBe("A");
54
52
  expect(link).toHaveAttribute("href", "/test");
55
53
  expect(link).toHaveTextContent("Link Badge");