@authdog/react-elements 0.0.39 → 0.0.42

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 (74) hide show
  1. package/.eslintrc.js +1 -1
  2. package/.storybook/main.ts +21 -0
  3. package/.storybook/preview.ts +17 -0
  4. package/.storybook/vitest.setup.ts +7 -0
  5. package/.turbo/turbo-build.log +45 -42
  6. package/CHANGELOG.md +18 -0
  7. package/dist/components/ui/alert.js.map +1 -1
  8. package/dist/components/ui/alert.mjs.map +1 -1
  9. package/dist/components/ui/avatar.js.map +1 -1
  10. package/dist/components/ui/avatar.mjs.map +1 -1
  11. package/dist/components/ui/badge.js.map +1 -1
  12. package/dist/components/ui/badge.mjs.map +1 -1
  13. package/dist/components/ui/card.js.map +1 -1
  14. package/dist/components/ui/card.mjs.map +1 -1
  15. package/dist/components/ui/dropdown-menu.js +1 -1
  16. package/dist/components/ui/dropdown-menu.js.map +1 -1
  17. package/dist/components/ui/dropdown-menu.mjs +1 -1
  18. package/dist/components/ui/dropdown-menu.mjs.map +1 -1
  19. package/dist/components/ui/input.js.map +1 -1
  20. package/dist/components/ui/input.mjs.map +1 -1
  21. package/dist/components/ui/label.js.map +1 -1
  22. package/dist/components/ui/label.mjs.map +1 -1
  23. package/dist/components/ui/separator.js.map +1 -1
  24. package/dist/components/ui/separator.mjs.map +1 -1
  25. package/dist/components/ui/sheet.js.map +1 -1
  26. package/dist/components/ui/sheet.mjs.map +1 -1
  27. package/dist/index.d.mts +3 -1
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +1 -1
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/styles.css +218 -8
  34. package/package.json +25 -14
  35. package/src/components/core/client-only.tsx +10 -15
  36. package/src/components/core/navbar.tsx +81 -50
  37. package/src/components/core/placeholder-alert.tsx +7 -9
  38. package/src/components/core/user-dropdown.tsx +117 -57
  39. package/src/components/core/user-profile.tsx +180 -86
  40. package/src/components/flow/login.tsx +42 -29
  41. package/src/components/flow/totp-validator.tsx +94 -73
  42. package/src/components/icons.tsx +15 -21
  43. package/src/components/ui/alert.tsx +11 -11
  44. package/src/components/ui/avatar.tsx +10 -10
  45. package/src/components/ui/badge.tsx +9 -9
  46. package/src/components/ui/card.tsx +13 -13
  47. package/src/components/ui/dropdown-menu.tsx +214 -211
  48. package/src/components/ui/input.tsx +5 -5
  49. package/src/components/ui/label.tsx +7 -7
  50. package/src/components/ui/separator.tsx +7 -7
  51. package/src/components/ui/sheet.tsx +21 -21
  52. package/src/index.ts +6 -6
  53. package/src/stories/core/Navbar.stories.tsx +45 -0
  54. package/src/stories/core/PlaceholderAlert.stories.tsx +23 -0
  55. package/src/stories/core/UserDropdown.stories.tsx +56 -0
  56. package/src/stories/core/UserProfile.stories.tsx +47 -0
  57. package/src/stories/flow/LoginForm.stories.tsx +20 -0
  58. package/src/stories/flow/TotpValidator.stories.tsx +23 -0
  59. package/src/stories/showcase/Landing.stories.tsx +376 -0
  60. package/src/stories/ui/Button.stories.tsx +45 -0
  61. package/tsup.config.ts +6 -9
  62. package/vitest.config.ts +39 -0
  63. package/vitest.shims.d.ts +1 -0
  64. package/wrangler.prod.toml +4 -0
  65. package/ladle.config.mjs +0 -21
  66. package/src/main.tsx +0 -11
  67. package/src/preview.tsx +0 -11
  68. package/src/stories/Button._stories.tsx +0 -24
  69. package/src/stories/LoginForm.stories.tsx +0 -29
  70. package/src/stories/Navbar._stories.tsx +0 -28
  71. package/src/stories/PlaceholderAlert._stories.tsx +0 -13
  72. package/src/stories/TotpValidator.stories.tsx +0 -14
  73. package/src/stories/UserDropdown.stories.tsx +0 -36
  74. package/src/stories/UserProfile.stories.tsx +0 -46
@@ -1,93 +1,87 @@
1
- "use client"
1
+ "use client";
2
2
 
3
- import type React from "react"
3
+ import type React from "react";
4
4
 
5
- import { useState, useRef } from "react"
6
- import { Button } from "../../components/ui/button"
7
- import { Card, CardContent } from "../../components/ui/card"
8
- import { Alert, AlertDescription } from "../../components/ui/alert"
9
- import { Shield, CheckCircle, AlertCircle } from "lucide-react"
5
+ import { useState, useRef } from "react";
6
+ import { Button } from "../../components/ui/button";
7
+ import { Card, CardContent } from "../../components/ui/card";
8
+ import { Alert, AlertDescription } from "../../components/ui/alert";
9
+ import { Shield, CheckCircle, AlertCircle } from "lucide-react";
10
10
 
11
11
  interface TOTPValidatorProps {
12
- onValidate: (code: string) => Promise<void>;
12
+ onValidate: (code: string) => Promise<void>;
13
13
  }
14
14
 
15
- export const TOTPValidator = (
16
- {
17
- onValidate
18
- }: TOTPValidatorProps
19
- ) => {
20
- const [code, setCode] = useState(["", "", "", "", "", ""])
21
- const [loading, setLoading] = useState(false)
22
- const [error, setError] = useState("")
23
- const [success, setSuccess] = useState(false)
24
- const inputRefs = useRef<(HTMLInputElement | null)[]>([])
15
+ export const TOTPValidator = ({ onValidate }: TOTPValidatorProps) => {
16
+ const [code, setCode] = useState(["", "", "", "", "", ""]);
17
+ const [loading, setLoading] = useState(false);
18
+ const [error, setError] = useState("");
19
+ const [success, setSuccess] = useState(false);
20
+ const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
25
21
 
26
22
  const handleInputChange = (index: number, value: string) => {
27
23
  // Only allow digits
28
- if (!/^\d*$/.test(value)) return
24
+ if (!/^\d*$/.test(value)) return;
29
25
 
30
- const newCode = [...code]
31
- newCode[index] = value.slice(-1) // Only take the last character
26
+ const newCode = [...code];
27
+ newCode[index] = value.slice(-1); // Only take the last character
32
28
 
33
- setCode(newCode)
34
- setError("")
35
- setSuccess(false)
29
+ setCode(newCode);
30
+ setError("");
31
+ setSuccess(false);
36
32
 
37
33
  // Auto-focus next input
38
34
  if (value && index < 5) {
39
- inputRefs.current[index + 1]?.focus()
35
+ inputRefs.current[index + 1]?.focus();
40
36
  }
41
- }
37
+ };
42
38
 
43
39
  const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
44
40
  // Handle backspace
45
41
  if (e.key === "Backspace" && !code[index] && index > 0) {
46
- inputRefs.current[index - 1]?.focus()
42
+ inputRefs.current[index - 1]?.focus();
47
43
  }
48
44
 
49
45
  // Handle paste
50
46
  if (e.key === "v" && (e.ctrlKey || e.metaKey)) {
51
- e.preventDefault()
47
+ e.preventDefault();
52
48
  navigator.clipboard.readText().then((text) => {
53
- const digits = text.replace(/\D/g, "").slice(0, 6).split("")
54
- const newCode = [...code]
49
+ const digits = text.replace(/\D/g, "").slice(0, 6).split("");
50
+ const newCode = [...code];
55
51
  digits.forEach((digit, i) => {
56
- if (i < 6) newCode[i] = digit
57
- })
58
- setCode(newCode)
52
+ if (i < 6) newCode[i] = digit;
53
+ });
54
+ setCode(newCode);
59
55
 
60
56
  // Focus the next empty input or the last one
61
- const nextIndex = Math.min(digits.length, 5)
62
- inputRefs.current[nextIndex]?.focus()
63
- })
57
+ const nextIndex = Math.min(digits.length, 5);
58
+ inputRefs.current[nextIndex]?.focus();
59
+ });
64
60
  }
65
- }
61
+ };
66
62
 
67
63
  const validateTOTP = async () => {
68
- const totpCode = code.join("")
64
+ const totpCode = code.join("");
69
65
 
70
66
  if (totpCode.length !== 6) {
71
- setError("Please enter all 6 digits")
72
- return
67
+ setError("Please enter all 6 digits");
68
+ return;
73
69
  }
74
70
 
75
- setLoading(true)
76
- setError("")
71
+ setLoading(true);
72
+ setError("");
77
73
 
78
74
  try {
79
- await onValidate(totpCode)
80
- setSuccess(true)
75
+ await onValidate(totpCode);
76
+ setSuccess(true);
81
77
  } catch (error) {
82
- setError("Invalid TOTP code. Please try again.")
78
+ setError("Invalid TOTP code. Please try again.");
83
79
  } finally {
84
- setLoading(false)
80
+ setLoading(false);
85
81
  }
86
82
 
87
-
88
-
89
83
  // try {
90
- // Call your TOTP validation endpoint
84
+ // Call your TOTP validation endpoint
91
85
  // const response = await fetch("/api/validate-totp", {
92
86
  // method: "POST",
93
87
  // headers: {
@@ -126,20 +120,20 @@ export const TOTPValidator = (
126
120
  // } finally {
127
121
  // setLoading(false)
128
122
  // }
129
- }
123
+ };
130
124
 
131
125
  const handleSubmit = (e: React.FormEvent) => {
132
- e.preventDefault()
133
- validateTOTP()
134
- }
126
+ e.preventDefault();
127
+ validateTOTP();
128
+ };
135
129
 
136
130
  const clearCode = () => {
137
- setCode(["", "", "", "", "", ""])
138
- setError("")
139
- setSuccess(false)
140
- setLoading(false)
141
- inputRefs.current[0]?.focus()
142
- }
131
+ setCode(["", "", "", "", "", ""]);
132
+ setError("");
133
+ setSuccess(false);
134
+ setLoading(false);
135
+ inputRefs.current[0]?.focus();
136
+ };
143
137
 
144
138
  if (success) {
145
139
  return (
@@ -151,17 +145,25 @@ export const TOTPValidator = (
151
145
  <CheckCircle className="w-8 h-8 text-green-600" />
152
146
  </div>
153
147
  <div>
154
- <h3 className="text-lg font-semibold text-green-900">Verification Successful!</h3>
155
- <p className="text-sm text-green-700 mt-1">Your TOTP code has been validated.</p>
148
+ <h3 className="text-lg font-semibold text-green-900">
149
+ Verification Successful!
150
+ </h3>
151
+ <p className="text-sm text-green-700 mt-1">
152
+ Your TOTP code has been validated.
153
+ </p>
156
154
  </div>
157
- <Button onClick={clearCode} variant="outline" className="bg-white">
155
+ <Button
156
+ onClick={clearCode}
157
+ variant="outline"
158
+ className="bg-white"
159
+ >
158
160
  Verify Another Code
159
161
  </Button>
160
162
  </div>
161
163
  </CardContent>
162
164
  </Card>
163
165
  </div>
164
- )
166
+ );
165
167
  }
166
168
 
167
169
  return (
@@ -174,29 +176,36 @@ export const TOTPValidator = (
174
176
  <Shield className="w-6 h-6 text-blue-600" />
175
177
  </div>
176
178
  <h3 className="text-lg font-semibold">Enter Verification Code</h3>
177
- <p className="text-sm text-muted-foreground mt-1">Enter the 6-digit code from your authenticator app</p>
179
+ <p className="text-sm text-muted-foreground mt-1">
180
+ Enter the 6-digit code from your authenticator app
181
+ </p>
178
182
  </div>
179
183
 
180
184
  <form onSubmit={handleSubmit} className="space-y-6">
181
185
  <div className="flex justify-center gap-2">
182
186
  {code.map((digit, index) => (
183
- <Card key={index} className="w-12 h-14 border-2 focus-within:border-blue-500 transition-colors">
187
+ <Card
188
+ key={index}
189
+ className="w-12 h-14 border-2 focus-within:border-blue-500 transition-colors"
190
+ >
184
191
  <CardContent className="p-0 h-full flex items-center justify-center">
185
192
  <input
186
193
  ref={(el) => {
187
- inputRefs.current[index] = el
194
+ inputRefs.current[index] = el;
188
195
  }}
189
196
  type="tel"
190
197
  inputMode="numeric"
191
198
  maxLength={1}
192
199
  value={digit}
193
- onChange={(e) => handleInputChange(index, e.target.value)}
200
+ onChange={(e) =>
201
+ handleInputChange(index, e.target.value)
202
+ }
194
203
  onKeyDown={(e) => handleKeyDown(index, e)}
195
204
  className="w-full h-full text-center text-2xl font-bold border-none outline-none bg-transparent"
196
205
  autoComplete="one-time-code"
197
206
  disabled={loading}
198
207
  style={{
199
- height: 'auto'
208
+ height: "auto",
200
209
  }}
201
210
  />
202
211
  </CardContent>
@@ -212,20 +221,32 @@ export const TOTPValidator = (
212
221
  )}
213
222
 
214
223
  <div className="space-y-3">
215
- <Button type="submit" className="w-full" disabled={loading || code.some((digit) => digit === "")}>
224
+ <Button
225
+ type="submit"
226
+ className="w-full"
227
+ disabled={loading || code.some((digit) => digit === "")}
228
+ >
216
229
  {loading ? "Verifying..." : "Verify Code"}
217
230
  </Button>
218
231
 
219
- <Button type="button" variant="ghost" size="sm" onClick={clearCode} className="w-full text-xs">
232
+ <Button
233
+ type="button"
234
+ variant="ghost"
235
+ size="sm"
236
+ onClick={clearCode}
237
+ className="w-full text-xs"
238
+ >
220
239
  Clear Code
221
240
  </Button>
222
241
  </div>
223
242
  </form>
224
243
 
225
- <p className="text-xs text-muted-foreground">Codes refresh every 30 seconds</p>
244
+ <p className="text-xs text-muted-foreground">
245
+ Codes refresh every 30 seconds
246
+ </p>
226
247
  </div>
227
248
  </CardContent>
228
249
  </Card>
229
250
  </div>
230
- )
231
- }
251
+ );
252
+ };
@@ -1,31 +1,25 @@
1
- import { LucideProps } from "lucide-react"
2
- import { useEffect, useState } from "react"
1
+ import { LucideProps } from "lucide-react";
2
+ import { useEffect, useState } from "react";
3
3
 
4
4
  const iconProps: LucideProps = {
5
5
  className: "mr-2 h-4 w-4",
6
- "aria-hidden": "true"
7
- }
6
+ "aria-hidden": "true",
7
+ };
8
8
 
9
- export const renderIcon = ((Icon: any) => {
10
- const [isMounted, setIsMounted] = useState(false)
9
+ export const IconWrapper = ({ Icon }: { Icon: any }) => {
10
+ const [isMounted, setIsMounted] = useState(false);
11
11
 
12
12
  useEffect(() => {
13
- setIsMounted(true)
14
- }, [])
15
-
16
- if (!isMounted) {
17
- return <span className="mr-2 h-4 w-4" aria-hidden="true" />
18
- }
13
+ setIsMounted(true);
14
+ }, []);
19
15
 
20
- return <Icon {...iconProps} />
21
- }) as React.FC<{
22
- Icon: any
23
- }>
24
-
25
- export const IconWrapper = ({ Icon }: { Icon: any }) => {
26
16
  return (
27
17
  <span className="inline-flex items-center justify-center">
28
- {renderIcon(Icon)}
18
+ {!isMounted ? (
19
+ <span className="mr-2 h-4 w-4" aria-hidden="true" />
20
+ ) : (
21
+ <Icon {...iconProps} />
22
+ )}
29
23
  </span>
30
- )
31
- }
24
+ );
25
+ };
@@ -1,7 +1,7 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
3
 
4
- import { cn } from "@authdog/react-elements/lib/utils"
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const alertVariants = cva(
7
7
  "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
@@ -16,8 +16,8 @@ const alertVariants = cva(
16
16
  defaultVariants: {
17
17
  variant: "default",
18
18
  },
19
- }
20
- )
19
+ },
20
+ );
21
21
 
22
22
  function Alert({
23
23
  className,
@@ -31,7 +31,7 @@ function Alert({
31
31
  className={cn(alertVariants({ variant }), className)}
32
32
  {...props}
33
33
  />
34
- )
34
+ );
35
35
  }
36
36
 
37
37
  function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
40
40
  data-slot="alert-title"
41
41
  className={cn(
42
42
  "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
- className
43
+ className,
44
44
  )}
45
45
  {...props}
46
46
  />
47
- )
47
+ );
48
48
  }
49
49
 
50
50
  function AlertDescription({
@@ -56,11 +56,11 @@ function AlertDescription({
56
56
  data-slot="alert-description"
57
57
  className={cn(
58
58
  "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
59
- className
59
+ className,
60
60
  )}
61
61
  {...props}
62
62
  />
63
- )
63
+ );
64
64
  }
65
65
 
66
- export { Alert, AlertTitle, AlertDescription }
66
+ export { Alert, AlertTitle, AlertDescription };
@@ -1,9 +1,9 @@
1
- "use client"
1
+ "use client";
2
2
 
3
- import * as React from "react"
4
- import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+ import * as React from "react";
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
5
 
6
- import { cn } from "@authdog/react-elements/lib/utils"
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  function Avatar({
9
9
  className,
@@ -14,11 +14,11 @@ function Avatar({
14
14
  data-slot="avatar"
15
15
  className={cn(
16
16
  "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
- className
17
+ className,
18
18
  )}
19
19
  {...props}
20
20
  />
21
- )
21
+ );
22
22
  }
23
23
 
24
24
  function AvatarImage({
@@ -31,7 +31,7 @@ function AvatarImage({
31
31
  className={cn("aspect-square size-full", className)}
32
32
  {...props}
33
33
  />
34
- )
34
+ );
35
35
  }
36
36
 
37
37
  function AvatarFallback({
@@ -43,11 +43,11 @@ function AvatarFallback({
43
43
  data-slot="avatar-fallback"
44
44
  className={cn(
45
45
  "bg-muted flex size-full items-center justify-center rounded-full",
46
- className
46
+ className,
47
47
  )}
48
48
  {...props}
49
49
  />
50
- )
50
+ );
51
51
  }
52
52
 
53
- export { Avatar, AvatarImage, AvatarFallback }
53
+ export { Avatar, AvatarImage, AvatarFallback };
@@ -1,8 +1,8 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { cva, type VariantProps } from "class-variance-authority"
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
4
 
5
- import { cn } from "@authdog/react-elements/lib/utils"
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const badgeVariants = cva(
8
8
  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
@@ -22,8 +22,8 @@ const badgeVariants = cva(
22
22
  defaultVariants: {
23
23
  variant: "default",
24
24
  },
25
- }
26
- )
25
+ },
26
+ );
27
27
 
28
28
  function Badge({
29
29
  className,
@@ -32,7 +32,7 @@ function Badge({
32
32
  ...props
33
33
  }: React.ComponentProps<"span"> &
34
34
  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
35
- const Comp = asChild ? Slot : "span"
35
+ const Comp = asChild ? Slot : "span";
36
36
 
37
37
  return (
38
38
  <Comp
@@ -40,7 +40,7 @@ function Badge({
40
40
  className={cn(badgeVariants({ variant }), className)}
41
41
  {...props}
42
42
  />
43
- )
43
+ );
44
44
  }
45
45
 
46
- export { Badge, badgeVariants }
46
+ export { Badge, badgeVariants };
@@ -1,6 +1,6 @@
1
- import * as React from "react"
1
+ import * as React from "react";
2
2
 
3
- import { cn } from "@authdog/react-elements/lib/utils"
3
+ import { cn } from "../../lib/utils";
4
4
 
5
5
  function Card({ className, ...props }: React.ComponentProps<"div">) {
6
6
  return (
@@ -8,11 +8,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
8
8
  data-slot="card"
9
9
  className={cn(
10
10
  "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
- className
11
+ className,
12
12
  )}
13
13
  {...props}
14
14
  />
15
- )
15
+ );
16
16
  }
17
17
 
18
18
  function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -21,11 +21,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
21
21
  data-slot="card-header"
22
22
  className={cn(
23
23
  "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
- className
24
+ className,
25
25
  )}
26
26
  {...props}
27
27
  />
28
- )
28
+ );
29
29
  }
30
30
 
31
31
  function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
35
35
  className={cn("leading-none font-semibold", className)}
36
36
  {...props}
37
37
  />
38
- )
38
+ );
39
39
  }
40
40
 
41
41
  function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
@@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
45
45
  className={cn("text-muted-foreground text-sm", className)}
46
46
  {...props}
47
47
  />
48
- )
48
+ );
49
49
  }
50
50
 
51
51
  function CardAction({ className, ...props }: React.ComponentProps<"div">) {
@@ -54,11 +54,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
54
54
  data-slot="card-action"
55
55
  className={cn(
56
56
  "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
- className
57
+ className,
58
58
  )}
59
59
  {...props}
60
60
  />
61
- )
61
+ );
62
62
  }
63
63
 
64
64
  function CardContent({ className, ...props }: React.ComponentProps<"div">) {
@@ -68,7 +68,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
68
68
  className={cn("px-6", className)}
69
69
  {...props}
70
70
  />
71
- )
71
+ );
72
72
  }
73
73
 
74
74
  function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -78,7 +78,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
78
78
  className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
79
  {...props}
80
80
  />
81
- )
81
+ );
82
82
  }
83
83
 
84
84
  export {
@@ -89,4 +89,4 @@ export {
89
89
  CardAction,
90
90
  CardDescription,
91
91
  CardContent,
92
- }
92
+ };