Spaces:
Sleeping
Sleeping
Download frontend/src/components/UserMenu.tsx from BrainForge/AdvisoryBuilderWorkshop: direct link, hf CLI and curl.
- Browser
- Download file 5.69 kB
-
https://huggingface.co/spaces/BrainForge/AdvisoryBuilderWorkshop/resolve/main/frontend/src/components/UserMenu.tsx
- Command line
-
hf download hf://spaces/BrainForge/AdvisoryBuilderWorkshop/frontend/src/components/UserMenu.tsx
-
curl -L -o UserMenu.tsx https://huggingface.co/spaces/BrainForge/AdvisoryBuilderWorkshop/resolve/main/frontend/src/components/UserMenu.tsx
5.69 kB
| import { useState, useRef, useEffect } from 'react' | |
| import { Link } from 'react-router-dom' | |
| import { useAuth } from '../context/AuthContext' | |
| import { useTutorialOptional } from '../context/TutorialContext' | |
| import { SampleDataMenuItem } from './SampleDataMenuItem' | |
| import { SpeechSettingsMenuItem } from './SpeechSettingsPanel' | |
| type Props = { | |
| onProfile: () => void | |
| onAccount: () => void | |
| onClearData: () => void | |
| } | |
| /** | |
| * Account menu: hover opens transiently; click pins until click again, | |
| * menu item action, Escape, or outside click. | |
| */ | |
| export function UserMenu({ onProfile, onAccount, onClearData }: Props) { | |
| const { user, signOut } = useAuth() | |
| const tutorial = useTutorialOptional() | |
| const [hoverOpen, setHoverOpen] = useState(false) | |
| const [pinned, setPinned] = useState(false) | |
| const [speechOpen, setSpeechOpen] = useState(false) | |
| const [sampleDataOpen, setSampleDataOpen] = useState(false) | |
| const ref = useRef<HTMLDivElement>(null) | |
| const open = pinned || hoverOpen | |
| const closeAll = () => { | |
| setPinned(false) | |
| setHoverOpen(false) | |
| setSpeechOpen(false) | |
| setSampleDataOpen(false) | |
| } | |
| useEffect(() => { | |
| if (!open) return | |
| function onPointerDown(e: MouseEvent) { | |
| if (ref.current && !ref.current.contains(e.target as Node)) { | |
| closeAll() | |
| } | |
| } | |
| function onKeyDown(e: KeyboardEvent) { | |
| if (e.key === 'Escape') closeAll() | |
| } | |
| document.addEventListener('mousedown', onPointerDown) | |
| document.addEventListener('keydown', onKeyDown) | |
| return () => { | |
| document.removeEventListener('mousedown', onPointerDown) | |
| document.removeEventListener('keydown', onKeyDown) | |
| } | |
| }, [open]) | |
| const label = user ? user.firstName || user.email : 'Guest' | |
| return ( | |
| <div | |
| className="user-menu-wrap" | |
| ref={ref} | |
| onMouseEnter={() => setHoverOpen(true)} | |
| onMouseLeave={() => { | |
| if (!pinned) { | |
| setHoverOpen(false) | |
| setSpeechOpen(false) | |
| setSampleDataOpen(false) | |
| } | |
| }} | |
| > | |
| <button | |
| type="button" | |
| className={`user-menu-trigger${open ? ' user-menu-trigger--open' : ''}`} | |
| onClick={() => { | |
| const next = !pinned | |
| setPinned(next) | |
| setHoverOpen(next) | |
| if (!next) { | |
| setSpeechOpen(false) | |
| setSampleDataOpen(false) | |
| } | |
| }} | |
| aria-expanded={open} | |
| aria-haspopup="menu" | |
| aria-label="Account menu" | |
| > | |
| <span className="user-menu-name">{label}</span> | |
| <span className="user-menu-kebab" aria-hidden title="Menu"> | |
| ⋮ | |
| </span> | |
| </button> | |
| {open && ( | |
| <div className="user-dropdown" role="menu"> | |
| <button | |
| type="button" | |
| role="menuitem" | |
| onClick={() => { | |
| closeAll() | |
| onProfile() | |
| }} | |
| > | |
| Profile | |
| </button> | |
| <button | |
| type="button" | |
| role="menuitem" | |
| onClick={() => { | |
| closeAll() | |
| if (user) onAccount() | |
| }} | |
| disabled={!user} | |
| title={!user ? 'Sign in to change account settings' : undefined} | |
| style={!user ? { opacity: 0.55, cursor: 'not-allowed' } : undefined} | |
| > | |
| Account | |
| </button> | |
| <button | |
| type="button" | |
| role="menuitem" | |
| onClick={() => { | |
| closeAll() | |
| onClearData() | |
| }} | |
| > | |
| Clear User Data | |
| </button> | |
| <SampleDataMenuItem | |
| open={sampleDataOpen} | |
| onToggle={() => { | |
| setSampleDataOpen((s) => !s) | |
| setSpeechOpen(false) | |
| }} | |
| onActionComplete={closeAll} | |
| /> | |
| <div className="user-dropdown-divider" role="presentation" /> | |
| {tutorial && ( | |
| <> | |
| <button | |
| type="button" | |
| role="menuitem" | |
| onClick={() => { | |
| closeAll() | |
| tutorial.turnOnTutorial() | |
| }} | |
| > | |
| Turn on Tutorial | |
| </button> | |
| <button | |
| type="button" | |
| role="menuitem" | |
| onClick={() => { | |
| closeAll() | |
| tutorial.removeTutorialButton() | |
| }} | |
| > | |
| Remove Tutorial Button | |
| </button> | |
| <div className="user-dropdown-divider" role="presentation" /> | |
| </> | |
| )} | |
| <SpeechSettingsMenuItem | |
| open={speechOpen} | |
| onToggle={() => { | |
| setSpeechOpen((s) => !s) | |
| setSampleDataOpen(false) | |
| }} | |
| /> | |
| {user ? ( | |
| <button | |
| type="button" | |
| role="menuitem" | |
| className="danger" | |
| onClick={() => { | |
| closeAll() | |
| signOut() | |
| }} | |
| > | |
| Sign out | |
| </button> | |
| ) : ( | |
| <Link | |
| to="/auth" | |
| role="menuitem" | |
| className="user-dropdown-link" | |
| onClick={closeAll} | |
| style={{ display: 'block', padding: '0.5rem 1rem', textDecoration: 'none', color: 'inherit' }} | |
| > | |
| Sign In / Create Account | |
| </Link> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } | |