@authdog/react-elements 0.0.35 → 0.0.36

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/dist/styles.css CHANGED
@@ -665,6 +665,9 @@ video {
665
665
  .mt-1 {
666
666
  margin-top: 0.25rem;
667
667
  }
668
+ .mt-2 {
669
+ margin-top: 0.5rem;
670
+ }
668
671
  .mt-auto {
669
672
  margin-top: auto;
670
673
  }
@@ -871,6 +874,9 @@ video {
871
874
  .items-center {
872
875
  align-items: center;
873
876
  }
877
+ .justify-end {
878
+ justify-content: flex-end;
879
+ }
874
880
  .justify-center {
875
881
  justify-content: center;
876
882
  }
@@ -998,9 +1004,9 @@ video {
998
1004
  .border-none {
999
1005
  border-style: none;
1000
1006
  }
1001
- .border-amber-600 {
1007
+ .border-amber-300 {
1002
1008
  --tw-border-opacity: 1;
1003
- border-color: rgb(217 119 6 / var(--tw-border-opacity, 1));
1009
+ border-color: rgb(252 211 77 / var(--tw-border-opacity, 1));
1004
1010
  }
1005
1011
  .border-border {
1006
1012
  border-color: hsl(var(--border));
@@ -1009,9 +1015,9 @@ video {
1009
1015
  --tw-border-opacity: 1;
1010
1016
  border-color: rgb(187 247 208 / var(--tw-border-opacity, 1));
1011
1017
  }
1012
- .border-green-700 {
1018
+ .border-green-300 {
1013
1019
  --tw-border-opacity: 1;
1014
- border-color: rgb(21 128 61 / var(--tw-border-opacity, 1));
1020
+ border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
1015
1021
  }
1016
1022
  .border-input {
1017
1023
  border-color: hsl(var(--input));
@@ -1019,9 +1025,9 @@ video {
1019
1025
  .border-transparent {
1020
1026
  border-color: transparent;
1021
1027
  }
1022
- .bg-amber-500 {
1028
+ .bg-amber-100 {
1023
1029
  --tw-bg-opacity: 1;
1024
- background-color: rgb(245 158 11 / var(--tw-bg-opacity, 1));
1030
+ background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1));
1025
1031
  }
1026
1032
  .bg-background {
1027
1033
  background-color: hsl(var(--background));
@@ -1062,10 +1068,6 @@ video {
1062
1068
  --tw-bg-opacity: 1;
1063
1069
  background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1));
1064
1070
  }
1065
- .bg-green-600 {
1066
- --tw-bg-opacity: 1;
1067
- background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
1068
- }
1069
1071
  .bg-muted {
1070
1072
  background-color: hsl(var(--muted));
1071
1073
  }
@@ -1117,10 +1119,17 @@ video {
1117
1119
  .p-4 {
1118
1120
  padding: 1rem;
1119
1121
  }
1122
+ .p-6 {
1123
+ padding: 1.5rem;
1124
+ }
1120
1125
  .px-2 {
1121
1126
  padding-left: 0.5rem;
1122
1127
  padding-right: 0.5rem;
1123
1128
  }
1129
+ .px-2\.5 {
1130
+ padding-left: 0.625rem;
1131
+ padding-right: 0.625rem;
1132
+ }
1124
1133
  .px-3 {
1125
1134
  padding-left: 0.75rem;
1126
1135
  padding-right: 0.75rem;
@@ -1231,6 +1240,10 @@ video {
1231
1240
  .tracking-widest {
1232
1241
  letter-spacing: 0.1em;
1233
1242
  }
1243
+ .text-amber-800 {
1244
+ --tw-text-opacity: 1;
1245
+ color: rgb(146 64 14 / var(--tw-text-opacity, 1));
1246
+ }
1234
1247
  .text-blue-600 {
1235
1248
  --tw-text-opacity: 1;
1236
1249
  color: rgb(37 99 235 / var(--tw-text-opacity, 1));
@@ -1271,6 +1284,10 @@ video {
1271
1284
  --tw-text-opacity: 1;
1272
1285
  color: rgb(21 128 61 / var(--tw-text-opacity, 1));
1273
1286
  }
1287
+ .text-green-800 {
1288
+ --tw-text-opacity: 1;
1289
+ color: rgb(22 101 52 / var(--tw-text-opacity, 1));
1290
+ }
1274
1291
  .text-green-900 {
1275
1292
  --tw-text-opacity: 1;
1276
1293
  color: rgb(20 83 45 / var(--tw-text-opacity, 1));
@@ -1458,6 +1475,10 @@ video {
1458
1475
  .hover\:bg-primary\/90:hover {
1459
1476
  background-color: hsl(var(--primary) / 0.9);
1460
1477
  }
1478
+ .hover\:bg-red-50:hover {
1479
+ --tw-bg-opacity: 1;
1480
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
1481
+ }
1461
1482
  .hover\:bg-secondary\/80:hover {
1462
1483
  background-color: hsl(var(--secondary) / 0.8);
1463
1484
  }
@@ -1471,6 +1492,10 @@ video {
1471
1492
  .hover\:text-primary:hover {
1472
1493
  color: hsl(var(--primary));
1473
1494
  }
1495
+ .hover\:text-red-700:hover {
1496
+ --tw-text-opacity: 1;
1497
+ color: rgb(185 28 28 / var(--tw-text-opacity, 1));
1498
+ }
1474
1499
  .hover\:underline:hover {
1475
1500
  text-decoration-line: underline;
1476
1501
  }
@@ -1480,6 +1505,10 @@ video {
1480
1505
  .focus\:bg-accent:focus {
1481
1506
  background-color: hsl(var(--accent));
1482
1507
  }
1508
+ .focus\:bg-red-50:focus {
1509
+ --tw-bg-opacity: 1;
1510
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
1511
+ }
1483
1512
  .focus\:text-accent-foreground:focus {
1484
1513
  color: hsl(var(--accent-foreground));
1485
1514
  }
@@ -1679,24 +1708,20 @@ video {
1679
1708
  .group[data-disabled="true"] .group-data-\[disabled\=true\]\:opacity-50 {
1680
1709
  opacity: 0.5;
1681
1710
  }
1682
- .dark\:border-amber-600:is(.dark *) {
1683
- --tw-border-opacity: 1;
1684
- border-color: rgb(217 119 6 / var(--tw-border-opacity, 1));
1711
+ .dark\:border-amber-400\/40:is(.dark *) {
1712
+ border-color: rgb(251 191 36 / 0.4);
1685
1713
  }
1686
- .dark\:border-green-600:is(.dark *) {
1687
- --tw-border-opacity: 1;
1688
- border-color: rgb(22 163 74 / var(--tw-border-opacity, 1));
1714
+ .dark\:border-green-400\/40:is(.dark *) {
1715
+ border-color: rgb(74 222 128 / 0.4);
1689
1716
  }
1690
- .dark\:bg-amber-500:is(.dark *) {
1691
- --tw-bg-opacity: 1;
1692
- background-color: rgb(245 158 11 / var(--tw-bg-opacity, 1));
1717
+ .dark\:bg-amber-500\/20:is(.dark *) {
1718
+ background-color: rgb(245 158 11 / 0.2);
1693
1719
  }
1694
1720
  .dark\:bg-destructive\/60:is(.dark *) {
1695
1721
  background-color: hsl(var(--destructive) / 0.6);
1696
1722
  }
1697
- .dark\:bg-green-500:is(.dark *) {
1698
- --tw-bg-opacity: 1;
1699
- background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1));
1723
+ .dark\:bg-green-500\/20:is(.dark *) {
1724
+ background-color: rgb(34 197 94 / 0.2);
1700
1725
  }
1701
1726
  .dark\:bg-input\/30:is(.dark *) {
1702
1727
  background-color: hsl(var(--input) / 0.3);
@@ -1705,6 +1730,10 @@ video {
1705
1730
  --tw-bg-opacity: 1;
1706
1731
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
1707
1732
  }
1733
+ .dark\:text-amber-200:is(.dark *) {
1734
+ --tw-text-opacity: 1;
1735
+ color: rgb(253 230 138 / var(--tw-text-opacity, 1));
1736
+ }
1708
1737
  .dark\:text-gray-100:is(.dark *) {
1709
1738
  --tw-text-opacity: 1;
1710
1739
  color: rgb(243 244 246 / var(--tw-text-opacity, 1));
@@ -1717,9 +1746,27 @@ video {
1717
1746
  --tw-text-opacity: 1;
1718
1747
  color: rgb(156 163 175 / var(--tw-text-opacity, 1));
1719
1748
  }
1720
- .dark\:text-white:is(.dark *) {
1749
+ .dark\:text-green-200:is(.dark *) {
1721
1750
  --tw-text-opacity: 1;
1722
- color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1751
+ color: rgb(187 247 208 / var(--tw-text-opacity, 1));
1752
+ }
1753
+ .dark\:text-red-400:is(.dark *) {
1754
+ --tw-text-opacity: 1;
1755
+ color: rgb(248 113 113 / var(--tw-text-opacity, 1));
1756
+ }
1757
+ .dark\:hover\:bg-red-950\/40:hover:is(.dark *) {
1758
+ background-color: rgb(69 10 10 / 0.4);
1759
+ }
1760
+ .dark\:hover\:text-red-300:hover:is(.dark *) {
1761
+ --tw-text-opacity: 1;
1762
+ color: rgb(252 165 165 / var(--tw-text-opacity, 1));
1763
+ }
1764
+ .dark\:focus\:bg-red-950\/40:focus:is(.dark *) {
1765
+ background-color: rgb(69 10 10 / 0.4);
1766
+ }
1767
+ .dark\:focus\:text-red-300:focus:is(.dark *) {
1768
+ --tw-text-opacity: 1;
1769
+ color: rgb(252 165 165 / var(--tw-text-opacity, 1));
1723
1770
  }
1724
1771
  .dark\:focus-visible\:ring-destructive\/40:focus-visible:is(.dark *) {
1725
1772
  --tw-ring-color: hsl(var(--destructive) / 0.4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authdog/react-elements",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",
@@ -25,8 +25,8 @@
25
25
  "ts-loader": "^9.5.1",
26
26
  "typescript": "^5.6.3",
27
27
  "webpack": "^5.89.0",
28
- "@authdog/typescript-config": "0.0.0",
29
- "@authdog/eslint-config": "0.0.0"
28
+ "@authdog/eslint-config": "0.0.0",
29
+ "@authdog/typescript-config": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@radix-ui/react-avatar": "^1.1.9",
@@ -87,8 +87,8 @@ export const UserDropdown = ({ trigger, user, className, onManageAccount, onSign
87
87
  )
88
88
  })}
89
89
  <DropdownMenuSeparator />
90
- <DropdownMenuItem className="cursor-pointer text-red-600 focus:text-red-700 py-2" onClick={() => onSignout?.()}>
91
- <LogOut className="mr-2 h-4 w-4" />
90
+ <DropdownMenuItem className="cursor-pointer py-2 rounded-md font-semibold text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 hover:text-red-700 dark:hover:text-red-300 focus:bg-red-50 dark:focus:bg-red-950/40 focus:text-red-700 dark:focus:text-red-300" onClick={() => onSignout?.()}>
91
+ <LogOut className="mr-2 h-4 w-4 text-red-600 dark:text-red-400" />
92
92
  <span>Sign out</span>
93
93
  </DropdownMenuItem>
94
94
  </DropdownMenuContent>
@@ -4,6 +4,16 @@ import { useEffect, useState } from "react"
4
4
  import { Avatar, AvatarFallback, AvatarImage } from "../../components/ui/avatar"
5
5
  import { Button } from "../../components/ui/button"
6
6
  import { Badge } from "../../components/ui/badge"
7
+ import {
8
+ Card,
9
+ CardHeader,
10
+ CardTitle,
11
+ CardDescription,
12
+ CardContent,
13
+ CardFooter,
14
+ } from "../../components/ui/card"
15
+ import { Input } from "../../components/ui/input"
16
+ import { Label } from "../../components/ui/label"
7
17
  import { User, Shield, SlidersHorizontal, LucideProps } from "lucide-react"
8
18
 
9
19
  export interface UserProfileProps {
@@ -13,6 +23,7 @@ export interface UserProfileProps {
13
23
  handleAuthenticated?: () => void;
14
24
  onRequestEmailVerification?: (email: string) => Promise<{ success: boolean; message?: string } | void>;
15
25
  onVerifyEmail?: (email: string, code: string) => Promise<{ success: boolean; message?: string } | void>;
26
+ onAddEmail?: (email: string) => Promise<{ success: boolean; message?: string } | void>;
16
27
  }
17
28
 
18
29
  export const UserProfile = ({
@@ -21,11 +32,14 @@ export const UserProfile = ({
21
32
  handleAuthenticated,
22
33
  onRequestEmailVerification,
23
34
  onVerifyEmail,
35
+ onAddEmail,
24
36
  }: UserProfileProps) => {
25
37
  const [isMounted, setIsMounted] = useState(false)
26
38
  const [activeTab, setActiveTab] = useState<"profile" | "security" | "preferences">("profile");
27
39
  const [verifyingEmail, setVerifyingEmail] = useState<string | null>(null)
28
40
  const [codeByEmail, setCodeByEmail] = useState<Record<string, string>>({})
41
+ const [addingEmail, setAddingEmail] = useState<boolean>(false)
42
+ const [newEmail, setNewEmail] = useState<string>("")
29
43
 
30
44
  useEffect(() => {
31
45
  setIsMounted(true);
@@ -161,11 +175,11 @@ export const UserProfile = ({
161
175
  <span className="text-foreground">{email.value}</span>
162
176
  <div className="mt-1">
163
177
  {isVerified ? (
164
- <Badge className="text-xs bg-green-600 text-white border-green-700 dark:bg-green-500 dark:text-white dark:border-green-600">
178
+ <Badge className="text-xs rounded-full px-2.5 py-0.5 bg-green-100 text-green-800 border border-green-300 dark:bg-green-500/20 dark:text-green-200 dark:border-green-400/40">
165
179
  Verified
166
180
  </Badge>
167
181
  ) : (
168
- <Badge className="text-xs bg-amber-500 text-white border-amber-600 dark:bg-amber-500 dark:text-white dark:border-amber-600">
182
+ <Badge className="text-xs rounded-full px-2.5 py-0.5 bg-amber-100 text-amber-800 border border-amber-300 dark:bg-amber-500/20 dark:text-amber-200 dark:border-amber-400/40">
169
183
  Not verified
170
184
  </Badge>
171
185
  )}
@@ -225,10 +239,66 @@ export const UserProfile = ({
225
239
  </div>
226
240
  )
227
241
  })}
228
- {/* <Button variant="ghost" size="sm" className="flex items-center text-gray-700">
229
- {renderIcon(PlusCircle)}
230
- Add email address
231
- </Button> */}
242
+ <div className="mt-2">
243
+ {addingEmail ? (
244
+ <div className="max-w-md">
245
+ <Card>
246
+ <CardHeader>
247
+ <CardTitle>Add email address</CardTitle>
248
+ <CardDescription>
249
+ You'll need to verify this email address before it can be added to your account.
250
+ </CardDescription>
251
+ </CardHeader>
252
+ <CardContent>
253
+ <div className="space-y-2">
254
+ <Label htmlFor="new-email">Email address</Label>
255
+ <Input
256
+ id="new-email"
257
+ placeholder="Enter your email address"
258
+ value={newEmail}
259
+ onChange={(e) => setNewEmail(e.target.value)}
260
+ onKeyDown={async (e) => {
261
+ if (e.key === "Enter") {
262
+ const v = String(newEmail || "").trim().toLowerCase()
263
+ if (!v) return
264
+ if (onAddEmail) await onAddEmail(v)
265
+ setAddingEmail(false)
266
+ setNewEmail("")
267
+ }
268
+ }}
269
+ />
270
+ </div>
271
+ </CardContent>
272
+ <CardFooter className="justify-end gap-2">
273
+ <Button
274
+ variant="ghost"
275
+ onClick={() => {
276
+ setAddingEmail(false)
277
+ setNewEmail("")
278
+ }}
279
+ >
280
+ Cancel
281
+ </Button>
282
+ <Button
283
+ onClick={async () => {
284
+ const v = String(newEmail || "").trim().toLowerCase()
285
+ if (!v) return
286
+ if (onAddEmail) await onAddEmail(v)
287
+ setAddingEmail(false)
288
+ setNewEmail("")
289
+ }}
290
+ >
291
+ Add
292
+ </Button>
293
+ </CardFooter>
294
+ </Card>
295
+ </div>
296
+ ) : (
297
+ <Button variant="outline" size="sm" className="text-xs" onClick={() => setAddingEmail(true)}>
298
+ Add email
299
+ </Button>
300
+ )}
301
+ </div>
232
302
  </div>
233
303
  </div>
234
304
 
@@ -1,67 +1,46 @@
1
- import type { Story } from '@ladle/react';
2
- import { UserProfile, UserProfileProps } from '../components/core/user-profile';
3
- import "../global.css";
1
+ import type { StoryDefault, Story } from "@ladle/react"
2
+ import { UserProfile } from "../components/core/user-profile"
3
+ import "../global.css"
4
4
 
5
- export const Default: Story = () => (
6
- <UserProfile
7
- user={{
8
- id: "123",
9
- displayName: "Jaylon Dias",
10
- emails: [{ value: "example@authdog.xyz" }],
11
- photos: [{ value: "https://i.pravatar.cc/150?u=a042581f4e29026704d" }],
12
- provider: "authdog",
13
- }}
14
- loading={false}
15
- handleAuthenticated={() => {}}
16
- emails={[{ address: "example@authdog.xyz", isPrimary: true }]}
17
- />
18
- );
19
- Default.storyName = 'Default User Profile';
5
+ export default {
6
+ title: "Core/UserProfile",
7
+ } satisfies StoryDefault
20
8
 
21
- export const WithCustomUser: Story = () => (
22
- <UserProfile
23
- user={{
24
- id: "123",
25
- displayName: "Jaylon Dias",
26
- emails: [{ value: "example@authdog.xyz" }],
27
- photos: [{ value: "https://i.pravatar.cc/150?u=a042581f4e29026704d" }],
28
- provider: "authdog",
29
- }}
30
- loading={false}
31
- handleAuthenticated={() => {}}
32
- emails={[{ address: "example@authdog.xyz", isPrimary: true }]}
33
- />
34
- );
35
- WithCustomUser.storyName = 'User Profile with Custom User';
9
+ const baseUser: any = {
10
+ id: "user_123",
11
+ displayName: "Jane Doe",
12
+ provider: "google-oauth20",
13
+ emails: [{ id: "e1", value: "jane.primary@example.com" }],
14
+ verifications: [],
15
+ photos: [],
16
+ }
36
17
 
37
- export const WithMultipleEmails: Story = () => (
38
- <UserProfile
39
- user={{
40
- id: "123",
41
- displayName: "Jaylon Dias",
42
- emails: [{ value: "example@authdog.xyz" }, { value: "example@gmail.com" }],
43
- photos: [{ value: "https://i.pravatar.cc/150?u=a042581f4e29026704d" }],
44
- provider: "authdog",
45
- }}
46
- loading={false}
47
- handleAuthenticated={() => {}}
48
- emails={[{ address: "example@authdog.xyz", isPrimary: true }, { address: "example@gmail.com", isPrimary: false }]}
49
- />
50
- );
51
- WithMultipleEmails.storyName = 'User Profile with Multiple Emails';
18
+ export const EmailNotVerified: Story = () => {
19
+ return (
20
+ <div className="p-6 bg-background text-foreground">
21
+ <UserProfile loading={false} user={{ ...baseUser }} />
22
+ </div>
23
+ )
24
+ }
25
+ EmailNotVerified.storyName = "Email not verified"
52
26
 
53
- export const WithConnectedAccounts: Story = () => (
54
- <UserProfile
55
- user={{
56
- id: "123",
57
- displayName: "Alex Johnson",
58
- emails: [{ value: "alex@example.com" }],
59
- photos: [{ value: "https://i.pravatar.cc/150?u=a042581f4e29026704d" }],
60
- provider: "authdog",
61
- }}
62
- loading={false}
63
- handleAuthenticated={() => {}}
64
- emails={[{ address: "alex@example.com", isPrimary: true }]}
65
- />
66
- );
67
- WithConnectedAccounts.storyName = 'User Profile with Connected Accounts';
27
+ export const EmailVerified: Story = () => {
28
+ const verifiedUser = {
29
+ ...baseUser,
30
+ verifications: [
31
+ {
32
+ id: "v1",
33
+ email: "jane.primary@example.com",
34
+ verified: true,
35
+ createdAt: new Date().toISOString(),
36
+ updatedAt: new Date().toISOString(),
37
+ },
38
+ ],
39
+ }
40
+ return (
41
+ <div className="p-6 bg-background text-foreground">
42
+ <UserProfile loading={false} user={verifiedUser} />
43
+ </div>
44
+ )
45
+ }
46
+ EmailVerified.storyName = "Email verified"