@authdog/react-elements 0.0.42 → 0.0.44

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
@@ -531,6 +531,39 @@ video {
531
531
 
532
532
  --radius: 0.5rem;
533
533
  }
534
+
535
+ .dark {
536
+ --background: 224 71% 4%;
537
+ --foreground: 213 31% 91%;
538
+
539
+ --muted: 223 47% 11%;
540
+ --muted-foreground: 215.4 16.3% 56.9%;
541
+
542
+ --popover: 224 71% 4%;
543
+ --popover-foreground: 215 20.2% 65.1%;
544
+
545
+ --card: 224 71% 4%;
546
+ --card-foreground: 213 31% 91%;
547
+
548
+ --border: 216 34% 17%;
549
+ --input: 216 34% 17%;
550
+
551
+ --primary: 210 40% 98%;
552
+ --primary-foreground: 222.2 47.4% 1.2%;
553
+
554
+ --secondary: 222.2 47.4% 11.2%;
555
+ --secondary-foreground: 210 40% 98%;
556
+
557
+ --accent: 216 34% 17%;
558
+ --accent-foreground: 210 40% 98%;
559
+
560
+ --destructive: 0 63% 31%;
561
+ --destructive-foreground: 210 40% 98%;
562
+
563
+ --ring: 216 34% 17%;
564
+
565
+ --radius: 0.5rem;
566
+ }
534
567
  * {
535
568
  border-color: hsl(var(--border));
536
569
  }
@@ -539,19 +572,6 @@ video {
539
572
  color: hsl(var(--foreground));
540
573
  font-feature-settings: "rlig" 1, "calt" 1;
541
574
  }
542
- .container {
543
- width: 100%;
544
- margin-right: auto;
545
- margin-left: auto;
546
- padding-right: 2rem;
547
- padding-left: 2rem;
548
- }
549
- @media (min-width: 1400px) {
550
-
551
- .container {
552
- max-width: 1400px;
553
- }
554
- }
555
575
  .sr-only {
556
576
  position: absolute;
557
577
  width: 1px;
@@ -575,6 +595,9 @@ video {
575
595
  .relative {
576
596
  position: relative;
577
597
  }
598
+ .sticky {
599
+ position: sticky;
600
+ }
578
601
  .inset-0 {
579
602
  inset: 0px;
580
603
  }
@@ -610,6 +633,9 @@ video {
610
633
  .top-4 {
611
634
  top: 1rem;
612
635
  }
636
+ .z-40 {
637
+ z-index: 40;
638
+ }
613
639
  .z-50 {
614
640
  z-index: 50;
615
641
  }
@@ -814,6 +840,9 @@ video {
814
840
  .w-6 {
815
841
  width: 1.5rem;
816
842
  }
843
+ .w-7 {
844
+ width: 1.75rem;
845
+ }
817
846
  .w-72 {
818
847
  width: 18rem;
819
848
  }
@@ -1103,6 +1132,9 @@ video {
1103
1132
  .bg-background {
1104
1133
  background-color: hsl(var(--background));
1105
1134
  }
1135
+ .bg-background\/80 {
1136
+ background-color: hsl(var(--background) / 0.8);
1137
+ }
1106
1138
  .bg-black\/50 {
1107
1139
  background-color: rgb(0 0 0 / 0.5);
1108
1140
  }
@@ -1148,6 +1180,9 @@ video {
1148
1180
  .bg-muted {
1149
1181
  background-color: hsl(var(--muted));
1150
1182
  }
1183
+ .bg-muted\/80 {
1184
+ background-color: hsl(var(--muted) / 0.8);
1185
+ }
1151
1186
  .bg-popover {
1152
1187
  background-color: hsl(var(--popover));
1153
1188
  }
@@ -1191,6 +1226,10 @@ video {
1191
1226
  .fill-current {
1192
1227
  fill: currentColor;
1193
1228
  }
1229
+ .object-contain {
1230
+ -o-object-fit: contain;
1231
+ object-fit: contain;
1232
+ }
1194
1233
  .p-0 {
1195
1234
  padding: 0px;
1196
1235
  }
@@ -1218,6 +1257,10 @@ video {
1218
1257
  .p-8 {
1219
1258
  padding: 2rem;
1220
1259
  }
1260
+ .px-1 {
1261
+ padding-left: 0.25rem;
1262
+ padding-right: 0.25rem;
1263
+ }
1221
1264
  .px-2 {
1222
1265
  padding-left: 0.5rem;
1223
1266
  padding-right: 0.5rem;
@@ -1534,6 +1577,14 @@ video {
1534
1577
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1535
1578
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1536
1579
  }
1580
+ .ring-1 {
1581
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1582
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1583
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1584
+ }
1585
+ .ring-border {
1586
+ --tw-ring-color: hsl(var(--border));
1587
+ }
1537
1588
  .ring-offset-background {
1538
1589
  --tw-ring-offset-color: hsl(var(--background));
1539
1590
  }
@@ -1542,6 +1593,10 @@ video {
1542
1593
  -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1543
1594
  backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1544
1595
  }
1596
+ .backdrop-filter {
1597
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1598
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1599
+ }
1545
1600
  .transition {
1546
1601
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
1547
1602
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
@@ -1749,6 +1804,9 @@ video {
1749
1804
  .focus-visible\:ring-offset-2:focus-visible {
1750
1805
  --tw-ring-offset-width: 2px;
1751
1806
  }
1807
+ .focus-visible\:ring-offset-background:focus-visible {
1808
+ --tw-ring-offset-color: hsl(var(--background));
1809
+ }
1752
1810
  .disabled\:pointer-events-none:disabled {
1753
1811
  pointer-events: none;
1754
1812
  }
@@ -1758,6 +1816,9 @@ video {
1758
1816
  .disabled\:opacity-50:disabled {
1759
1817
  opacity: 0.5;
1760
1818
  }
1819
+ .group:hover .group-hover\:text-primary {
1820
+ color: hsl(var(--primary));
1821
+ }
1761
1822
  .peer:disabled ~ .peer-disabled\:cursor-not-allowed {
1762
1823
  cursor: not-allowed;
1763
1824
  }
@@ -1897,6 +1958,12 @@ video {
1897
1958
  .group[data-disabled="true"] .group-data-\[disabled\=true\]\:opacity-50 {
1898
1959
  opacity: 0.5;
1899
1960
  }
1961
+ @supports ((-webkit-backdrop-filter: var(--tw)) or (backdrop-filter: var(--tw))) {
1962
+
1963
+ .supports-\[backdrop-filter\]\:bg-background\/60 {
1964
+ background-color: hsl(var(--background) / 0.6);
1965
+ }
1966
+ }
1900
1967
  .dark\:border-amber-400\/40:is(.dark *) {
1901
1968
  border-color: rgb(251 191 36 / 0.4);
1902
1969
  }
@@ -2011,6 +2078,14 @@ video {
2011
2078
  grid-template-columns: repeat(3, minmax(0, 1fr));
2012
2079
  }
2013
2080
 
2081
+ .md\:gap-3 {
2082
+ gap: 0.75rem;
2083
+ }
2084
+
2085
+ .md\:gap-4 {
2086
+ gap: 1rem;
2087
+ }
2088
+
2014
2089
  .md\:space-y-6 > :not([hidden]) ~ :not([hidden]) {
2015
2090
  --tw-space-y-reverse: 0;
2016
2091
  margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
@@ -2030,6 +2105,11 @@ video {
2030
2105
  padding-right: 1.5rem;
2031
2106
  }
2032
2107
 
2108
+ .md\:text-lg {
2109
+ font-size: 1.125rem;
2110
+ line-height: 1.75rem;
2111
+ }
2112
+
2033
2113
  .md\:text-sm {
2034
2114
  font-size: 0.875rem;
2035
2115
  line-height: 1.25rem;
@@ -2037,6 +2117,10 @@ video {
2037
2117
  }
2038
2118
  @media (min-width: 1024px) {
2039
2119
 
2120
+ .lg\:max-w-\[80vw\] {
2121
+ max-width: 80vw;
2122
+ }
2123
+
2040
2124
  .lg\:grid-cols-4 {
2041
2125
  grid-template-columns: repeat(4, minmax(0, 1fr));
2042
2126
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authdog/react-elements",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",
@@ -15,7 +15,7 @@
15
15
  "@storybook/react-vite": "^10.1.3",
16
16
  "@types/node": "22.10.10",
17
17
  "@types/react": "19.1.8",
18
- "@types/react-dom": "19.0.0",
18
+ "@types/react-dom": "19.1.8",
19
19
  "@vitejs/plugin-react": "^4.4.1",
20
20
  "autoprefixer": "^10",
21
21
  "css-loader": "^6.8.1",
@@ -33,7 +33,7 @@
33
33
  "tailwindcss": "3.4.18",
34
34
  "ts-loader": "^9.5.1",
35
35
  "typescript": "5.7.3",
36
- "vite": "^4.5.0",
36
+ "vite": "^5.0.0",
37
37
  "webpack": "^5.89.0",
38
38
  "@authdog/eslint-config": "0.0.0",
39
39
  "@authdog/typescript-config": "0.0.0"
@@ -84,6 +84,6 @@
84
84
  "build": "pnpm tsup && pnpm build:styles",
85
85
  "storybook": "storybook dev -p 7007",
86
86
  "build-storybook": "storybook build",
87
- "deploy-docs": "wrangler pages deploy"
87
+ "deploy-docs": "pnpm build-storybook && cp wrangler.prod.toml wrangler.toml && wrangler pages deploy"
88
88
  }
89
89
  }
@@ -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>
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { Sun, Moon } from "lucide-react";
5
+
6
+ import { Button } from "./button";
7
+
8
+ const STORAGE_KEY = "authdog-theme";
9
+
10
+ type ThemeMode = "light" | "dark";
11
+
12
+ const getPreferredTheme = (): ThemeMode => {
13
+ if (typeof window === "undefined") return "light";
14
+ const stored = window.localStorage.getItem(STORAGE_KEY);
15
+ if (stored === "light" || stored === "dark") {
16
+ return stored;
17
+ }
18
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
19
+ };
20
+
21
+ const applyTheme = (mode: ThemeMode) => {
22
+ document.documentElement.classList.toggle("dark", mode === "dark");
23
+ };
24
+
25
+ export const ThemeToggle = () => {
26
+ const [mode, setMode] = useState<ThemeMode>("light");
27
+
28
+ useEffect(() => {
29
+ const initial = getPreferredTheme();
30
+ applyTheme(initial);
31
+ setMode(initial);
32
+ }, []);
33
+
34
+ const toggle = () => {
35
+ const nextMode: ThemeMode = mode === "dark" ? "light" : "dark";
36
+ applyTheme(nextMode);
37
+ try {
38
+ window.localStorage.setItem(STORAGE_KEY, nextMode);
39
+ } catch {
40
+ // ignore
41
+ }
42
+ setMode(nextMode);
43
+ };
44
+
45
+ return (
46
+ <Button
47
+ variant="ghost"
48
+ size="icon"
49
+ aria-label={mode === "dark" ? "Switch to light theme" : "Switch to dark theme"}
50
+ onClick={toggle}
51
+ >
52
+ {mode === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
53
+ </Button>
54
+ );
55
+ };
@@ -9,7 +9,7 @@ const demoItems = [
9
9
  { title: "Home", href: "/" },
10
10
  { title: "Features", href: "/features" },
11
11
  { title: "Pricing", href: "/pricing" },
12
- { title: "Contact", href: "/contact" },
12
+ // { title: "Contact", href: "/contact" },
13
13
  ];
14
14
 
15
15
  const demoUser = {
@@ -43,3 +43,9 @@ export const Authenticated: Story = {
43
43
  user: demoUser,
44
44
  },
45
45
  };
46
+
47
+ export const WithLogo: Story = {
48
+ args: {
49
+ logoSrc: "https://dummyimage.com/64x64/111827/ffffff&text=AD",
50
+ },
51
+ };