Skip to content

Commit d7e348a

Browse files
committed
♻️ Refactor Common components
1 parent d9bee25 commit d7e348a

3 files changed

Lines changed: 166 additions & 7 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Monitor, Moon, Sun } from "lucide-react"
2+
3+
import { type Theme, useTheme } from "@/components/theme-provider"
4+
import { Button } from "@/components/ui/button"
5+
import {
6+
DropdownMenu,
7+
DropdownMenuContent,
8+
DropdownMenuItem,
9+
DropdownMenuTrigger,
10+
} from "@/components/ui/dropdown-menu"
11+
import {
12+
SidebarMenuButton,
13+
SidebarMenuItem,
14+
useSidebar,
15+
} from "@/components/ui/sidebar"
16+
17+
type LucideIcon = React.FC<React.SVGProps<SVGSVGElement>>
18+
19+
const ICON_MAP: Record<Theme, LucideIcon> = {
20+
system: Monitor,
21+
light: Sun,
22+
dark: Moon,
23+
}
24+
25+
export const SidebarAppearance = () => {
26+
const { isMobile } = useSidebar()
27+
const { setTheme, theme } = useTheme()
28+
const Icon = ICON_MAP[theme]
29+
30+
return (
31+
<SidebarMenuItem>
32+
<DropdownMenu modal={false}>
33+
<DropdownMenuTrigger asChild>
34+
<SidebarMenuButton tooltip="Appearance" data-testid="theme-button">
35+
<Icon className="size-4 text-muted-foreground" />
36+
<span>Appearance</span>
37+
<span className="sr-only">Toggle theme</span>
38+
</SidebarMenuButton>
39+
</DropdownMenuTrigger>
40+
<DropdownMenuContent
41+
side={isMobile ? "top" : "right"}
42+
align="end"
43+
className="w-(--radix-dropdown-menu-trigger-width) min-w-56"
44+
>
45+
<DropdownMenuItem
46+
data-testid="light-mode"
47+
onClick={() => setTheme("light")}
48+
>
49+
<Sun className="mr-2 h-4 w-4" />
50+
Light
51+
</DropdownMenuItem>
52+
<DropdownMenuItem
53+
data-testid="dark-mode"
54+
onClick={() => setTheme("dark")}
55+
>
56+
<Moon className="mr-2 h-4 w-4" />
57+
Dark
58+
</DropdownMenuItem>
59+
<DropdownMenuItem onClick={() => setTheme("system")}>
60+
<Monitor className="mr-2 h-4 w-4" />
61+
System
62+
</DropdownMenuItem>
63+
</DropdownMenuContent>
64+
</DropdownMenu>
65+
</SidebarMenuItem>
66+
)
67+
}
68+
69+
export const Appearance = () => {
70+
const { setTheme } = useTheme()
71+
72+
return (
73+
<div className="flex items-center justify-center">
74+
<DropdownMenu modal={false}>
75+
<DropdownMenuTrigger asChild>
76+
<Button data-testid="theme-button" variant="outline" size="icon">
77+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
78+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
79+
<span className="sr-only">Toggle theme</span>
80+
</Button>
81+
</DropdownMenuTrigger>
82+
<DropdownMenuContent align="end">
83+
<DropdownMenuItem
84+
data-testid="light-mode"
85+
onClick={() => setTheme("light")}
86+
>
87+
<Sun className="mr-2 h-4 w-4" />
88+
Light
89+
</DropdownMenuItem>
90+
<DropdownMenuItem
91+
data-testid="dark-mode"
92+
onClick={() => setTheme("dark")}
93+
>
94+
<Moon className="mr-2 h-4 w-4" />
95+
Dark
96+
</DropdownMenuItem>
97+
<DropdownMenuItem onClick={() => setTheme("system")}>
98+
<Monitor className="mr-2 h-4 w-4" />
99+
System
100+
</DropdownMenuItem>
101+
</DropdownMenuContent>
102+
</DropdownMenu>
103+
</div>
104+
)
105+
}

frontend/src/components/Common/AuthLayout.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import Logo from "/assets/images/fastapi-logo.svg"
1+
import { Appearance } from "@/components/Common/Appearance"
2+
import { Logo } from "@/components/Common/Logo"
23
import { Footer } from "./Footer"
34

45
interface AuthLayoutProps {
@@ -8,14 +9,13 @@ interface AuthLayoutProps {
89
export function AuthLayout({ children }: AuthLayoutProps) {
910
return (
1011
<div className="grid min-h-svh lg:grid-cols-2">
11-
<div className="bg-muted relative hidden lg:flex lg:items-center lg:justify-center">
12-
<img
13-
src={Logo}
14-
alt="Logo"
15-
className="h-16 w-auto dark:brightness-[0.8] dark:grayscale"
16-
/>
12+
<div className="bg-muted dark:bg-zinc-900 relative hidden lg:flex lg:items-center lg:justify-center">
13+
<Logo variant="full" className="h-16" asLink={false} />
1714
</div>
1815
<div className="flex flex-col gap-4 p-6 md:p-10">
16+
<div className="flex justify-end">
17+
<Appearance />
18+
</div>
1919
<div className="flex flex-1 items-center justify-center">
2020
<div className="w-full max-w-xs">{children}</div>
2121
</div>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Link } from "@tanstack/react-router"
2+
3+
import { useTheme } from "@/components/theme-provider"
4+
import { cn } from "@/lib/utils"
5+
import icon from "/assets/images/fastapi-icon.svg"
6+
import iconLight from "/assets/images/fastapi-icon-light.svg"
7+
import logo from "/assets/images/fastapi-logo.svg"
8+
import logoLight from "/assets/images/fastapi-logo-light.svg"
9+
10+
interface LogoProps {
11+
variant?: "full" | "icon" | "responsive"
12+
className?: string
13+
asLink?: boolean
14+
}
15+
16+
export function Logo({
17+
variant = "full",
18+
className,
19+
asLink = true,
20+
}: LogoProps) {
21+
const { resolvedTheme } = useTheme()
22+
const isDark = resolvedTheme === "dark"
23+
24+
const fullLogo = isDark ? logoLight : logo
25+
const iconLogo = isDark ? iconLight : icon
26+
27+
const content =
28+
variant === "responsive" ? (
29+
<>
30+
<img
31+
src={fullLogo}
32+
alt="FastAPI"
33+
className={cn("h-6 w-auto group-data-[collapsible=icon]:hidden", className)}
34+
/>
35+
<img
36+
src={iconLogo}
37+
alt="FastAPI"
38+
className={cn("size-5 hidden group-data-[collapsible=icon]:block", className)}
39+
/>
40+
</>
41+
) : (
42+
<img
43+
src={variant === "full" ? fullLogo : iconLogo}
44+
alt="FastAPI"
45+
className={cn(variant === "full" ? "h-6 w-auto" : "size-5", className)}
46+
/>
47+
)
48+
49+
if (!asLink) {
50+
return content
51+
}
52+
53+
return <Link to="/">{content}</Link>
54+
}

0 commit comments

Comments
 (0)