Skip to content

Shared UI component library

At a glance

  • Action (the base icon-button factory) isn't exported — only its 19 pre-bound variants are.
  • Four focused "cell" molecules cover computed values, confirm/deny, inline edit, and route-level errors.
  • ConfigLabel expects translation keys, not display text — it runs both props through t().
  • ui's MultiSelect and node-inputs's MultiSelect are unrelated components that happen to share a name.

packages/ui is a small library of Mantine-based building blocks shared across the apps: icon actions, cells for tabular/config UIs, layout chrome, and a couple of standalone widgets. This page is a functional tour — what each is for and where it's typically reached for — as a companion to the Reference section's prop signatures.

Actions: one icon button factory, many pre-bound variants

atoms/action/action.tsx defines Action — a Mantine ActionIcon wrapped with an optional Tooltip and a required Icon (a react-icons component) — but Action itself isn't exported from the package; atoms/action/index.ts only re-exports its variants. Each variant is a couple of lines: Action pre-bound to one specific icon and default variant. There are nineteen — Add, Edit, Delete, Move, Cancel, Complete, Connect, Settings, Options, Reset, Grid, Pointer, Lock / UnLock, TextSelect / FieldSelect, Clipboard / CellClipboard / ClipboardPlus — covering everything from the file-viewer selection clipboard buttons to graph-editing gestures. If you need a new icon button, adding a variant file (icon + default variant) is the existing pattern rather than using ActionIcon directly.

Cells: small, focused table/config renderers

Four molecules, each doing one narrow job inside a table or config row:

  • ComputedCell — renders a computed value, handling the shapes that value can actually take: null ("can't compute"), undefined ("undefined"), an empty string ("empty"), a single-element array (unwrapped and shown directly, after first flattening a [[...]] double-wrap), or a multi-element array (shown one at a time with Move buttons to step through it, labeled n/total in a Badge). Non-array values are passed through parseOutput (utils) before display.
  • ConfirmCell — a plain Confirm/Cancel button pair (handleAccept/handleDeny), no state of its own.
  • EditCell — switches between a Select and a TextInput based on whether the passed Zod validation is an enum (isZodEnum, from types): enum-validated fields get a dropdown of the enum's values, everything else gets free text. Calls handleChange(defaultValue) once on mount regardless of which branch renders.
  • ErrorBoundary — a route-level error display (reads the error via react-router-dom's useRouteError, so it's meant to be used as a route's errorElement, not a component-level try/catch boundary), reports to Sentry on mount, and shows the error's raw message only when process.env.NODE_ENV === 'development'.

Layout chrome

BaseHeader and BaseFooter are thin wrappers around Mantine's Header/Footer with a fixed height (60, uniformly across breakpoints) and a default sx (space-between for the footer, a bottom border for the header) that callers can still override via their own sx prop. ConfigInput is a Box capped at maxWidth: 300 for wrapping a single config field, and ConfigLabel renders a title/description pair for one, running both through i18next's t() — which means the strings passed to it are expected to already be translation keys, not display text (matching how node-logic's translate step turns block/option titles into keys before they ever reach the UI).

Standalone widgets

Badge is a minimal styled wrapper with no logic. InfoTooltip is a small "i" icon that shows label in a tooltip on hover — used throughout config panels for inline help text. MultiSelect is a creatable Mantine multi-select.

Two components, one name

Don't confuse ui's MultiSelect with the similarly-named, separately implemented MultiSelect in node-inputs — they're unrelated components in different packages that happen to share a name. Check the import path, not just the name, before assuming which one you're looking at.