src/components/ui/. Pages compose these primitives instead of hand-rolling inputs, modals, or tables, which keeps theming (CSS variables) and behavior consistent across brands.
UI Primitives
Each primitive is a small typed component with a CSS module of the same name.| Component | File | Role |
|---|---|---|
TextInput | components/ui/TextInput.tsx | Text/number/url/email/password/date/time input with suffix, error, onBlur |
TextArea | components/ui/TextArea.tsx | Multiline input with variant, monospace, showCount, maxHeight |
Select | components/ui/Select.tsx | Dropdown taking options: {label, value}[] |
Toggle | components/ui/Toggle.tsx | Boolean switch with a label |
Slider | components/ui/Slider.tsx | Range input with min/max/step/showValue |
TagInput | components/ui/TagInput.tsx | Multi-value tag entry |
FormField | components/ui/FormField.tsx | Label + helpText + error wrapper around an input |
Section | components/ui/Section.tsx | Titled, optionally collapsible panel with a badge slot |
SectionGroup | components/ui/SectionGroup.tsx | Grouped sections with title/description/icon/variant |
Modal | components/ui/Modal.tsx | Animated dialog with backdrop, Escape-to-close, body scroll lock |
Badge | components/ui/Badge.tsx | Status pill with success/primary/neutral/danger variants |
DataTable | components/ui/DataTable.tsx | Generic sortable, paginated table |
DateRangeFilter | components/ui/DateRangeFilter.tsx | Preset + custom date range picker |
EmptyState | components/ui/EmptyState.tsx | Empty placeholder with optional action |
Skeleton | components/ui/Skeleton.tsx | Loading placeholder (row/card variants) |
TextPreview | components/ui/TextPreview.tsx | Truncated text with expand |
SoftAurora | components/ui/SoftAurora.tsx | Decorative brand-colored background |
var(--primary), var(--surface), var(--line), etc.), which the white-label build injects per brand.
DataTable
DataTable<T> is the generic table used by list pages such as Call Logs.
Props:
| Prop | Type | Notes |
|---|---|---|
columns | Column<T>[] | Each has key, header, optional render, sortable, width |
data | T[] | Row data |
onRowClick | (item: T) => void | Optional; makes rows clickable |
loading | boolean | Renders skeleton rows |
pageSize | number | Defaults to 10 |
emptyMessage | string | Shown when data is empty |
- Sorting is client-side. Clicking a sortable header toggles
asc/desc; numeric values compare numerically, everything else vialocaleCompare. Nulls sort last. - Pagination is client-side and only appears when
data.length > pageSize. renderoverrides cell content; without it the raw value is stringified.
DataTable paginates the array it is given. Pages with server-side pagination (for example Call Logs, which fetches 50 rows per server page) keep the table’s pageSize aligned with the fetch size and drive paging through their own state so the table is not double-paginating a partial dataset.DateRangeFilter
DateRangeFilter renders preset buttons — Today, Yesterday, 7 Days, Week to Date, Month to Date, Custom — plus two <input type="date"> fields when Custom is selected. It computes a { from, to } range in the supplied timezone via computeDateRange (from utils/timezone) and calls onChange whenever the range changes.
The Number-Input Blur Pattern
Number fields never parse on every keystroke. Instead they keep an empty intermediate string in state while typing and commit a default on blur. This avoidsparseInt snapping the field to 0 (or NaN) mid-edit.
The canonical shape (from StepIdentity re-engagement gaps):
- A value of
0displays as an empty string so the field can be cleared. onChangestores0for empty input and never throws on partial entry.onBlurrestores a sensible default when the field is left blank.
ToolBuilder.tsx), which defaults to 8 seconds on blur. Reuse it for any numeric config field.
Related Docs
App Shell & Routing
Where these primitives get rendered.
White-Label Build
How CSS variables are themed per brand.
Call Logs
DataTable and DateRangeFilter in use.