import * as React from 'react'; export { markdownToText } from './markdown.cjs'; /** * The one action primitive. Primary is a gradient; everything else is flat. * Never more than one primary per view. */ interface ButtonOwnProps { children?: React.ReactNode; /** primary = the one action. brandSoft = marketing CTA at >=19px/700 only (AA large-text). */ variant?: "primary" | "brandSoft" | "secondary" | "ghost" | "danger" | "link"; size?: "sm" | "md" | "lg"; /** Phosphor icon name, leading, e.g. "plus" */ icon?: string; /** Phosphor icon name, trailing */ trailingIcon?: string; /** The site's signature inset-circle arrow. Use on hero CTAs only. */ arrow?: boolean; /** Label stays put; a spinner replaces the leading icon. */ loading?: boolean; block?: boolean; /** When `disabled` (or `loading`), stay in the tab order instead of going natively * `disabled`: renders `aria-disabled="true"` only while actually disabled/loading (never * the `disabled` attribute), keeps the `:focus-visible` ring, and swallows * click/Enter/Space — including never letting a click bubble to an ancestor's own * handler — without ever submitting a form. Use whenever a button's disabled state can * flip while it holds focus (e.g. a controlled panel re-seeding mid-edit) — a native * `disabled` drops focus to `` the instant it applies, and nothing can intercept or * redirect that. Once enabled again, this behaves exactly like a plain button: no * `aria-disabled` attribute at all. */ focusableWhenDisabled?: boolean; disabled?: boolean; } /** `as="a"` takes anchor props (href, target, rel); a plain button refuses them. */ type ButtonProps = (ButtonOwnProps & Omit, "disabled"> & { as?: "button"; href?: never; target?: never; rel?: never; }) | (ButtonOwnProps & Omit, "children"> & { as: "a"; }); declare function Button({ children, variant, size, icon, trailingIcon, arrow, loading, disabled, focusableWhenDisabled, block, as, className, onClick, onKeyDown, ...rest }: ButtonProps): React.JSX.Element; /** A square action carrying only a Phosphor glyph. Always needs a label for a11y. */ interface IconButtonProps extends React.ButtonHTMLAttributes { /** Phosphor icon name, e.g. "dots-three-vertical" */ icon: string; /** Accessible name, also the title tooltip. Required. */ label: string; size?: "sm" | "md" | "lg"; /** "ghost" and "outline" are quiet: no fill, the glyph carries the tone. "solid" is a * filled button in the tone's colour — for the one action on a surface that has to be * found at a glance, like the add/remove button on every row of a transfer list. "soft" * is the same idea with a lighter, less saturated fill and a visibly darker border * drawn around it, for a caller that wants the button to read as a button without the * full-saturation "solid" look — only "ok" and "danger" tones are defined for it. */ variant?: "ghost" | "outline" | "solid" | "soft"; /** What the action does. On "solid"/"soft" it picks the fill; on "ghost"/"outline" it * tints the glyph. "neutral" is the default look of each variant, which for "solid" is * the brand fill ("soft" has no neutral fill — pass "ok" or "danger"). Every tone has * hover, pressed and focus states and is legible in both themes — the fills are the * --btn-solid-* / --btn-soft-* tokens, measured against light and dark grounds. */ tone?: "neutral" | "ok" | "danger"; round?: boolean; disabled?: boolean; className?: string; } declare function IconButton({ icon, label, size, variant, tone, round, disabled, className, ...rest }: IconButtonProps): React.JSX.Element; type Placement = "bottom-start" | "bottom-end" | "bottom-center" | "top-start" | "top-end" | "top-center"; interface PopoverProps { open?: boolean; anchorRef: React.RefObject; /** The element whose own clicks open and close this popover, when that is more than * the anchor — a form field's label toggles it too, but the surface is positioned * against the input box alone. A pointerdown inside it is not an "outside" press, so * the toggle it is about to run is not undone by a dismissal a moment earlier. * Defaults to `anchorRef`. */ triggerRef?: React.RefObject; onClose?: () => void; placement?: Placement; offset?: number; /** `true`: exactly the anchor's width. `"min"`: at least the anchor's width, growing * with content past it (a select whose options are wider than its box). */ matchWidth?: boolean | "min"; width?: number; minWidth?: number; maxHeight?: number; /** Standard content padding on the body. The header and footer own their own. */ padded?: boolean; role?: string; label?: string; closeOnOutside?: boolean; closeOnEscape?: boolean; returnFocus?: boolean; className?: string; style?: React.CSSProperties; /** DS-51: the standard header — this title and a Close button (labelled "Close", calls * `onClose`), with the header's own padding. A `header` given as well renders beneath * the title row inside the same pinned region. The title also names the surface when no * `label` is given. */ title?: string; /** Pinned above the body and never scrolled away: a back link, a title, a segmented * toggle, a search field. */ header?: React.ReactNode; /** Pinned below the body and never scrolled away: the Add / Apply / Confirm action and * any summary beside it. */ footer?: React.ReactNode; /** The body — the one region of the surface that scrolls. */ children?: React.ReactNode; /** DOM id of the surface, so a trigger can point at it with aria-controls. */ id?: string; /** Pointer and focus events on the surface itself, for a popover opened by hover or focus * (see useHoverPopover). */ onPointerEnter?: React.PointerEventHandler; onPointerLeave?: React.PointerEventHandler; onBlur?: React.FocusEventHandler; } interface PopoverPosition { left: number; width: number | undefined; minWidth: number | undefined; top: number | null; bottom: number | null; maxH: number; /** The widest the layer may be: the viewport less its margins. A fixed `width`, a * `minWidth` or an anchor-matched width wider than the screen is capped to this, so the * surface — and the action in its footer — never runs past the viewport edge. */ maxW: number; side: "top" | "bottom"; anchorWidth: number; } /** The least height the body is left with. Below this a list shows a sliver — or nothing * — between a tall header and footer, which reads as an empty picker. When the room * beside the anchor cannot hold the pinned regions plus this much body, the layer moves * (see usePopoverPosition) rather than squeezing the body further. A body whose whole * content is shorter than this only needs its content. */ declare const POPOVER_BODY_MIN = 96; /** * Fixed-position layer geometry for an anchored surface. Flips side when the * preferred one doesn't fit, clamps to the viewport, and re-measures continuously * while open. * * Continuously, on an animation frame, rather than on scroll and resize events: those * two cover an anchor that moves because the PAGE moved, and miss every other reason an * anchor moves. The one that broke this was a row inside a virtualized list — rows are * positioned by a CSS transform off their index, so re-sorting the list slides a row to * a new slot with no scroll, no resize and no layout event of any kind, and a popover * anchored to it simply stayed behind. Re-measuring per frame costs one * getBoundingClientRect on one element for as long as a popover is open, and state is * only set when the geometry actually changed (see samePosition), so a stationary anchor * causes no re-renders at all. * * The side is chosen from the layer's real size once `layerRef` has rendered (see * measureLayer): it flips when what it needs does not fit on the preferred side and the * other side has more room. Until then — the first frame, before there is anything to * measure — `estHeight` stands in, and only a nearly-exhausted side (under 180px) flips, * so a small layer of unknown size is not thrown to the far side on a guess. The real * width replaces `estWidth` the same way, which is what keeps an end- or center-aligned * layer of content width lined up with its anchor. * * The same per-frame measurement is what re-fits the layer when its CONTENT changes size * while open — a picker stepping from a short search header to a tall header plus a * footer. No event announces that either, and the next frame sees the new header and * footer heights: the side is re-chosen from the new need, and when even the pinned * regions plus POPOVER_BODY_MIN of body fit on neither side, the layer slides over its * anchor to the far viewport edge instead of collapsing the body (see measureLayer and * the `beside` branch). The height is never capped past the real room, so the surface * itself never overflows the viewport. * * The same loop is what notices the anchor being REMOVED — a virtualized row scrolled * out of the mounted band, a menu item deleted — and reports it through `onDetach`, so * an anchored layer can never end up pinned to an element that no longer exists. */ declare function usePopoverPosition(open: boolean, anchorRef: React.RefObject, opts?: Partial & { estHeight?: number; estWidth?: number; onDetach?: () => void; /** The rendered layer, so its real size replaces the estimates once it exists. */ layerRef?: React.RefObject; }): PopoverPosition | null; /** * An anchored floating surface: menus, pickers, disclosure panels, meters. * Owns placement, dismissal, focus return — and its own layout, which is the one rule * every popover shares: THE SURFACE NEVER SCROLLS. * * It is always three stacked regions: an optional `header`, the body (`children`), and * an optional `footer`. Header and footer are pinned; the body is the only thing that * scrolls, and it shrinks to whatever height is left once they are laid out. The surface * is capped by the real room between the anchor and the viewport edge (or `maxHeight`, if * that is smaller) and flips to the other side when it doesn't fit, so the footer is * never the thing that gets cut off. * * This is structural rather than advice because the failure it prevents is structural: * a surface that scrolls as a whole takes its Apply button below the fold with it, and a * scrolling list inside a scrolling surface is two nested scrollbars. Anything inside * the body that owns a scroll region of its own (a Menu, a Select's options) is laid out * to shrink into the body rather than overflow it — see `.fd-pop-body` in * components.css — so there is only ever one scrollbar. */ declare function Popover({ open, anchorRef, triggerRef, onClose, placement, offset, matchWidth, width, minWidth, maxHeight, padded, role, label, closeOnOutside, closeOnEscape, returnFocus, className, style, title, header, footer, children, id, onPointerEnter, onPointerLeave, onBlur, }: PopoverProps): React.ReactElement> | null; interface MenuItem { id?: string; label?: React.ReactNode; icon?: string; description?: React.ReactNode; meta?: React.ReactNode; /** A key HINT. Renders a cap, binds nothing. */ shortcut?: React.ReactNode; /** Present (true/false) makes the row a menuitemradio with a check column. */ checked?: boolean; disabled?: boolean; submenu?: MenuItem[]; /** A secondary action pinned to the row (e.g. delete). Rendered as a sibling button, so it is keyboard-reachable and does not nest inside the row button. */ trailingAction?: { icon?: string; label: string; danger?: boolean; closeOnSelect?: boolean; onSelect?: (item: MenuItem) => void; }; onSelect?: (item: MenuItem) => void; kind?: "separator" | "section" | "custom"; render?: () => React.ReactNode; } interface MenuProps { items: MenuItem[]; onSelect?: (item: MenuItem) => void; onClose?: () => void; autoFocus?: boolean; header?: React.ReactNode; footer?: React.ReactNode; className?: string; } /** * A keyboard-complete menu list. Items may be: * {id,label,icon,description,meta,shortcut,checked,disabled,submenu,onSelect} * {kind:"separator"} · {kind:"section",label} · {kind:"custom",render} * `shortcut` is a HINT — it renders a cap and binds nothing. * * `header` and `footer` are pinned and only the items scroll: the whole menu is one * shrinkable frame, so inside a Popover body a search header stays put while a long list * scrolls beneath it, instead of the header scrolling away with the items. */ declare function Menu({ items, onSelect, onClose, autoFocus, className, footer, header }: MenuProps): React.JSX.Element; interface MenuButtonProps extends Omit { label?: React.ReactNode; icon?: string; trailingIcon?: string | null; placement?: Placement; variant?: "chip" | "ghost"; size?: "sm" | "md"; disabled?: boolean; title?: string; ariaLabel?: string; width?: number; minWidth?: number; maxHeight?: number; children?: React.ReactNode; } /** * Trigger + menu in one: a compact chip that opens an anchored Menu. * Used for model pickers, row overflow actions, view switchers. */ declare function MenuButton({ label, icon, trailingIcon, items, onSelect, placement, variant, size, disabled, title, ariaLabel, width, minWidth, maxHeight, className, children, header, footer, }: MenuButtonProps): React.JSX.Element; /** Two to four mutually exclusive views — density, date range, light/dark. */ interface SegmentedControlProps { options: Array; value?: string; onChange?: (value: string) => void; /** Names the group. Each button already names itself from its own text, so the * buttons were never the gap — the GROUP was: two segmented controls in one * toolbar both announce as an unnamed group of buttons. There is no visible * `label` prop because this control is used inline next to the thing it * switches (a density toggle beside a table, a range toggle beside a chart), * where a stacked field label would be wrong; aria-label is what names it. * The rest spread below already forwarded this to the DOM — what was missing * was any typed way for a caller to pass it. */ "aria-label"?: string; className?: string; } declare function SegmentedControl({ options, value, onChange, className, ...rest }: SegmentedControlProps): React.JSX.Element; interface CopyButtonProps extends Omit { /** The text put on the clipboard. */ text: string; /** Accessible name. Default `Copy `; pass one when the text is long or opaque. */ label?: string; } /** * An IconButton that copies `text`. It confirms a copy that landed with a check glyph for * `COPIED_FEEDBACK_MS`, and reports one the browser refused with a warning glyph for the same * time; both are announced politely, so a screen reader hears what happened. The accessible * name stays "Copy ..." throughout. */ declare function CopyButton({ text, label, ...rest }: CopyButtonProps): React.JSX.Element; /** Mouse rest time before the panel opens: a pointer sweeping across the page does not * flash panels, a pointer resting on the trigger opens one. */ declare const HOVER_POPOVER_OPEN_DELAY_MS = 150; interface UseHoverPopoverOptions { /** Mouse rest time before hover opens the panel. */ openDelayMs?: number; } interface HoverPopoverApi { /** Spread on the trigger, with `triggerRef` as its ref. Adds the hover, focus and tap * handlers plus aria-expanded / aria-controls / aria-haspopup. */ triggerProps: React.HTMLAttributes & { "aria-expanded": boolean; "aria-controls": string; "aria-haspopup": "dialog"; }; triggerRef: React.RefObject; /** Spread on `` (give it a `label`): open state, anchor, close, id and the * pointer / focus handlers that keep it open while the pointer or focus is on it. */ popoverProps: Pick; open: boolean; } /** * useHoverPopover: opens a `Popover` on mouse hover and keyboard focus as well as on tap, for * explanations too big for a Tooltip. * * It is `useHoverIntent` (the logic behind `useTooltip`: mouse-only hover, `:focus-visible` * focus, tap toggle, a close grace delay so the pointer can cross onto the panel) plus the * Popover wiring: Escape and outside press come from Popover itself, and hover or focus * opening never moves focus (`returnFocus` is off, so Escape leaves focus on the trigger). * Blur to anything outside the trigger and the panel closes it. */ declare function useHoverPopover({ openDelayMs }?: UseHoverPopoverOptions): HoverPopoverApi; interface AnchoredPopoverProps { rect: DOMRect | null; width?: number; onClose: () => void; children: React.ReactNode; } declare function AnchoredPopover({ rect, width, onClose, children }: AnchoredPopoverProps): React.ReactPortal | null; /** * The container. White, 12px radius, hairline border, low soft shadow, and a * divider under the header — that divider is the signature detail on flytedesk.com. */ interface CardProps { title?: React.ReactNode; /** Right-aligned control in the header — usually an IconButton or a link Button. */ action?: React.ReactNode; footer?: React.ReactNode; children?: React.ReactNode; elevation?: "flat" | "rest" | "raised"; hoverable?: boolean; /** Set false to let children run edge to edge (tables, images). */ padded?: boolean; /** MP-271: renders the dashed draft/placeholder treatment — an unsaved or * not-yet-created item shown alongside real ones (SMS-12's local-draft audience * card is the first consumer). The dash uses `--border-strong`, an existing * token also used elsewhere for a "more emphatic than the hairline default" * border, so light, dark and `[data-contrast="high"]` all resolve it correctly * with no new colour. */ draft?: boolean; /** Renders the card as a real `` (keeps `hoverable`'s lift, adds real * hover/focus-visible/press states, MP-271). Takes precedence over `onClick`. * Review (comment 2920, finding 3): when either `href` or `onClick` is set, `action` * and `footer` are rendered OUTSIDE the ``/`