UI Pro
The interactive layer @caisson/ui doesn't ship, Tooltip, Popover, and Menu hand-rolled against zero Radix and zero @floating-ui, plus the data grid, hash-chain audit timeline, and diff viewer a real dashboard needs.
What it is
ui-pro is the commercial component tier built on the open @caisson/ui floor: a hand-rolled, zero-Radix, zero-@floating-ui interactive layer (Tooltip, Popover, Menu) plus eleven sellable data surfaces, an advanced data grid, virtualized tree, ops/coverage matrix, hash-chain audit timeline, redaction-aware payload and diff viewers, type-to-confirm, date-range picker, charts, kanban board, and command palette.
What ships in the module
Tooltip, Popover, Menu, zero Radix, zero @floating-ui
Popover and Menu render their own trigger <button>, portal their panel with react-dom's createPortal, and position it with the package's own computeFloatingPosition, a pure flip-and-clamp function shared through the useFloatingPosition hook by all three interactive primitives (ADR-0291). Menu follows the WAI-ARIA Menu Button pattern (role="menu", roving tabindex, Up/Down/Home/End); Popover follows the APG disclosure-with-portal pattern (explicit focus-in on open, focus-return on every keyboard-initiated close).
DataTablePro, filter, group, aggregate, export, virtualize
DataTablePro composes the open kit's own Button and Select, then drives the pure lib/table-ops.ts transforms (applyFilters, sortRows, groupRows, aggregate, toCsv, compareCells) plus lib/virtual.ts's windowRange for row virtualization. The transforms are exported standalone (DataTableProColumn, SavedView) so the filtering/grouping/CSV logic is unit-testable and reusable server-side, not locked inside the component.
AuditTimeline, renders a hash-chain verification result
AuditTimeline takes AuditEntry rows extending lib/audit-chain.ts's ChainEntry, and an optional anchor-derived statuses prop of six-state badges (wired to @caisson/kernel's per-row verifier, ADR-0331/0344). shortHash formats the display; chainIntact and verifyChain are exported for a caller to run the actual chain check, the component displays a verdict, it doesn't compute one.
PayloadViewer redacts by default; DiffViewer redacts on request
PayloadViewer falls back to lib/redact.ts's DEFAULT_REDACT_KEYS and uses isRedactedKey/redactValue (the predicate now re-exported from @caisson/kernel, ADR-0331) whenever the caller doesn't supply its own key list (masking is on out of the box. DiffViewer's JSON mode only redacts when the caller passes its own redactKeys set, which it threads into lib/diff.ts's diffJson (that module calls redactValue internally); DiffViewer imports no default key list itself, and its plain text-line diff mode has no redaction path at all) so a support or audit screen stays unmasked unless the integrator wires redactKeys explicitly.
TypeToConfirm, DateRangePicker, charts, CommandPalette, KanbanBoard
Rounding out the eleven: TypeToConfirm gates a destructive action behind an exact-text match; DateRangePicker ships standardPresets (thisMonth, lastNDays, fiscalQuarter, billingCycle...) from lib/date-presets.ts; AreaChart/BarChart/LineChart/Sparkline read lib/charts.ts's linearScale/areaPath/linePath/niceTicks; CommandPalette runs lib/fuzzy.ts's fuzzyFilter/fuzzyMatch; KanbanBoard drives lib/board.ts's columnCards/moveCard.
/**
* Computes a viewport-relative `{top, left}` (paired with `position: fixed`, matching
* `getBoundingClientRect`'s coordinate space) for a panel anchored to `trigger` on the
* `preferred` side. Flips to the opposite side if the preferred side would overflow the viewport
* and the opposite side fits better; otherwise falls back to `preferred` unfit. Both axes are then
* clamped into `[gap, viewport - panel - gap]` — cheap insurance on the axis that already fit (a
* no-op there) and the only thing keeping the *main* axis on-screen when neither placement fit.
* A panel taller/wider than the viewport can still get clamped to `gap` on both ends and overflow
* regardless — coordinates alone can't shrink it, so panels also carry their own
* `max-height`/`overflow-y: auto` (see `.cs-popover`/`.cs-menu`) as the belt-and-braces.
*/
export function computeFloatingPosition(
trigger: Rect,
panel: Size,
viewport: Size,
preferred: Placement = "bottom",
gap = 8,
): FloatingPosition {
const placement = fits(preferred, trigger, panel, viewport, gap)
? preferred
: fits(OPPOSITE[preferred], trigger, panel, viewport, gap)
? OPPOSITE[preferred]
: preferred;
const { top, left } = place(placement, trigger, panel, gap);
const clampedLeft = Math.min(
Math.max(left, gap),
Math.max(gap, viewport.width - panel.width - gap),
);
const clampedTop = Math.min(
Math.max(top, gap),
Math.max(gap, viewport.height - panel.height - gap),
);
return { top: clampedTop, left: clampedLeft, placement };
}- computeFloatingPosition is the entire positioning engine, a pure, unit-tested function with no @floating-ui and no Radix import anywhere in the file
- the OPPOSITE-indexed fits() call is the flip logic: try the preferred side, fall back to the opposite side, or give up and let the clamp save it
- clampedLeft/clampedTop are the belt-and-braces, even an unfit placement gets pinned inside the viewport instead of rendering off-screen