The /cmp/ package provides Hoist's cross-platform UI components - reusable building blocks that
work across both desktop and mobile applications. These components handle data display, user input,
layout, and interaction patterns while remaining platform-agnostic.
Components in /cmp/ fall into two categories:
- Complete components - Ready-to-use in applications (Grid, Chart, DataView, TabContainer)
- Base classes and models - Extended by platform-specific implementations in
/desktop/and/mobile/(form fields, input models, layout containers)
/cmp/ (cross-platform)
├── Models, base classes, platform-agnostic logic
├── Used directly by apps OR wrapped by platform packages
│
├── /desktop/
│ ├── Extends /cmp/ components with Blueprint UI
│ ├── Adds desktop-specific inputs, panels, toolbars
│ └── Desktop app container and shell
│
└── /mobile/
├── Extends /cmp/ components with Onsen UI
├── Adds mobile-specific inputs, navigator, panels
└── Mobile app container and shell
Example: Grid across platforms
Both GridModel and the Grid component itself are fully cross-platform - applications import
and use them identically on desktop and mobile. The platform-specific packages provide helper
components built with platform-appropriate UI libraries:
/desktop/cmp/grid/- Cell editors, column chooser dialog, filter UI (using Blueprint)/mobile/cmp/grid/- Column chooser sheet (using Onsen UI)
The core grid functionality (sorting, grouping, selection, data binding) works the same everywhere.
Hoist components are created via element factories rather than JSX. Each component exports both a React component (PascalCase) and a factory function (camelCase):
// Component definition exports both forms
export const [Grid, grid] = hoistCmp.withFactory<GridProps>({...});
export const [TabContainer, tabContainer] = hoistCmp.withFactory<TabContainerProps>({...});
// Application usage - factory style (preferred)
panel({
title: 'Data View',
items: [
grid({model: gridModel}),
tabContainer({model: tabModel})
]
})
// JSX also supported
<Panel title="Data View">
<Grid model={gridModel} />
<TabContainer model={tabModel} />
</Panel>Factories accept a config object with item/items for children, or children directly as
arguments when no other props are needed.
For details on creating components with hoistCmp.factory() and hoistCmp.withFactory(),
see the Core Package README.
Most complex components are paired with a HoistModel subclass that manages state and behavior:
| Component | Model | Purpose |
|---|---|---|
| Grid | GridModel | Data display, sorting, grouping, selection |
| Chart | ChartModel | Highcharts configuration and series data |
| DataView | DataViewModel | Custom item rendering with Grid features |
| TabContainer | TabContainerModel | Tab state and navigation |
| Form | FormModel | Form fields, validation, data binding |
| Card | CardModel | Bordered container, collapsibility, persistence |
| FormFieldSet | FormFieldSetModel | Grouped fields with aggregate validation |
Models are created in application code and passed to components via the model prop or
discovered automatically via context lookup (see Core README):
class MyPanelModel extends HoistModel {
@managed gridModel = new GridModel({
store: {fields: ['name', 'value']},
columns: [{field: 'name'}, {field: 'value'}]
});
}
// In component
grid({model: this.model.gridModel})Component models use MobX decorators to mark state as observable. Components created with
hoistCmp are automatically wrapped as MobX observers - when observable state accessed during
render changes, the component efficiently re-renders.
// Model defines observable state
class MyComponentModel extends HoistModel {
@observable selectedId: string = null;
@observable.ref data: MyData[] = [];
@action
setSelectedId(id: string) {
this.selectedId = id;
}
}
// Component reads observable state during render
const myComponent = hoistCmp.factory({
render({model}) {
const {selectedId, data} = model;
return div(
`Selected: ${selectedId}`,
`Count: ${data.length}`
);
}
});This reactive binding is automatic and efficient - MobX tracks which observables each component
accesses and triggers re-renders only when those specific values change. This eliminates manual
subscription management and optimizes rendering without requiring React.memo or similar
techniques.
| Sub-package | Description |
|---|---|
/grid/ |
Primary data grid built on ag-Grid (sorting, grouping, filtering, editing, export). See README |
/dataview/ |
Custom item rendering using Grid infrastructure |
/chart/ |
Highcharts integration for data visualization |
/treemap/ |
Hierarchical treemap and split treemap visualizations |
/zoneGrid/ |
Multi-zone grid layout for complex record displays |
| Sub-package | Description |
|---|---|
/layout/ |
Foundational flexbox containers (Box, VBox, HBox, Frame, Viewport). See README |
/card/ |
Bordered container for grouping content with optional header, intent styling, and collapsibility |
| Sub-package | Description |
|---|---|
/form/ |
Form container, field models, and FormFieldSet for grouped validation. See README |
/input/ |
Base input model and props for platform inputs. See README |
| Sub-package | Description |
|---|---|
/tab/ |
Tabbed interface container and models. See README |
| Sub-package | Description |
|---|---|
/filter/ |
FilterChooserModel for building filter UIs |
/grouping/ |
GroupingChooserModel for dimension selection |
/store/ |
Store-related UI components (count label, filter field) |
| Sub-package | Description |
|---|---|
/viewmanager/ |
Save and restore named views with GridModel state. See README |
| Sub-package | Description |
|---|---|
/badge/ |
Badge component for counts and status indicators |
/loadingindicator/ |
Loading state display in a compact and non-blocking form |
/mask/ |
Overlay mask for blocking interactions |
/spinner/ |
Loading spinner animations |
| Sub-package | Description |
|---|---|
/ag-grid/ |
AgGridModel wrapper for ag-Grid integration |
/clock/ |
Real-time clock display |
/error/ |
Error boundary and error message display |
/markdown/ |
Markdown rendering component |
/pinpad/ |
PIN entry interface |
/relativetimestamp/ |
Human-readable relative time display |
/websocket/ |
WebSocket indicator component |
The primary component for tabular data. Wraps ag-Grid with a consistent API for sorting, grouping, filtering, selection, inline editing, and export. Most data-intensive applications use Grid extensively.
import {grid, GridModel, date, number} from '@xh/hoist/cmp/grid';
// Model - typically created as a @managed property of a parent HoistModel
const gridModel = new GridModel({
store: {fields: ['name', 'value', 'date']},
columns: [
{field: 'name', flex: 1},
{field: 'value', ...number},
{field: 'date', ...date}
],
sortBy: 'name'
});
// Component - renders the grid, model found via context or passed explicitly
grid() // GridModel found via context lookup
grid({model: gridModel}) // Or pass model explicitlySee the Grid README for complete documentation.
Highcharts integration for data visualization. ChartModel manages chart configuration and series data, while the Chart component handles rendering.
import {chart, ChartModel} from '@xh/hoist/cmp/chart';
const chartModel = new ChartModel({
highchartsConfig: {
chart: {type: 'line'},
xAxis: {type: 'datetime'},
yAxis: {title: {text: 'Value'}}
}
});
chartModel.setSeries([{name: 'Series 1', data: [...]}]);Custom item rendering using Grid infrastructure. Useful when each record needs a custom visual representation rather than tabular columns.
import {dataView, DataViewModel} from '@xh/hoist/cmp/dataview';
const dataViewModel = new DataViewModel({
store: {fields: ['title', 'description', 'thumbnail']},
itemHeight: 80,
renderer: (value, {record}) => div({
className: 'my-card',
items: [
img({src: record.data.thumbnail}),
span(record.data.title)
]
})
});Tabbed interface for organizing content into switchable views. TabContainerModel manages tab state while platform-specific components handle rendering.
import {TabContainerModel} from '@xh/hoist/cmp/tab';
const tabModel = new TabContainerModel({
tabs: [
{id: 'overview', title: 'Overview', content: () => overviewPanel()},
{id: 'details', title: 'Details', content: () => detailsPanel()},
{id: 'history', title: 'History', content: () => historyPanel()}
]
});See the Tab README for complete documentation.
A bordered container for grouping related content with an optional inline header, intent-based
border/header coloring, and collapsible content. Built on an HTML <fieldset> and <legend> for
base styling. Children are arranged vertically in a flexbox container by default.
import {card, CardModel} from '@xh/hoist/cmp/card';
// Simple static card - no model needed (created automatically)
card({
title: 'User Details',
icon: Icon.user(),
items: [nameField(), emailField()]
})
// Collapsible card with persistence
const cardModel = new CardModel({
collapsible: true,
defaultCollapsed: false,
renderMode: 'unmountOnHide',
persistWith: {localStorageKey: 'detailsCard'}
});
card({
model: cardModel,
title: 'Details',
items: [/* ... */]
})CardModel supports collapsible state with configurable renderMode ('always', 'lazy',
'unmountOnHide') and optional persistence of collapsed state. Cards can display an intent
('primary', 'success', 'warning', 'danger') for colored borders and headers.
FormFieldSet (/cmp/form/formfieldset/) extends Card to group related FormField components,
displaying their aggregate validation state as intent-colored borders and tooltips. See the
Form README for details.
Foundational flexbox containers for building layouts:
- Box - Base flex container
- VBox - Vertical (column) flex layout
- HBox - Horizontal (row) flex layout
- Frame - Box that flexes to fill available space (
flex: 'auto') with HFrame/VFrame variants - Viewport - Full-screen container
import {vframe, hbox} from '@xh/hoist/cmp/layout';
vframe({
items: [
hbox({items: [sidebar(), mainContent()]}),
footer()
]
})See the Layout README for complete documentation.