import type { ReactNode, RefObject } from "react"; /** * Window state for a WorkspaceWindow. * - "medium": default centered dialog (Jira-style) * - "maximized": renders IDENTICALLY to "medium" — no separate maximize size exists (see onMaximize) * - "minimized": collapsed to a tray pill (rendered by WorkspaceTray, not the window) */ export type WorkspaceWindowState = "medium" | "maximized" | "minimized"; /** Accessible names of the window's icon controls; omitted keys keep the English default. */ export interface WorkspaceWindowLabels { /** Default "Minimize". */ minimize?: string; /** Default "Close". */ close?: string; /** Default "Open full page". */ openFullPage?: string; } /** Props for one WorkspaceWindow instance; minimized windows render separately via WorkspaceTrayProps below. */ export interface WorkspaceWindowProps { /** Title shown in the window's control bar. */ title: ReactNode; /** Current window state. When "minimized", the window renders nothing. */ state: WorkspaceWindowState; /** Window body. */ children: ReactNode; /** Adds the Minimize button to the control bar; omitted hides it. The parent sets `state` to "minimized" itself. */ onMinimize?: () => void; /** @deprecated Never called; no maximize/restore control is rendered. */ onMedium?: () => void; /** @deprecated Never called; no maximize/restore control is rendered. */ onMaximize?: () => void; /** Adds the Close button to the control bar and is its only trigger: Esc and backdrop clicks never close the window. */ onClose?: () => void; /** Optional "pop out to full page" affordance; hidden when omitted. */ onOpenFullPage?: () => void; /** * Fires when the blurred area around the window is clicked. With * `size="full"` the transparent ModalContent covers the whole viewport, so * Chakra's own `closeOnOverlayClick` never fires — the parent handles this * itself (e.g. minimize-on-backdrop-click) via this callback. */ onOverlayClick?: () => void; /** Translated names for the Minimize, Close and Open full page buttons. */ labels?: WorkspaceWindowLabels; /** * Where focus lands when the window closes or minimizes. Without it focus returns to whatever opened the * window, which is gone once it was restored from the tray; point it at a stable element (e.g. the page's main region). */ finalFocusRef?: RefObject; /** Extra controls rendered in the control bar, left of the window buttons. */ headerActions?: ReactNode; /** data-testid for the window container. */ testId?: string; } /** One minimized window in the tray; id is passed back to onRestore/onClose so callers can target the right window. */ export interface WorkspaceTrayItem { /** Identifier handed back to `onRestore` and `onClose`. */ id: string; /** Main text of the pill. */ title: ReactNode; /** Shows an unsaved-changes dot on the built-in pill; named for screen readers by the tray's `dirtyLabel`. */ isDirty?: boolean; /** Secondary text after the title on the built-in pill, e.g. a record type or id. */ subtitle?: ReactNode; } /** Props for the cluster of minimized-window pills; renders nothing when items is empty. */ export interface WorkspaceTrayProps { /** Minimized items rendered as restore pills along the bottom. */ items: WorkspaceTrayItem[]; /** Called with the item id when its pill is clicked to bring the window back. */ onRestore?: (id: string) => void; /** Called with the item id from the pill's close button; omitted removes that button. */ onClose?: (id: string) => void; /** data-testid for the tray; pills, restore, dirty dot and close buttons derive suffixed ids from it. Default "workspace-tray". */ testId?: string; /** Which bottom corner the tray sits in. Default "bottom-end". Portalled above modals but outside their focus trap: a pointer restores a pill while a modal is open, Tab does not reach it. */ placement?: "bottom-end" | "bottom-start"; /** Distance from the viewport edges, to clear a sticky bar. Numbers are px, strings any CSS length. Default 1rem each. */ offset?: { /** Gap from the side edge. */ x?: number | string; /** Gap from the bottom edge. */ y?: number | string; }; /** Accessible name for each pill's close button. Default "Close"; pass one that names the item for non-English or per-window labels. */ closeLabel?: (item: WorkspaceTrayItem) => string; /** Accessible name of the unsaved dot on a pill with `isDirty`. Default "Unsaved changes". */ dirtyLabel?: string; /** Accessible name of the tray region. Default "Minimized windows". */ label?: string; /** Replaces one item's pill inside the same tray; gets the item, bound restore/close and `pill`, the built-in pill (dot, subtitle, close button) to return as is or wrap, e.g. in WorkspaceTrayPreview. */ renderItem?: (item: WorkspaceTrayItem, api: { /** Calls `onRestore` with this item's id. */ restore: () => void; /** Calls `onClose` with this item's id. */ close: () => void; }, pill: ReactNode) => ReactNode; } type WorkspaceTrayPreviewTriggerProps = { "aria-expanded": boolean; "aria-controls": string | undefined; "aria-describedby": string | undefined; }; /** Props for WorkspaceTrayPreview — despite the name, works for any hover trigger, not just tray pills. */ export interface WorkspaceTrayPreviewProps { /** * The trigger markup (typically a tray pill). The preview + trigger share one * open/close timer so the cursor can travel pill ⇄ preview without the card * closing (hover-bridge pattern). A render-prop form receives `triggerProps` * (aria-expanded, plus aria-controls/aria-describedby while open): spread them * on the one focusable trigger so assistive tech hears the card. */ children: ReactNode | ((api: { /** Accessibility attributes to spread on the one focusable trigger. */ triggerProps: WorkspaceTrayPreviewTriggerProps; }) => ReactNode); /** * The preview body, rendered inside the floating card. Headless: the consumer * supplies whatever it wants to show (ticket summary, chips, actions, etc.). * A render-prop form receives the card's own `close()` so an action inside * the preview can dismiss it. */ preview: ReactNode | ((api: { /** Closes the card immediately, e.g. after an action inside it. */ close: () => void; }) => ReactNode); /** Whether the preview is enabled at all (e.g. disable on touch devices). */ enabled?: boolean; /** ms before the card opens on hover/focus (anti-flash). Default 350. */ openDelayMs?: number; /** ms grace before the card closes on leave (bridge). Default 200. */ closeDelayMs?: number; /** * Where the card opens relative to the trigger. Default "top-end" (above, * right-aligned) — matches a bottom-right tray. */ placement?: "top-start" | "top-end" | "bottom-start" | "bottom-end"; /** Fires with `true` when the card opens and `false` when it closes (never before it opened) — use `true` to lazy-load the preview data. */ onOpenChange?: (open: boolean) => void; /** Card width. Default 340px, capped to the viewport on small screens. */ width?: string | number; /** data-testid for the floating card. */ testId?: string; } export {};