import type { MouseEvent, ReactNode } from 'react' import { Clock, MoreHorizontal, type LucideIcon } from 'lucide-react' import { Tooltip } from '../shared/Tooltip' import { ContextMenu, useContextMenu } from '../shared/ContextMenu' import { fs } from '../shared/DialogShell' /** * The small pieces the Plan tab is built from, so the day list, the places * list, the inspector and the day panel speak one language: the head band a * panel opens with, its icon buttons, the "…" that keeps a row's actions out * of sight until they are wanted, and the pills a row states its facts in. */ /** The band a floating panel opens with, tinted like every card head in the planner. */ export const PANEL_BAR = 'flex min-h-[52px] flex-none items-center gap-1.5 border-b border-edge-faint px-3 py-2' /** A 32px icon control on a panel bar. Raised like an active filter tab while `active`. */ export function BarButton({ label, onClick, active = false, disabled = false, children, className = '', ariaPressed, ariaExpanded, ariaHaspopup, placement = 'bottom' }: { label: string onClick: (e: MouseEvent) => void active?: boolean disabled?: boolean children: ReactNode className?: string ariaPressed?: boolean ariaExpanded?: boolean ariaHaspopup?: 'dialog' | 'menu' placement?: 'top' | 'bottom' | 'left' | 'right' }) { return ( ) } export interface MenuEntry { label?: string icon?: LucideIcon onClick?: () => void danger?: boolean divider?: boolean } /** * The "…" of a row or card: the actions a right-click also offers, for the * mouse that doesn't right-click and for the keyboard. Quiet at rest, it comes * forward with its row's hover or keyboard focus (the row carries the `group` * class). Keyboard focus only: a row a click selected keeps the focus, and the * button stayed out on it after the mouse had left. */ export function MoreButton({ label, items, size = 26, alwaysVisible = false, className = '' }: { label: string items: Array size?: number alwaysVisible?: boolean className?: string }) { const menu = useContextMenu() const entries = items.filter(Boolean) as MenuEntry[] // Nothing but dividers is nothing to offer. if (!entries.some(e => !e.divider)) return null return ( <> ) } /** A time or time range as a white badge, like the other facts on a card. */ /** A small white badge on a row's second line, for what the row says about when. */ export function WhiteBadge({ children, icon, className = '' }: { children: ReactNode icon?: ReactNode className?: string /** On a white row: a hairline under the shadow, so the white pill does not vanish there. */ quiet?: boolean }) { return ( {icon} {children} ) } export function TimePill({ children, className = '', quiet = false }: { children: ReactNode; className?: string; quiet?: boolean }) { return }>{children} } type PillTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'accent' // White badges on whatever tint the row has; the tone only colours the words and the icon. const PILL_TONES: Record = { neutral: 'bg-surface-card text-content-secondary shadow-sm ring-1 ring-inset ring-edge-faint', success: 'bg-surface-card text-success shadow-sm ring-1 ring-inset ring-edge-faint', warning: 'bg-surface-card text-warning shadow-sm ring-1 ring-inset ring-edge-faint', danger: 'bg-surface-card text-danger shadow-sm ring-1 ring-inset ring-edge-faint', info: 'bg-surface-card text-info shadow-sm ring-1 ring-inset ring-edge-faint', accent: 'bg-accent text-accent-text', } /** A small fact on a row (a status, a carrier, a phase): a white badge, its words in one of the status tones. */ export function SoftPill({ tone = 'neutral', icon, children, className = '', caps = false }: { tone?: PillTone icon?: ReactNode children?: ReactNode className?: string caps?: boolean }) { return ( {icon} {children} ) } /** The tint a card head takes from a colour (a category, a note, a plugin's day tone). */ export const tintOf = (color: string, percent = 11) => `color-mix(in srgb, ${color} ${percent}%, transparent)`