@authdog/react-elements 0.0.40 → 0.0.43

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 (72) 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 +48 -44
  6. package/CHANGELOG.md +19 -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/components/ui/theme-toggle.js +3 -0
  28. package/dist/components/ui/theme-toggle.js.map +1 -0
  29. package/dist/components/ui/theme-toggle.mjs +3 -0
  30. package/dist/components/ui/theme-toggle.mjs.map +1 -0
  31. package/dist/index.d.mts +5 -2
  32. package/dist/index.d.ts +5 -2
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +1 -1
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/styles.css +314 -20
  38. package/package.json +23 -12
  39. package/src/components/core/navbar.tsx +145 -112
  40. package/src/components/core/user-dropdown.tsx +22 -4
  41. package/src/components/icons.tsx +6 -12
  42. package/src/components/ui/alert.tsx +1 -1
  43. package/src/components/ui/avatar.tsx +1 -1
  44. package/src/components/ui/badge.tsx +1 -1
  45. package/src/components/ui/card.tsx +1 -1
  46. package/src/components/ui/dropdown-menu.tsx +198 -197
  47. package/src/components/ui/input.tsx +1 -1
  48. package/src/components/ui/label.tsx +1 -1
  49. package/src/components/ui/separator.tsx +1 -1
  50. package/src/components/ui/sheet.tsx +1 -1
  51. package/src/components/ui/theme-toggle.tsx +55 -0
  52. package/src/stories/core/Navbar.stories.tsx +45 -0
  53. package/src/stories/core/PlaceholderAlert.stories.tsx +23 -0
  54. package/src/stories/core/UserDropdown.stories.tsx +56 -0
  55. package/src/stories/core/UserProfile.stories.tsx +47 -0
  56. package/src/stories/flow/LoginForm.stories.tsx +20 -0
  57. package/src/stories/flow/TotpValidator.stories.tsx +23 -0
  58. package/src/stories/showcase/Landing.stories.tsx +376 -0
  59. package/src/stories/ui/Button.stories.tsx +45 -0
  60. package/vitest.config.ts +39 -0
  61. package/vitest.shims.d.ts +1 -0
  62. package/wrangler.prod.toml +4 -0
  63. package/ladle.config.mjs +0 -21
  64. package/src/main.tsx +0 -9
  65. package/src/preview.tsx +0 -7
  66. package/src/stories/Button._stories.tsx +0 -28
  67. package/src/stories/LoginForm.stories.tsx +0 -29
  68. package/src/stories/Navbar._stories.tsx +0 -66
  69. package/src/stories/PlaceholderAlert._stories.tsx +0 -13
  70. package/src/stories/TotpValidator.stories.tsx +0 -16
  71. package/src/stories/UserDropdown.stories.tsx +0 -34
  72. package/src/stories/UserProfile.stories.tsx +0 -46
@@ -22,6 +22,7 @@ import {
22
22
  } from "../../components/ui/dropdown-menu";
23
23
  import { Sheet, SheetContent, SheetTrigger } from "../../components/ui/sheet";
24
24
  import { IconWrapper } from "../icons";
25
+ import { ThemeToggle } from "../ui/theme-toggle";
25
26
 
26
27
  interface NavItem {
27
28
  title: string;
@@ -34,6 +35,7 @@ interface NavbarProps {
34
35
  children?: React.ReactNode;
35
36
  className?: string;
36
37
  logoText?: string;
38
+ logoSrc?: string;
37
39
  isLoading?: boolean;
38
40
  user?: any;
39
41
  onNavigateHome?: () => void;
@@ -52,6 +54,7 @@ export function Navbar({
52
54
  children,
53
55
  className,
54
56
  logoText = "ACME Corp",
57
+ logoSrc,
55
58
  user = {
56
59
  name: "John Doe",
57
60
  email: "john@example.com",
@@ -66,22 +69,54 @@ export function Navbar({
66
69
  environmentId = "58be35b0-708f-49f6-84f0-6695d307d997",
67
70
  }: NavbarProps) {
68
71
  const [open, setOpen] = useState(false);
72
+ const [logoFailed, setLogoFailed] = useState(false);
69
73
  const isAuthenticated =
70
74
  user !== null &&
71
75
  user !== undefined &&
72
76
  user.id !== null &&
73
77
  user.id !== undefined;
78
+
74
79
  return (
75
- <header className={cn("border-b bg-background", className)}>
76
- <div className="container flex h-16 items-center justify-between px-4 md:px-6">
77
- <div className="flex items-center gap-4">
78
- <span
79
- className="text-xl font-bold cursor-pointer"
80
+ <header
81
+ className={cn(
82
+ "sticky top-0 z-40 w-full border-b bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60",
83
+ className,
84
+ )}
85
+ >
86
+ <div
87
+ className={cn(
88
+ "flex h-16 items-center justify-between px-4 md:px-6",
89
+ "w-full lg:max-w-[80vw] mx-auto",
90
+ )}
91
+ >
92
+ <div className="flex items-center gap-3 md:gap-4">
93
+ <button
94
+ type="button"
80
95
  onClick={onNavigateHome}
96
+ className={cn(
97
+ "group inline-flex items-center gap-2 md:gap-3 rounded-md px-1 py-1 text-left",
98
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
99
+ )}
100
+ aria-label="Go to homepage"
81
101
  >
82
- {logoText}
83
- </span>
84
- <nav className="hidden md:flex gap-6">
102
+ {logoSrc && !logoFailed && (
103
+ <span className="inline-flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-muted/80 ring-1 ring-border">
104
+ <img
105
+ src={logoSrc}
106
+ alt={logoText}
107
+ className="h-7 w-7 object-contain"
108
+ onError={() => setLogoFailed(true)}
109
+ />
110
+ </span>
111
+ )}
112
+ <span className="text-base font-semibold tracking-tight md:text-lg group-hover:text-primary">
113
+ {logoText}
114
+ </span>
115
+ </button>
116
+ {children}
117
+ </div>
118
+ <div className="flex flex-1 items-center justify-end gap-6">
119
+ <nav className="hidden md:flex items-center gap-6">
85
120
  {items?.map((item, index) => (
86
121
  <span
87
122
  key={index}
@@ -99,119 +134,117 @@ export function Navbar({
99
134
  </span>
100
135
  ))}
101
136
  </nav>
102
- </div>
103
- <div className="flex items-center gap-4">
104
- {children}
105
-
106
- {isAuthenticated ? (
107
- <DropdownMenu>
108
- <DropdownMenuTrigger asChild>
137
+ <div className="flex items-center gap-3">
138
+ <Sheet open={open} onOpenChange={setOpen}>
139
+ <SheetTrigger asChild>
109
140
  <Button
110
141
  variant="ghost"
111
- className="relative h-8 w-8 rounded-full"
112
- disabled={isLoading}
142
+ size="icon"
143
+ className="md:hidden"
144
+ aria-label="Open Menu"
113
145
  >
114
- <Avatar className="h-8 w-8">
115
- {isLoading ? (
116
- <div className="h-8 w-8 animate-pulse bg-muted rounded-full" />
117
- ) : (
118
- <>
119
- <AvatarImage
120
- src={user.photos?.[0]?.value || "/placeholder.svg"}
121
- alt={user.displayName}
122
- />
123
- <AvatarFallback>
124
- {user.displayName?.charAt(0)}
125
- </AvatarFallback>
126
- </>
127
- )}
128
- </Avatar>
146
+ <IconWrapper Icon={Menu} />
129
147
  </Button>
130
- </DropdownMenuTrigger>
131
- <DropdownMenuContent className="w-56" align="end" forceMount>
132
- {isLoading ? (
133
- <div className="p-4">
134
- <div className="h-4 w-3/4 animate-pulse bg-muted rounded mb-2" />
135
- <div className="h-3 w-1/2 animate-pulse bg-muted rounded" />
136
- </div>
137
- ) : (
138
- <>
139
- <DropdownMenuLabel className="font-normal">
140
- <div className="flex flex-col space-y-1">
141
- <p className="text-sm font-medium leading-none">
142
- {user.displayName}
143
- </p>
144
- <p className="text-xs leading-none text-muted-foreground">
145
- {user.emails?.[0]?.value}
146
- </p>
147
- </div>
148
- </DropdownMenuLabel>
149
- <DropdownMenuSeparator />
150
- <DropdownMenuGroup>
151
- <DropdownMenuItem onClick={onProfileSelected}>
152
- <IconWrapper Icon={User} />
153
- <span>Profile</span>
148
+ </SheetTrigger>
149
+ <SheetContent side="left" className="pr-0">
150
+ <nav className="grid gap-2 py-6">
151
+ {items?.map((item, index) => (
152
+ <a
153
+ key={index}
154
+ href={item.href}
155
+ className={cn(
156
+ "flex w-full items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent",
157
+ item.disabled && "cursor-not-allowed opacity-80",
158
+ )}
159
+ onClick={() => setOpen(false)}
160
+ >
161
+ {item.title}
162
+ </a>
163
+ ))}
164
+ </nav>
165
+ </SheetContent>
166
+ </Sheet>
167
+ <ThemeToggle />
168
+ {isAuthenticated ? (
169
+ <DropdownMenu>
170
+ <DropdownMenuTrigger asChild>
171
+ <Button
172
+ variant="ghost"
173
+ className="relative h-8 w-8 rounded-full"
174
+ disabled={isLoading}
175
+ >
176
+ <Avatar className="h-8 w-8">
177
+ {isLoading ? (
178
+ <div className="h-8 w-8 animate-pulse bg-muted rounded-full" />
179
+ ) : (
180
+ <>
181
+ <AvatarImage
182
+ src={user.photos?.[0]?.value || "/placeholder.svg"}
183
+ alt={user.displayName}
184
+ />
185
+ <AvatarFallback>
186
+ {user.displayName?.charAt(0)}
187
+ </AvatarFallback>
188
+ </>
189
+ )}
190
+ </Avatar>
191
+ </Button>
192
+ </DropdownMenuTrigger>
193
+ <DropdownMenuContent className="w-56" align="end" forceMount>
194
+ {isLoading ? (
195
+ <div className="p-4">
196
+ <div className="h-4 w-3/4 animate-pulse bg-muted rounded mb-2" />
197
+ <div className="h-3 w-1/2 animate-pulse bg-muted rounded" />
198
+ </div>
199
+ ) : (
200
+ <>
201
+ <DropdownMenuLabel className="font-normal">
202
+ <div className="flex flex-col space-y-1">
203
+ <p className="text-sm font-medium leading-none">
204
+ {user.displayName}
205
+ </p>
206
+ <p className="text-xs leading-none text-muted-foreground">
207
+ {user.emails?.[0]?.value}
208
+ </p>
209
+ </div>
210
+ </DropdownMenuLabel>
211
+ <DropdownMenuSeparator />
212
+ <DropdownMenuGroup>
213
+ <DropdownMenuItem onClick={onProfileSelected}>
214
+ <IconWrapper Icon={User} />
215
+ <span>Profile</span>
216
+ </DropdownMenuItem>
217
+ </DropdownMenuGroup>
218
+ <DropdownMenuSeparator />
219
+ <DropdownMenuItem onClick={onLogout}>
220
+ <IconWrapper Icon={LogOut} />
221
+ <span>Log out</span>
154
222
  </DropdownMenuItem>
155
- </DropdownMenuGroup>
156
- <DropdownMenuSeparator />
157
- <DropdownMenuItem onClick={onLogout}>
158
- <IconWrapper Icon={LogOut} />
159
- <span>Log out</span>
160
- </DropdownMenuItem>
161
- </>
162
- )}
163
- </DropdownMenuContent>
164
- </DropdownMenu>
165
- ) : (
166
- <Button
167
- variant="default"
168
- aria-label="Sign in"
169
- onClick={() => {
170
- if (!environmentId) {
171
- throw new Error("Environment ID is required");
172
- }
173
-
174
- if (!identityHost) {
175
- throw new Error("Identity Host is required");
176
- }
223
+ </>
224
+ )}
225
+ </DropdownMenuContent>
226
+ </DropdownMenu>
227
+ ) : (
228
+ <Button
229
+ variant="default"
230
+ aria-label="Sign in"
231
+ onClick={() => {
232
+ if (!environmentId) {
233
+ throw new Error("Environment ID is required");
234
+ }
177
235
 
178
- const signinUrl = `${identityHost}/signin/${environmentId}`;
179
- window.open(signinUrl, "_blank");
180
- }}
181
- >
182
- Sign in
183
- </Button>
184
- )}
236
+ if (!identityHost) {
237
+ throw new Error("Identity Host is required");
238
+ }
185
239
 
186
- <Sheet open={open} onOpenChange={setOpen}>
187
- <SheetTrigger asChild>
188
- <Button
189
- variant="ghost"
190
- size="icon"
191
- className="md:hidden"
192
- aria-label="Open Menu"
240
+ const signinUrl = `${identityHost}/signin/${environmentId}`;
241
+ window.open(signinUrl, "_blank");
242
+ }}
193
243
  >
194
- <IconWrapper Icon={Menu} />
244
+ Sign in
195
245
  </Button>
196
- </SheetTrigger>
197
- <SheetContent side="left" className="pr-0">
198
- <nav className="grid gap-2 py-6">
199
- {items?.map((item, index) => (
200
- <a
201
- key={index}
202
- href={item.href}
203
- className={cn(
204
- "flex w-full items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent",
205
- item.disabled && "cursor-not-allowed opacity-80",
206
- )}
207
- onClick={() => setOpen(false)}
208
- >
209
- {item.title}
210
- </a>
211
- ))}
212
- </nav>
213
- </SheetContent>
214
- </Sheet>
246
+ )}
247
+ </div>
215
248
  </div>
216
249
  </div>
217
250
  </header>
@@ -12,8 +12,9 @@ import {
12
12
  AvatarFallback,
13
13
  AvatarImage,
14
14
  } from "../../components/ui/avatar";
15
- import { cn } from "@authdog/react-elements/lib/utils";
15
+ import { cn } from "../../lib/utils";
16
16
  import { LogOut, Settings, ExternalLink } from "lucide-react";
17
+ import type { ComponentType } from "react";
17
18
 
18
19
  export type UserDropdownLink = {
19
20
  label: string;
@@ -40,6 +41,8 @@ export interface UserDropdownProps {
40
41
  align?: "start" | "center" | "end";
41
42
  sideOffset?: number;
42
43
  modal?: boolean;
44
+ triggerAsChild?: boolean;
45
+ triggerWrapperClassName?: string;
43
46
  }
44
47
 
45
48
  const getInitials = (name?: string) => {
@@ -63,6 +66,8 @@ export const UserDropdown = ({
63
66
  align = "end",
64
67
  sideOffset = 8,
65
68
  modal = false,
69
+ triggerAsChild = false,
70
+ triggerWrapperClassName,
66
71
  }: UserDropdownProps) => {
67
72
  const primaryEmail = user?.emails?.[0]?.value || user?.email || "";
68
73
  const displayName = user?.displayName || user?.name || "";
@@ -80,10 +85,23 @@ export const UserDropdown = ({
80
85
  };
81
86
 
82
87
  const IconExternal = ExternalLink as any;
88
+ const SettingsIcon = Settings as ComponentType<any>;
89
+ const LogOutIcon = LogOut as ComponentType<any>;
83
90
 
84
91
  return (
85
92
  <DropdownMenu modal={modal}>
86
- <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
93
+ {triggerAsChild ? (
94
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
95
+ ) : (
96
+ <DropdownMenuTrigger
97
+ className={cn(
98
+ "inline-flex items-center justify-center bg-transparent p-0 border-0 outline-none focus-visible:outline-none",
99
+ triggerWrapperClassName,
100
+ )}
101
+ >
102
+ {trigger}
103
+ </DropdownMenuTrigger>
104
+ )}
87
105
  <DropdownMenuContent
88
106
  align={align}
89
107
  side={side}
@@ -112,7 +130,7 @@ export const UserDropdown = ({
112
130
  className="cursor-pointer py-2"
113
131
  onClick={() => onManageAccount?.()}
114
132
  >
115
- <Settings className="mr-2 h-4 w-4" />
133
+ <SettingsIcon className="mr-2 h-4 w-4" />
116
134
  <span>Manage account</span>
117
135
  </DropdownMenuItem>
118
136
  {links.map((item, idx) => {
@@ -133,7 +151,7 @@ export const UserDropdown = ({
133
151
  className="cursor-pointer py-2 rounded-md font-semibold text-red-600 dark:text-red-300 hover:bg-red-50 hover:text-red-700 focus:bg-red-50 focus:text-red-700 dark:hover:bg-red-500/20 dark:focus:bg-red-500/25 dark:hover:text-red-100 dark:focus:text-red-100 border border-transparent dark:border-red-500/30 ring-0 focus-visible:ring-2 focus-visible:ring-red-400/40 dark:focus-visible:ring-red-400/40"
134
152
  onClick={() => onSignout?.()}
135
153
  >
136
- <LogOut className="mr-2 h-4 w-4 text-red-600 dark:text-red-300" />
154
+ <LogOutIcon className="mr-2 h-4 w-4 text-red-600 dark:text-red-300" />
137
155
  <span>Sign out</span>
138
156
  </DropdownMenuItem>
139
157
  </DropdownMenuContent>
@@ -6,26 +6,20 @@ const iconProps: LucideProps = {
6
6
  "aria-hidden": "true",
7
7
  };
8
8
 
9
- export const renderIcon = ((Icon: any) => {
9
+ export const IconWrapper = ({ Icon }: { Icon: any }) => {
10
10
  const [isMounted, setIsMounted] = useState(false);
11
11
 
12
12
  useEffect(() => {
13
13
  setIsMounted(true);
14
14
  }, []);
15
15
 
16
- if (!isMounted) {
17
- return <span className="mr-2 h-4 w-4" aria-hidden="true" />;
18
- }
19
-
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
24
  );
31
25
  };
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
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",
@@ -3,7 +3,7 @@
3
3
  import * as React from "react";
4
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,
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { Slot } from "@radix-ui/react-slot";
3
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",
@@ -1,6 +1,6 @@
1
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 (