# Shared UI Components

Reuse erxes-ui and ui-modules


Reuse the `erxes-ui` primitives and `ui-modules` business modules instead of building parallel components.

Both libraries are Module Federation singletons; the host shares one copy with every remote. For how remotes load, see [Frontend Plugins](https://erxes.io/docs/plugin-frontend).

## What each library exports

`erxes-ui` ([index.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/frontend/libs/erxes-ui/src/index.ts)) barrels seven areas:

| Barrel | Contents |
| --- | --- |
| `components` | ~60 primitives: `Button`, `Form`, `Sheet`, `Dialog`, `Select`, `Combobox`, `Table`, `Tabs`, `Sidebar`, `CommandBar`, `Command`, `Breadcrumb`, `Tooltip`, `Spinner`, `Skeleton`, `Empty`, `Toast`/`Toaster`, `Dropzone`, date/time pickers, and more |
| `modules` | Composed feature blocks: `RecordTable`, `board`, `filter`, `record-field`, `inputs`, `select-tree`, `select-operation`, `navigation-menu`, `side-menu`, `focus-sheet`, `attachments`, `preview`, `report`, `display`, `layout`, `icons`, `blocks`, `hotkey` |
| `hooks`, `state`, `states` | Shared hooks and Jotai atoms (e.g. `useConfirm`, toast state) |
| `types` | `IUIConfig`, `TPropertyInputProps`, search-provider types |
| `constants`, `lib`, `utils` | Env constants (`NODE_ENV`, `REACT_APP_API_URL`), helpers |

`ui-modules` ([index.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/frontend/libs/ui-modules/src/index.ts)) exports business-domain modules plus `hooks` and `states`:

- `header`: `PageHeader` with `Start`/`End`/`FavoriteToggleButton` composition
- `contacts`, `products`, `sales`, `segments`, `tags`, `properties`, `permissions`, `structure`, `team-members`, `brands`
- `automations`, `approval`, `documents`, `import-export`, `internal-notes`, `activity-logs`, `notifications`, `payments`, `templates`, `widget`, `favorites`, `shared`, `tags-new`
- Plugin-facing atoms such as `pluginsConfigState` (the loaded `IUIConfig` map)

```tsx
import { Button, Form, RecordTable, Sheet } from 'erxes-ui';
import { PageHeader } from 'ui-modules';
```

## Composed components to reach for first

Prefer composed components over hand-rolled layouts:

- `RecordTable` ([record-table](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/frontend/libs/erxes-ui/src/modules/record-table/index.ts)): `Object.assign(RecordTableRoot, { Provider, Header, Head, InlineHead, RowList, Body, Cell, Row, RowSkeleton, MoreButton, checkboxColumn, Scroll, CursorProvider, CursorBackwardSkeleton, CursorForwardSkeleton, ColumnSelector })` plus `useRecordTable` and `useRecordTableCursor` for cursor-paginated lists.
- `Form.Field`: React Hook Form field wrapper (with `Form.useField`) for label/control/message per field.
- `Sheet.Content`: the side panel used for detail/edit flows.
- `CommandBar`: the bulk-action bar for selected table rows (`Object.assign(Root, { ... })` composition).
- `filter` module: query-state-driven filter bars and date filters.
- `NavigationMenuLinkItem` / `SettingsNavigationMenuLinkItem`: the generated navigation uses these for plugin sidebar entries.
- `PageHeader` (ui-modules): standard page chrome; the generated `IndexPage` already composes it with `Breadcrumb` and `Separator.Inline`.
- `select-operation`, `select-tree`: member/company/segment-style selectors.

Icons come from `@tabler/icons-react` (e.g. `IconSandbox`, `IconBriefcase`); `erxes-ui` also includes `IconsProvider`, `IconComponent`, and `IconPicker` for user-chosen icons.

## A full composition

`sales_ui`'s `PipelineRecordTable` shows the intended stack in one file: table state from URL query state, confirmation on destructive actions, translations via the plugin namespace:

```tsx
import { RecordTable, RecordTableTree } from 'erxes-ui';

<RecordTable.Provider
  columns={pipelinesColumns(t)}
  data={pipelines || []}
  stickyColumns={['more', 'checkbox', 'name']}
>
  <PipelineCommandBar />
  <RecordTableTree id="pipelines-list" ordered>
    <RecordTable.Scroll>
      <RecordTable>
        <RecordTable.Header />
        <RecordTable.Body>
          <RecordTable.RowList Row={RecordTableTree.Row} />
          {loading && <RecordTable.RowSkeleton rows={30} />}
          {!loading && pageInfo?.hasNextPage && (
            <RecordTable.RowSkeleton rows={1} handleInView={handleFetchMore} />
          )}
        </RecordTable.Body>
      </RecordTable>
    </RecordTable.Scroll>
  </RecordTableTree>
</RecordTable.Provider>
```

Cursor-style infinite scroll is a skeleton row whose `handleInView` calls the page's fetch-more handler; the same file uses `useConfirm` for destructive actions and `useTranslation('sales')` for the plugin's i18n namespace.

## Find the pattern before you build

- `rg "RecordTable.Provider" frontend/plugins --files-with-matches`: find every plugin already using a component, then read the closest one.
- `frontend/libs/erxes-ui/src/components/__stories__` and `src/modules/__stories__`: per-component Storybook stories (`button.stories.tsx`, `commandbar.stories.tsx`, `form.stories.tsx`) show intended usage.
- `rg "<ComponentName>" frontend/libs/ui-modules/src`: find the shared wrapper before composing your own (a `SelectMember`-style module may already exist under `contacts`, `team-members`, or `segments`).

## Rules

- **No direct `@radix-ui/*` imports and no hand-rolled primitives**: `erxes-ui` components are already the Radix layer; the host style system assumes them.
- **Search before building**: find a similar implementation before writing new code. Before a new list, form, or detail view, open a plugin that already has one (`sales_ui` deals, `operation_ui` tasks, `frontline_ui` inbox) and reuse its pattern without importing its source.
- Import only through the package names (`erxes-ui`, `ui-modules`); never deep-import `frontend/libs/*/src/...` paths, and never import another plugin's files.
- Keep plugin-specific components, hooks, translations, and utilities inside `frontend/plugins/<name>_ui/src/` (aliases `~/*` → `src`, `@/*` → `src/modules`).
- Match nearby spacing, loading, empty, and error patterns; avoid inline styles unless the neighboring code uses them for the same pattern.

