index.tsx168 lines · main
| 1 | 'use client' |
| 2 | |
| 3 | import type { User } from '@supabase/supabase-js' |
| 4 | import type { LucideIcon } from 'icons/src/createBrivenIcon' |
| 5 | import { UserIcon } from 'lucide-react' |
| 6 | import { useTheme } from 'next-themes' |
| 7 | import Image from 'next/image' |
| 8 | import Link from 'next/link' |
| 9 | import { Fragment } from 'react' |
| 10 | import { |
| 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' |
| 23 | import { themes } from 'ui/src/components/ThemeProvider/themes' |
| 24 | import type { Theme } from 'ui/src/components/ThemeProvider/themes' |
| 25 | |
| 26 | interface Props { |
| 27 | menu: menuItem[][] |
| 28 | user: User | null |
| 29 | hasThemeSelector?: boolean |
| 30 | site?: 'docs' | 'www' | 'studio' |
| 31 | } |
| 32 | |
| 33 | export 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 | |
| 46 | export 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 | |
| 160 | const 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 | |
| 168 | export default AuthenticatedDropdownMenu |