175 lines
4.6 KiB
TypeScript
175 lines
4.6 KiB
TypeScript
import { useStore } from '@nanostores/react'
|
|
import type { ComponentProps, ReactNode } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
import { triggerHaptic } from '@/lib/haptics'
|
|
import { NotebookTabs, Settings, SlidersHorizontal, Volume2, VolumeX } from '@/lib/icons'
|
|
import { cn } from '@/lib/utils'
|
|
import { $hapticsMuted, toggleHapticsMuted } from '@/store/haptics'
|
|
import { $inspectorOpen, $sidebarOpen, toggleInspectorOpen, toggleSidebarOpen } from '@/store/layout'
|
|
|
|
import { titlebarButtonClass } from './titlebar'
|
|
|
|
export interface TitlebarTool {
|
|
id: string
|
|
label: string
|
|
active?: boolean
|
|
className?: string
|
|
disabled?: boolean
|
|
hidden?: boolean
|
|
href?: string
|
|
icon: ReactNode
|
|
onSelect?: () => void
|
|
title?: string
|
|
to?: string
|
|
}
|
|
|
|
export type TitlebarToolSide = 'left' | 'right'
|
|
export type SetTitlebarToolGroup = (id: string, tools: readonly TitlebarTool[], side?: TitlebarToolSide) => void
|
|
|
|
interface TitlebarControlsProps extends ComponentProps<'div'> {
|
|
leftTools?: readonly TitlebarTool[]
|
|
showInspectorToggle: boolean
|
|
tools?: readonly TitlebarTool[]
|
|
onOpenSettings: () => void
|
|
}
|
|
|
|
export function TitlebarControls({
|
|
leftTools = [],
|
|
showInspectorToggle,
|
|
tools = [],
|
|
onOpenSettings
|
|
}: TitlebarControlsProps) {
|
|
const navigate = useNavigate()
|
|
const hapticsMuted = useStore($hapticsMuted)
|
|
const sidebarOpen = useStore($sidebarOpen)
|
|
const inspectorOpen = useStore($inspectorOpen)
|
|
|
|
const toggleHaptics = () => {
|
|
if (!hapticsMuted) {
|
|
triggerHaptic('tap')
|
|
}
|
|
|
|
toggleHapticsMuted()
|
|
|
|
if (hapticsMuted) {
|
|
window.requestAnimationFrame(() => triggerHaptic('success'))
|
|
}
|
|
}
|
|
|
|
const leftToolbarTools: TitlebarTool[] = [
|
|
{
|
|
icon: <NotebookTabs />,
|
|
id: 'sidebar',
|
|
label: sidebarOpen ? 'Hide sidebar' : 'Show sidebar',
|
|
onSelect: () => {
|
|
triggerHaptic('tap')
|
|
toggleSidebarOpen()
|
|
}
|
|
},
|
|
...leftTools
|
|
]
|
|
|
|
const rightToolbarTools: TitlebarTool[] = [
|
|
...tools,
|
|
{
|
|
active: inspectorOpen,
|
|
hidden: !showInspectorToggle,
|
|
icon: <SlidersHorizontal />,
|
|
id: 'session-details',
|
|
label: inspectorOpen ? 'Hide session details' : 'Show session details',
|
|
onSelect: () => {
|
|
triggerHaptic('tap')
|
|
toggleInspectorOpen()
|
|
}
|
|
},
|
|
{
|
|
active: hapticsMuted,
|
|
icon: hapticsMuted ? <VolumeX /> : <Volume2 />,
|
|
id: 'haptics',
|
|
label: hapticsMuted ? 'Unmute haptics' : 'Mute haptics',
|
|
onSelect: toggleHaptics
|
|
},
|
|
{
|
|
icon: <Settings />,
|
|
id: 'settings',
|
|
label: 'Open settings',
|
|
onSelect: () => {
|
|
triggerHaptic('open')
|
|
onOpenSettings()
|
|
}
|
|
}
|
|
]
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
aria-label="Window controls"
|
|
className="fixed left-(--titlebar-controls-left) top-(--titlebar-controls-top) z-70 flex translate-y-[2px] flex-row items-center gap-px pointer-events-auto select-none [-webkit-app-region:no-drag]"
|
|
>
|
|
{leftToolbarTools
|
|
.filter(tool => !tool.hidden)
|
|
.map(tool => (
|
|
<TitlebarToolButton key={tool.id} navigate={navigate} tool={tool} />
|
|
))}
|
|
</div>
|
|
|
|
<div
|
|
aria-label="App controls"
|
|
className="fixed right-(--titlebar-tools-right) top-(--titlebar-controls-top) z-70 flex flex-row items-center justify-end gap-px pointer-events-auto select-none [-webkit-app-region:no-drag]"
|
|
>
|
|
{rightToolbarTools
|
|
.filter(tool => !tool.hidden)
|
|
.map(tool => (
|
|
<TitlebarToolButton key={tool.id} navigate={navigate} tool={tool} />
|
|
))}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function TitlebarToolButton({ navigate, tool }: { navigate: ReturnType<typeof useNavigate>; tool: TitlebarTool }) {
|
|
const className = cn(
|
|
titlebarButtonClass,
|
|
'grid place-items-center bg-transparent select-none [&_svg]:size-4',
|
|
tool.className
|
|
)
|
|
|
|
if (tool.href) {
|
|
return (
|
|
<a
|
|
aria-label={tool.label}
|
|
className={className}
|
|
href={tool.href}
|
|
onPointerDown={event => event.stopPropagation()}
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
title={tool.title ?? tool.label}
|
|
>
|
|
{tool.icon}
|
|
</a>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<button
|
|
aria-label={tool.label}
|
|
aria-pressed={tool.active ?? undefined}
|
|
className={className}
|
|
disabled={tool.disabled}
|
|
onClick={() => {
|
|
if (tool.to) {
|
|
navigate(tool.to)
|
|
}
|
|
|
|
tool.onSelect?.()
|
|
}}
|
|
onPointerDown={event => event.stopPropagation()}
|
|
title={tool.title ?? tool.label}
|
|
type="button"
|
|
>
|
|
{tool.icon}
|
|
</button>
|
|
)
|
|
}
|