← full-stack-fastapi-template  /  frontend/src/components/Common/Appearance.tsx

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
}
106