index.tsx168 lines · main
1'use client'
2
3import type { User } from '@supabase/supabase-js'
4import type { LucideIcon } from 'icons/src/createBrivenIcon'
5import { UserIcon } from 'lucide-react'
6import { useTheme } from 'next-themes'
7import Image from 'next/image'
8import Link from 'next/link'
9import { Fragment } from 'react'
10import {
11 buttonVariants,
12 cn,
13 DropdownMenu,
14 DropdownMenuContent,
15 DropdownMenuItem,
16 DropdownMenuLabel,
17 DropdownMenuRadioGroup,
18 DropdownMenuRadioItem,
19 DropdownMenuSeparator,
20 DropdownMenuShortcut,
21 DropdownMenuTrigger,
22} from 'ui'
23import { themes } from 'ui/src/components/ThemeProvider/themes'
24import type { Theme } from 'ui/src/components/ThemeProvider/themes'
25
26interface Props {
27 menu: menuItem[][]
28 user: User | null
29 hasThemeSelector?: boolean
30 site?: 'docs' | 'www' | 'studio'
31}
32
33export interface menuItem {
34 label: string
35 type?: 'link' | 'button' | 'text' | 'theme'
36 icon?: LucideIcon
37 href?: string
38 shortcut?: string
39 onClick?: VoidFunction
40 otherProps?: {
41 target?: '_blank'
42 rel?: 'noreferrer noopener'
43 }
44}
45
46export const AuthenticatedDropdownMenu = ({ user, menu, site }: Props) => {
47 const { theme, setTheme } = useTheme()
48 const userAvatar = user && user.user_metadata?.avatar_url
49
50 return (
51 <DropdownMenu modal={false}>
52 <DropdownMenuTrigger asChild className="flex">
53 <button
54 title="Menu dropdown button"
55 className={cn(
56 buttonVariants({ type: 'default' }),
57 'text-foreground-light border-default w-[30px] min-w-[30px] h-[30px] data-[state=open]:bg-overlay-hover/30 hover:border-strong data-[state=open]:border-stronger hover:!bg-overlay-hover/50 bg-transparent',
58 'rounded-full overflow-hidden opacity-0 transition-opacity animate-fade-in'
59 )}
60 >
61 {userAvatar ? (
62 <Image
63 src={userAvatar}
64 alt={user?.email ?? ''}
65 placeholder="blur"
66 blurDataURL="/images/blur.png"
67 fill
68 sizes="30px"
69 className="object-cover object-center"
70 />
71 ) : (
72 <UserIcon size={16} strokeWidth={1.5} />
73 )}
74 </button>
75 </DropdownMenuTrigger>
76 <DropdownMenuContent side="bottom" align="end" className="w-52">
77 {menu.map((menuSection, sectionIdx) => (
78 <Fragment key={`${site}-auth-dropdown--${sectionIdx}`}>
79 {sectionIdx !== 0 && (
80 <DropdownMenuSeparator key={`${site}-auth-dropdown--${sectionIdx}`} />
81 )}
82 {menuSection.map((sectionItem, itemIdx) => {
83 switch (sectionItem.type) {
84 case 'text':
85 return (
86 <div
87 key={`${site}-auth-dropdown-${sectionItem.label}-${sectionIdx}-${itemIdx}`}
88 className="flex cursor-text items-center text-foreground rounded-xs px-2 py-1.5 text-xs outline-hidden space-x-2"
89 {...sectionItem.otherProps}
90 >
91 <DropdownItemContent {...sectionItem} />
92 </div>
93 )
94 case 'button':
95 return (
96 <DropdownMenuItem
97 key={`${site}-auth-dropdown-${sectionItem.label}-${sectionIdx}-${itemIdx}`}
98 className="space-x-2 hover:cursor-pointer"
99 onClick={sectionItem.onClick!}
100 {...sectionItem.otherProps}
101 >
102 <DropdownItemContent {...sectionItem} />
103 </DropdownMenuItem>
104 )
105 case 'theme':
106 return (
107 <>
108 <DropdownMenuLabel
109 key={`${site}-auth-dropdown-${sectionItem.label}-${sectionIdx}-${itemIdx}`}
110 >
111 {sectionItem.label}
112 </DropdownMenuLabel>
113 <DropdownMenuRadioGroup
114 value={theme}
115 onValueChange={(value) => {
116 setTheme(value)
117 }}
118 >
119 {themes
120 .filter(
121 (x) => x.value === 'light' || x.value === 'dark' || x.value === 'system'
122 )
123 .map((theme: Theme) => (
124 <DropdownMenuRadioItem
125 key={`${site}-auth-dropdown-theme-${theme.value}`}
126 value={theme.value}
127 className="hover:cursor-pointer"
128 >
129 {theme.name}
130 </DropdownMenuRadioItem>
131 ))}
132 </DropdownMenuRadioGroup>
133 </>
134 )
135 case 'link':
136 default:
137 return (
138 <Link
139 key={`${site}-auth-dropdown-${sectionItem.label}-${sectionIdx}-${itemIdx}`}
140 href={sectionItem.href!}
141 {...sectionItem.otherProps}
142 >
143 <DropdownMenuItem
144 className="space-x-2 hover:cursor-pointer"
145 onClick={() => {}}
146 >
147 <DropdownItemContent {...sectionItem} />
148 </DropdownMenuItem>
149 </Link>
150 )
151 }
152 })}
153 </Fragment>
154 ))}
155 </DropdownMenuContent>
156 </DropdownMenu>
157 )
158}
159
160const DropdownItemContent = ({ icon: Icon, ...sectionItem }: menuItem) => (
161 <>
162 {Icon && <Icon className="w-3 h-3" />}
163 <span className="grow truncate">{sectionItem.label}</span>
164 {sectionItem.shortcut && <DropdownMenuShortcut>{sectionItem.shortcut}</DropdownMenuShortcut>}
165 </>
166)
167
168export default AuthenticatedDropdownMenu