import React, { useCallback, useId } from 'react'; import type { CSSProperties } from 'react'; import { createPortal } from 'react-dom'; import { GithubIcon, MatrixIcon, WorldIcon, CheckIcon, SettingsIcon, LogoutIcon, RestoreIcon, SunIcon, MoonIcon, DesktopIcon, } from '../../ui/icons'; import { useSidebarContext } from '../SidebarContext'; import { THEME_MODE_LABELS, THEME_MODES } from '../../constants/sidebar'; import type { Identifier } from '../../types/identifiers'; export interface TenantOption { id?: Identifier | null; name?: string | null; } interface CommunityLink { label: string; href: string; title: string; Icon: typeof GithubIcon; } const COMMUNITY_LINKS: CommunityLink[] = [ { label: 'GitHub', href: 'https://github.com/papercrate-dms/papercrate', title: 'Open Papercrate on GitHub', Icon: GithubIcon, }, { label: 'Matrix', href: 'https://matrix.to/#/#papercrate:matrix.org', title: 'Join the Papercrate Matrix room', Icon: MatrixIcon, }, { label: 'Website', href: 'https://papercrate.org', title: 'Visit papercrate.org', Icon: WorldIcon, }, ]; interface SidebarMenuProps { isOpen: boolean; menuRef: React.RefObject; style: CSSProperties | null; tenants: TenantOption[]; activeTenantId: Identifier | null; onSelectTenant?: (tenant: TenantOption | null, options?: { refreshOnly?: boolean }) => void; onOpenSettings?: () => void; onLogout?: () => void; onClose: () => void; } const SidebarMenu: React.FC = ({ isOpen, menuRef, style, tenants, activeTenantId, onSelectTenant, onOpenSettings, onLogout, onClose, }) => { const { neutralHue, setNeutralHue, resetNeutralHue, neutralChroma, setNeutralChroma, resetNeutralChroma, themeMode, cycleThemeMode, themeModes, } = useSidebarContext(); const neutralHueInputId = useId(); const neutralChromaInputId = useId(); const handleThemeAdjustmentsReset = useCallback(() => { resetNeutralHue(); resetNeutralChroma(); }, [resetNeutralHue, resetNeutralChroma]); const handleNeutralHueChange = useCallback( (value: string) => { if (value === '') { resetNeutralHue(); return; } const parsed = Number(value); if (Number.isNaN(parsed)) { return; } setNeutralHue(parsed); }, [resetNeutralHue, setNeutralHue], ); const handleNeutralChromaChange = useCallback( (value: string) => { if (value === '') { resetNeutralChroma(); return; } const parsed = Number(value); if (Number.isNaN(parsed)) { return; } setNeutralChroma(parsed); }, [resetNeutralChroma, setNeutralChroma], ); const formatSliderValue = useCallback((value: number | string) => { const parsed = Number(value); if (Number.isNaN(parsed)) { return String(value); } return parsed.toFixed(2); }, []); const themeModeList = Array.isArray(themeModes) && themeModes.length ? themeModes : THEME_MODES; const themeModeIndex = themeModeList.indexOf(themeMode as string); const safeThemeModeIndex = themeModeIndex === -1 ? 0 : themeModeIndex; const resolvedThemeMode = themeModeList[safeThemeModeIndex] || THEME_MODES[0]; const nextThemeMode = themeModeList[(safeThemeModeIndex + 1) % themeModeList.length]; const themeModeLabel = THEME_MODE_LABELS[resolvedThemeMode] || THEME_MODE_LABELS.system; const nextThemeLabel = THEME_MODE_LABELS[nextThemeMode] || THEME_MODE_LABELS.system; const themeModeIcon = resolvedThemeMode === 'dark' ? : resolvedThemeMode === 'light' ? : ; const handleThemeModeToggle = useCallback(() => { cycleThemeMode(); }, [cycleThemeMode]); const handleTenantSelect = useCallback( (tenant: TenantOption | null) => { const targetId = tenant?.id || null; if (!targetId) { return; } onClose(); onSelectTenant?.(tenant); }, [onClose, onSelectTenant], ); const handleLogoutFromMenu = useCallback(() => { onClose(); onLogout?.(); }, [onClose, onLogout]); const handleSettingsFromMenu = useCallback(() => { onClose(); onOpenSettings?.(); }, [onClose, onOpenSettings]); const themeMenuSection = neutralHue != null ? (
Theme
) : null; const communityMenuFooter = COMMUNITY_LINKS.length ? (
{COMMUNITY_LINKS.map(({ href, label, title, Icon }) => ( {label} ))}
) : null; const showTenantList = tenants.length > 1; const menuClassName = `menu${!showTenantList && !themeMenuSection && !communityMenuFooter ? ' menu--simple' : ''}`; if (!isOpen || !style) { return null; } return createPortal(
{showTenantList ? (
Switch tenant
{tenants.map((tenant) => { const tenantId = tenant?.id || null; const isActive = tenantId === activeTenantId; const tenantLabel = tenant?.name || tenantId || 'Tenant'; return ( ); })}
) : null}
{themeMenuSection} {communityMenuFooter}
, document.body, ); }; export default SidebarMenu;