'use client' import { useId, useState, type FormEvent } from 'react' import { cn } from '../../lib/cn' import { Button } from '../Button' import { Input } from '../Input' import { Text } from '../Text' import { VisuallyHidden } from '../VisuallyHidden' import { ConfirmDialog } from '../ConfirmDialog' import { LockIcon } from '../internal/icons' export interface SavedView { id: string name: string /** Rows the view currently matches. */ count?: number /** Visible to the whole workspace. */ shared?: boolean /** Built in — cannot be overwritten or deleted. */ locked?: boolean } export interface SavedViewsProps { views: SavedView[] value: string onValueChange: (id: string) => void /** The filters, sort or columns have changed since the view was loaded. */ dirty?: boolean /** Overwrite the current view. */ onSave?: () => void onSaveAs?: (name: string) => void onDiscard?: () => void onDelete?: (view: SavedView) => void label?: string className?: string } /** * Named combinations of filters, sort and columns — "My open deals", "Churn * risk" — as a row above a table. * * The part usually missing is the unsaved state. Change a filter on a saved * view and the table no longer shows what the view's name says; this says so, * next to the three honest choices — save over it, save as a new view, or put * it back. Built-in views cannot be saved over, only copied. */ export function SavedViews({ views, value, onValueChange, dirty = false, onSave, onSaveAs, onDiscard, onDelete, label = 'Saved views', className, }: SavedViewsProps) { const nameId = useId() const [naming, setNaming] = useState(false) const [name, setName] = useState('') const [pending, setPending] = useState(null) const current = views.find((view) => view.id === value) const saveAs = (event: FormEvent) => { event.preventDefault() if (!name.trim() || !onSaveAs) return onSaveAs(name.trim()) setName('') setNaming(false) } return (
    {views.map((view) => { const active = view.id === value return (
  • ) })}
{(dirty || naming) && current && (
{naming ? (
setName(event.target.value)} autoFocus containerClassName="min-w-[160px] flex-1" />
) : ( <> Unsaved changes to “{current.name}” {onDiscard && ( )} {onSaveAs && ( )} {onSave && !current.locked && ( )} )}
)} {onDelete && current && !current.locked && !dirty && !naming && ( )} setPending(null)} onConfirm={() => { if (pending) onDelete?.(pending) setPending(null) }} destructive title={`Delete “${pending?.name ?? ''}”?`} description={pending?.shared ? 'It is shared, so it disappears for everyone in the workspace.' : 'The rows stay; only the saved view is removed.'} confirmLabel="Delete view" />
) }