import * as react_jsx_runtime from 'react/jsx-runtime'; import * as React from 'react'; /** * A labelled command in a row of actions that gives up space in a fixed order. * * `icon` is required rather than optional because it is the row's whole * narrow-width strategy: every rung below the first replaces labels with * glyphs, and an action with no glyph would have to either keep its label while * its neighbours lose theirs or collapse to an empty button. */ interface ResponsiveAction { /** Stable id, used as the React key and as the identity the row sheds by. */ id: string; label: string; /** Static Font Awesome glyph, for example `fa-plus`. */ icon: string; onSelect: () => void; /** * `"default"` is the filled primary CTA and **requires `shortcut`** (rendered * inline as a bare `Kbd`). See `exxat-primary-button-shortcuts.mdc`. */ variant?: "default" | "outline" | "secondary" | "ghost" | "destructive"; /** * Keyboard chord. Bound with `` whether the action is on the row or * in the overflow menu, so narrowing the window never costs a shortcut. * Build with `useModKeyLabel()` + `useAltKeyLabel()`. */ shortcut?: string; disabled?: boolean; /** * - `"row"` (default) — eligible for the visible row, up to the max. * - `"overflow"` — **always** under More. Tertiary commands declare this so * they never compete for a row slot. */ placement?: "row" | "overflow"; } /** * Three, and the reason is scanning rather than space: a row of peers is read as * a set, and past three the reader stops seeing "the actions here" and starts * reading a list, at which point the primary stops being obviously primary. * Wider viewports do not raise it — there is always room for a fourth button and * it is always the wrong thing to do. */ declare const RESPONSIVE_ACTIONS_MAX_VISIBLE = 3; /** At or below this container width every row action drops to icon-only. */ declare const RESPONSIVE_ACTIONS_COMPACT_MAX_WIDTH_PX = 640; /** At or below this width row secondaries move into More; the primary stays. */ declare const RESPONSIVE_ACTIONS_OVERFLOW_ONLY_MAX_WIDTH_PX = 520; /** * Container width tiers for {@link ResponsiveActionRow}, measured on whatever * element the returned ref is attached to. * * Container rather than viewport: the same row sits in a page header that spans * the content column and in a table toolbar that shares its row with a filter * chip rail, so the space a row actually has is not something the window width * knows. Width is held as `null` until first measurement so nothing collapses * for one frame on the way in. */ declare function useResponsiveActionWidth(): { ref: React.RefObject; /** Measured container width, `null` until first measurement. */ width: number | null; compact: boolean; overflowOnly: boolean; }; /** * Whether a row has run out of horizontal room, judged by a wrapping neighbour * rather than by the row's own width. * * A width tier cannot see this case. The filter chip rail wraps, so when the * row runs out of room the overflow is absorbed into a second line instead of * being reported as overflow: at 1400px with two filters the row is nowhere * near "narrow", yet there is no room left on the line and the chips drop * below while the action labels sit there having caused it. The rail taking a * second line *is* the signal, and the cheapest thing to sell for that room is * the action labels, because a glyph plus a Tip loses nothing a chip's value * would. * * ## Why this cannot oscillate * * Hiding the labels widens the rail, which un-wraps it, which is the condition * that asked for the labels back. So within one measurement epoch `crowded` * only ever travels false → true, and nothing reads it back the other way. An * epoch ends when a new answer is actually possible: * * - `resetKey` changes — the chips themselves changed, so the old verdict is * about a row that no longer exists. * - `rowWidth` grows — there is more room than when we last decided. * * Narrowing deliberately does not reset. Less room cannot make labels fit, so * re-deriving would spend two renders per frame of a rail drag to reach the * answer already on screen. */ declare function useWrapCrowding({ railRef, rowWidth, resetKey, enabled, }: { /** The wrapping neighbour, e.g. the filter chip rail. */ railRef: React.RefObject; /** * Width of the row that contains both the rail and the actions. Must be * measured on the row, not the rail: the rail's own width is an *output* of * this decision, so latching on it would compare against a moving target. */ rowWidth: number | null; /** Identity of the rail's contents, e.g. the active filter count. */ resetKey: unknown; enabled: boolean; }): boolean; interface ResponsiveActionRowProps { items: ResponsiveAction[]; /** Row is at or below the compact width — labels drop to glyphs. */ compact?: boolean; /** Row is at or below the overflow-only width — secondaries move into More. */ overflowOnly?: boolean; /** Accessible name for the group, e.g. `"Page actions"`. */ ariaLabel: string; /** Value for `data-slot`, so call sites stay addressable in tests and CSS. */ dataSlot?: string; /** Prefix for the dev-only missing-shortcut error. */ debugOwner?: string; className?: string; } /** * A row of up to {@link RESPONSIVE_ACTIONS_MAX_VISIBLE} commands plus a More * menu, shared by the page header and the table toolbar so the two cannot drift. * * How space is given up, in order: * * 1. Labels drop to glyphs, each keeping its name in a Tip. * 2. Row secondaries move into More. The filled primary never does — it is the * one command the surface exists to offer, and a primary you have to open a * menu to find is not a primary. * 3. Nothing further. What is left is one icon button and one menu. * * Every action binds its chord with `` from wherever it currently sits, * so a keyboard user's path to a command does not depend on the window being wide * enough to show it (P6). */ declare function ResponsiveActionRow({ items, compact, overflowOnly, ariaLabel, dataSlot, debugOwner, className, }: ResponsiveActionRowProps): react_jsx_runtime.JSX.Element | null; export { RESPONSIVE_ACTIONS_COMPACT_MAX_WIDTH_PX, RESPONSIVE_ACTIONS_MAX_VISIBLE, RESPONSIVE_ACTIONS_OVERFLOW_ONLY_MAX_WIDTH_PX, type ResponsiveAction, ResponsiveActionRow, type ResponsiveActionRowProps, useResponsiveActionWidth, useWrapCrowding };