gladvn 0.2.35 → 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 +2 -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
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  <p align="center">
7
7
  <a href="https://github.com/duongacy/gladvn/actions"><img src="https://img.shields.io/github/actions/workflow/status/duongacy/gladvn/ci.yml?branch=main" alt="Build Status"></a>
8
8
  <a href="https://github.com/duongacy/gladvn/stargazers"><img src="https://img.shields.io/github/stars/duongacy/gladvn" alt="GitHub Stars"></a>
9
- <a href="https://github.com/duongacy/gladvn/blob/main/LICENSE"><img src="https://img.shields.io/github/license/duongacy/gladvn" alt="License"></a>
9
+ <a href="https://github.com/duongacy/gladvn/blob/main/LICENSE"><img src="https://img.shields.io/github/license/duongacy/gladvn?t=1" alt="License"></a>
10
10
  </p>
11
11
  <br/>
12
12
  <!-- Hero Image Placeholder -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gladvn",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.11.0",
6
6
  "description": "A CLI to scaffold beautiful, accessible React components into your project. Powered by Tailwind CSS v4 and Base UI.",
@@ -35,6 +35,7 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "dev": "vite",
38
+ "build": "npm run build:app",
38
39
  "build:app": "vite build -c vite.config.app.ts",
39
40
  "lint": "oxlint",
40
41
  "preview": "vite preview",
@@ -0,0 +1,79 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as ts from 'typescript';
4
+
5
+ function stripCommentsFromFile(filePath: string) {
6
+ const content = fs.readFileSync(filePath, 'utf8');
7
+ let result = '';
8
+
9
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest, false);
10
+ scanner.setText(content);
11
+
12
+ let currentToken = scanner.scan();
13
+ let lastPos = 0;
14
+
15
+ while (currentToken !== ts.SyntaxKind.EndOfFileToken) {
16
+ const tokenStart = scanner.getTokenPos();
17
+ const tokenEnd = scanner.getTextPos();
18
+
19
+ if (currentToken === ts.SyntaxKind.SingleLineCommentTrivia) {
20
+ // It's a // comment. Strip it!
21
+ result += content.substring(lastPos, tokenStart);
22
+ lastPos = tokenEnd;
23
+ } else if (currentToken === ts.SyntaxKind.MultiLineCommentTrivia) {
24
+ // It's a /* */ comment.
25
+ const commentText = content.substring(tokenStart, tokenEnd);
26
+ if (commentText.startsWith('/**')) {
27
+ // Keep JSDoc
28
+ } else {
29
+ // Strip normal multiline comment
30
+ result += content.substring(lastPos, tokenStart);
31
+ lastPos = tokenEnd;
32
+ }
33
+ }
34
+
35
+ currentToken = scanner.scan();
36
+ }
37
+
38
+
39
+ result += content.substring(lastPos);
40
+
41
+ // Also clean up multiple blank lines left by deleted single line comments
42
+ // (Optional, but helps keep code clean)
43
+ result = result.replace(/\n\s*\n\s*\n/g, '\n\n');
44
+
45
+ if (result !== content) {
46
+ fs.writeFileSync(filePath, result, 'utf8');
47
+ return true;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ function processDirectory(dir: string) {
53
+ let count = 0;
54
+ const files = fs.readdirSync(dir);
55
+ for (const file of files) {
56
+ const fullPath = path.join(dir, file);
57
+ const stat = fs.statSync(fullPath);
58
+ if (stat.isDirectory()) {
59
+ count += processDirectory(fullPath);
60
+ } else if (fullPath.endsWith('.ts') || fullPath.endsWith('.tsx')) {
61
+ if (stripCommentsFromFile(fullPath)) {
62
+ count++;
63
+ console.log(`Stripped comments from: ${fullPath}`);
64
+ }
65
+ }
66
+ }
67
+ return count;
68
+ }
69
+
70
+ const targetDirs = ['src/components', 'src/blocks', 'src/dev'];
71
+ let totalChanged = 0;
72
+
73
+ for (const dir of targetDirs) {
74
+ if (fs.existsSync(dir)) {
75
+ totalChanged += processDirectory(dir);
76
+ }
77
+ }
78
+
79
+ console.log(`\nFinished! Stripped comments from ${totalChanged} files.`);
@@ -1,10 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { useState } from "react";
3
+ import { CheckboxPreset } from "../components/macro/checkbox-preset";
4
+ import { InputPreset } from "../components/macro/input-preset";
3
5
  import { Button } from "../components/micro/button";
4
6
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/micro/card";
5
- import { Checkbox } from "../components/micro/checkbox";
6
- import { Input } from "../components/micro/input";
7
- import { Label } from "../components/micro/label";
8
7
 
9
8
  export default function AuthCardBlock() {
10
9
  const [isLoading, setIsLoading] = useState(false);
@@ -32,44 +31,49 @@ export default function AuthCardBlock() {
32
31
  <CardContent className="flex flex-col gap-6 px-0 pb-0">
33
32
  <form onSubmit={handleSubmit} className="flex flex-col gap-4">
34
33
  {mode === "register" && (
35
- <div className="flex flex-col gap-2">
36
- <Label htmlFor="name">Full Name</Label>
37
- <Input id="name" name="name" placeholder="John Doe" className="w-full" required />
38
- </div>
34
+ <InputPreset id="name" name="name" label="Full Name" placeholder="John Doe" required />
39
35
  )}
40
- <div className="flex flex-col gap-2">
41
- <Label htmlFor="email">Email</Label>
42
- <Input id="email" name="email" type="email" placeholder="m@example.com" className="w-full" required />
43
- </div>
44
- <div className="flex flex-col gap-2">
45
- <div className="flex items-center justify-between">
46
- <Label htmlFor="password">Password</Label>
47
- {mode === "login" && (
48
- <Button variant="link" color="primary" className="h-auto p-0 text-xs">
49
- Forgot password?
50
- </Button>
51
- )}
52
- </div>
53
- <Input id="password" name="password" type="password" className="w-full" required />
54
- </div>
36
+
37
+ <InputPreset id="email" name="email" type="email" label="Email" placeholder="m@example.com" required />
38
+
39
+ <InputPreset
40
+ id="password"
41
+ name="password"
42
+ type="password"
43
+ label={
44
+ <div className="flex items-center justify-between w-full">
45
+ <span>Mật khẩu</span>
46
+ {mode === "login" && (
47
+ <Button variant="link" color="primary" className="h-auto p-0 text-xs">
48
+ Quên mật khẩu?
49
+ </Button>
50
+ )}
51
+ </div>
52
+ }
53
+ required
54
+ />
55
55
 
56
56
  {mode === "register" && (
57
- <div className="flex items-center gap-2">
58
- <Checkbox id="terms" name="terms" required />
59
- <Label htmlFor="terms" className="text-sm font-normal text-muted-foreground leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
60
- I agree to the <a href="#" className="text-primary hover:underline">terms and conditions</a>
61
- </Label>
62
- </div>
57
+ <CheckboxPreset
58
+ id="terms"
59
+ name="terms"
60
+ required
61
+ label={
62
+ <span className="text-sm font-normal text-muted-foreground">
63
+ Tôi đồng ý với <a href="#" className="text-primary hover:underline">các điều khoản và điều kiện</a>
64
+ </span>
65
+ }
66
+ />
63
67
  )}
64
68
 
65
69
  <Button type="submit" className="w-full" disabled={isLoading} aria-busy={isLoading}>
66
- {isLoading ? "Loading..." : mode === "login" ? "Sign In" : "Create Account"}
70
+ {isLoading ? "Đang xử lý..." : mode === "login" ? "Đăng nhập" : "Tạo tài khoản"}
67
71
  </Button>
68
72
  </form>
69
73
 
70
74
  <div className="flex justify-center">
71
75
  <span className="text-[13px] font-medium text-muted-foreground uppercase">
72
- Or continue with
76
+ Hoặc tiếp tục với
73
77
  </span>
74
78
  </div>
75
79
 
@@ -1,9 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { useState } from "react";
3
+ import { InputPreset } from "../components/macro/input-preset";
3
4
  import { Button } from "../components/micro/button";
4
5
  import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/micro/card";
5
- import { Input } from "../components/micro/input";
6
- import { Label } from "../components/micro/label";
7
6
 
8
7
  export default function AuthRecoveryBlock() {
9
8
  const [isLoading, setIsLoading] = useState(false);
@@ -19,40 +18,37 @@ export default function AuthRecoveryBlock() {
19
18
  };
20
19
 
21
20
  return (
22
- <div className="flex min-h-[50vh] w-full items-center justify-center p-4 bg-muted/20">
21
+ <div className="flex min-h-screen w-full items-center justify-center p-4 bg-muted/20">
23
22
  <Card className="w-full max-w-sm">
24
23
  <CardHeader className="text-center">
25
24
  <CardTitle className="text-2xl font-bold">
26
- {isSent ? "Check your email" : "Forgot Password"}
25
+ {isSent ? "Kiểm tra email của bạn" : "Quên mật khẩu?"}
27
26
  </CardTitle>
28
27
  <CardDescription>
29
28
  {isSent
30
- ? "We have sent a password reset link to your email."
31
- : "Enter your email address and we will send you a link to reset your password."}
29
+ ? "Chúng tôi đã gửi link đặt lại mật khẩu đến email của bạn."
30
+ : "Không sao đâu, ai cũng có lúc quên mà. Nhập email vào đây để tụi mình gửi link reset cho nha."}
32
31
  </CardDescription>
33
32
  </CardHeader>
34
33
  <CardContent>
35
34
  {!isSent ? (
36
35
  <form onSubmit={handleSubmit} className="flex flex-col gap-4">
37
- <div className="flex flex-col gap-1.5">
38
- <Label htmlFor="recovery-email">Email</Label>
39
- <Input id="recovery-email" type="email" placeholder="m@example.com" className="w-full" required />
40
- </div>
36
+ <InputPreset id="recovery-email" type="email" label="Email" placeholder="m@example.com" required />
41
37
  <Button type="submit" className="w-full" disabled={isLoading}>
42
- {isLoading ? "Sending link..." : "Send Reset Link"}
38
+ {isLoading ? "Đang gửi..." : "Gửi link reset mật khẩu"}
43
39
  </Button>
44
40
  </form>
45
41
  ) : (
46
42
  <div className="flex flex-col gap-4">
47
43
  <Button type="button" variant="outline" className="w-full" onClick={() => setIsSent(false)}>
48
- Try another email
44
+ Thử email khác
49
45
  </Button>
50
46
  </div>
51
47
  )}
52
48
  </CardContent>
53
49
  <CardFooter className="flex justify-center">
54
50
  <Button variant="link" color="primary" className="text-sm">
55
- Back to Login
51
+ Quay lại đăng nhập
56
52
  </Button>
57
53
  </CardFooter>
58
54
  </Card>
@@ -1,9 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { useState } from "react";
3
+ import { CheckboxPreset } from "../components/macro/checkbox-preset";
4
+ import { InputPreset } from "../components/macro/input-preset";
3
5
  import { Button } from "../components/micro/button";
4
- import { Checkbox } from "../components/micro/checkbox";
5
- import { Input } from "../components/micro/input";
6
- import { Label } from "../components/micro/label";
7
6
  import { Separator } from "../components/micro/separator";
8
7
 
9
8
  export default function AuthSplitBlock() {
@@ -18,9 +17,7 @@ export default function AuthSplitBlock() {
18
17
 
19
18
  return (
20
19
  <div className="flex min-h-screen w-full">
21
- {/* Left side - Image/Branding (hidden on mobile) */}
22
20
  <div className="hidden lg:flex w-1/2 flex-col justify-between bg-primary p-12 text-primary-foreground relative overflow-hidden">
23
- {/* Subtle background pattern */}
24
21
  <div className="absolute inset-0 opacity-10 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-white to-transparent pointer-events-none" />
25
22
 
26
23
  <div className="relative z-10">
@@ -42,58 +39,71 @@ export default function AuthSplitBlock() {
42
39
  <div key={i} className="size-8 rounded-full border-2 border-primary bg-primary-foreground/20" />
43
40
  ))}
44
41
  </div>
45
- <p>Trusted by 10k+ developers</p>
42
+ <p>Được tin dùng bởi hơn 10k+ nhà phát triển</p>
46
43
  </div>
47
44
  </div>
48
45
 
49
- {/* Right side - Form */}
50
46
  <div className="flex w-full lg:w-1/2 items-center justify-center p-8 sm:p-12">
51
47
  <div className="mx-auto flex w-full max-w-sm flex-col justify-center space-y-6">
52
48
  <div className="flex flex-col space-y-2 text-center lg:text-left">
53
49
  <h1 className="text-3xl font-bold tracking-tight">
54
- {mode === "login" ? "Welcome back" : "Create an account"}
50
+ {mode === "login" ? "Chào mừng trở lại" : "Tạo tài khoản mới"}
55
51
  </h1>
56
52
  <p className="text-sm text-muted-foreground">
57
53
  {mode === "login"
58
- ? "Enter your email below to login to your account"
59
- : "Enter your details below to create your account"}
54
+ ? "Nhập email của bạn bên dưới để đăng nhập vào tài khoản"
55
+ : "Nhập thông tin chi tiết bên dưới để tạo tài khoản của bạn"}
60
56
  </p>
61
57
  </div>
62
58
 
63
59
  <form onSubmit={handleSubmit} className="space-y-4">
64
60
  {mode === "register" && (
65
- <div className="flex flex-col gap-2">
66
- <Label htmlFor="name">Full Name</Label>
67
- <Input id="name" placeholder="John Doe" className="w-full" required />
68
- </div>
61
+ <InputPreset
62
+ id="name"
63
+ label="Họ tên"
64
+ placeholder="Nguyễn Văn A"
65
+ required
66
+ />
69
67
  )}
70
- <div className="flex flex-col gap-2">
71
- <Label htmlFor="email">Email</Label>
72
- <Input id="email" type="email" placeholder="m@example.com" className="w-full" required />
73
- </div>
74
- <div className="flex flex-col gap-2">
75
- <div className="flex items-center justify-between">
76
- <Label htmlFor="password">Password</Label>
77
- {mode === "login" && (
78
- <Button type="button" variant="link" color="primary" className="h-auto p-0 text-xs">
79
- Forgot password?
80
- </Button>
81
- )}
82
- </div>
83
- <Input id="password" type="password" className="w-full" required />
84
- </div>
68
+
69
+ <InputPreset
70
+ id="email"
71
+ type="email"
72
+ label="Email"
73
+ placeholder="m@example.com"
74
+ required
75
+ />
76
+
77
+ <InputPreset
78
+ id="password"
79
+ type="password"
80
+ label={
81
+ <div className="flex items-center justify-between w-full">
82
+ <span>Mật khẩu</span>
83
+ {mode === "login" && (
84
+ <Button type="button" variant="link" color="primary" className="h-auto p-0 text-xs">
85
+ Quên mật khẩu?
86
+ </Button>
87
+ )}
88
+ </div>
89
+ }
90
+ required
91
+ />
85
92
 
86
93
  {mode === "register" && (
87
- <div className="flex items-center gap-2">
88
- <Checkbox id="terms" required />
89
- <Label htmlFor="terms" className="text-sm font-normal text-muted-foreground">
90
- I agree to the <a href="#" className="text-primary hover:underline">terms and conditions</a>
91
- </Label>
92
- </div>
94
+ <CheckboxPreset
95
+ id="terms"
96
+ required
97
+ label={
98
+ <span className="text-sm font-normal text-muted-foreground">
99
+ Tôi đồng ý với các <a href="#" className="text-primary hover:underline">điều khoản và dịch vụ</a>
100
+ </span>
101
+ }
102
+ />
93
103
  )}
94
104
 
95
105
  <Button type="submit" className="w-full" disabled={isLoading}>
96
- {isLoading ? "Loading..." : mode === "login" ? "Sign In" : "Create Account"}
106
+ {isLoading ? "Đang xử lý..." : mode === "login" ? "Đăng nhập" : "Tạo tài khoản"}
97
107
  </Button>
98
108
  </form>
99
109