Table of Contents

@agilecontent/ui — React Primitives

@agilecontent/ui is the shared component library that everything in the React shell (apps/cms) and every federated remote build on top of. It bundles a curated set of UI primitives — form fields, layout shells, tables, modals, navigation — plus the theme tokens and icons that keep the rebrandable surface consistent across customers.

Concretely, it's an Ant Design wrapper. Most exports are either:

  1. A re-export of an Ant Design component, unchanged, so the whole shell agrees on the same antd version (Alert, Tooltip, Typography, Card, …).
  2. A thin wrapper on top of an Ant Design control, adding shell-wide behavior — color theming, read-only mode, async data loading, our form-item conventions (Select, Input, DatePicker, …).
  3. A pure agilecontent primitive with no antd counterpart (PageLayout, Sidebar, TopBar, Account, Tour, EPG).

The package lives at packages/ui inside the MibFrontEnd monorepo and is published as @agilecontent/ui. Federated remotes pin it as a shared dependency in their Module Federation config so the version the shell loads is the version the widget uses — no double-bundling antd into a customer remote.

Related docs.
Component Authoring — how a federated React widget plugs into the shell. The widgets use @agilecontent/ui primitives instead of importing antd directly.
Theming — the CSS custom variables this library reads.
Built-in Components — the stock React widgets, all built from these primitives.
Local Development — how to run the shell + a remote together for live development.

Why a wrapper, not antd directly

Three reasons we don't let federated widgets import antd directly:

  1. One antd version per shell. With Module Federation, @agilecontent/ui is shared (deduped). If a customer remote imports antd directly with its own version, you get two antd runtimes loaded on the same page — broken modals, duplicate ConfigProviders, doubled style sheets.
  2. A single rebranding surface. Every primitive reads the same CSS custom variables defined at :root. A customer can change the brand color in one place and every widget — built-in or custom — picks it up. See Theming.
  3. Project conventions baked in. Several wrappers add behavior the shell expects: Select honours readOnly, Input supports our AdmField permission flags, Form integrates with our save lifecycle. Going around the wrapper means re-implementing those conventions per widget.

Installation in a federated remote

If you started from the hands-on tutorial's federated remote or mini-agiletv it's already wired. For a fresh remote:

// remote/package.json
"dependencies": {
  "@agilecontent/ui": "^2.21.0",
  "@agilecontent/mib-api-connector": "^x.y.z",
  "@agilecontent/mib-modules": "^x.y.z",
  "react": ">=18.0.0",
  "react-dom": ">=18.0.0"
}
// remote/vite.config.ts
federation({
  name: 'customComponents',
  filename: 'remoteEntry.js',
  exposes: { './all': './src/main.tsx' },
  shared: [
    'react', 'react-dom',
    '@agilecontent/ui',           // ← share, do not duplicate
    '@agilecontent/mib-api-connector',
    '@agilecontent/mib-modules',
    'react-i18next', 'styled-components', 'swr',
  ],
})

The shell already lists the same packages as shared, so they're loaded once across the shell + every remote.

What's exported

The full surface lives in packages/ui/src/index.tsx. Below is the practical roll-up grouped by purpose.

Layout shells

Export Purpose
PageLayout The full page chrome — sidebar + topbar + content slot. Most full-screen federated widgets render inside this.
Sidebar Left-rail menu. Reads MenuItemConfig[].
TopBar The branded header with logo, notifications, account.
Account The user dropdown shown in the top-right.
Breadcrumb Path crumbs at the top of a sub-page.
ComponentLayout The card frame each page-component renders inside (title, toolbar slot, body, footer). Use this so your custom widget visually matches the built-in ones.
Card, Drawer, Modal, PopUpBox, Popover Standard surfaces. Modal exposes confirm, error, info, warning, plus the useModal hook.
Tabs, Collapse, Steps Containers for hierarchical or multi-step content.
Col, Row, Grid Flex-grid primitives.
Divider, Space Spacing primitives.
Skeleton, SkeletonInput, FormSkeleton, TableSkeleton, Spin Loading states.
Result Standard "success / error / 404" page result.
Empty "No data" placeholder.

Form controls

The form-item wrappers are the most-used part of the library — they're how your widget renders a single editable field. They all read shell context (theme, locale, permissions) and forward unknown props through to the underlying antd component.

Export Underlying antd Notes
Form, FormProvider, Field, FieldGroup, FormItemConfigContext, getOperatorFunction antd/Form Our form is wrapped to integrate with the shell's ContentEdition save lifecycle. Field is the equivalent of Form.Item plus our AdmField conventions.
Input antd/Input Adds our standard read-only / disabled handling.
TextArea antd/Input.TextArea Same.
InputNumber antd/InputNumber Same.
Select antd/Select Adds colors prop for color-tagged options, readOnly mode.
AsyncSelect antd/Select Async option-loading with debounced search.
AsyncTree, TreeSelect, AsyncTreeSelect antd/TreeSelect Hierarchical pickers; the Async variants lazy-load branches.
AutoComplete, AsyncAutoComplete antd/AutoComplete Free-text with suggestions.
DatePicker, TimePicker, RangePicker antd/DatePicker.* All wrapped to use shell locale + timezone (see Localization).
Switch antd/Switch Standard.
Radio, RadioGroup antd/Radio Plus RadioChangeEvent re-export.
Checkbox antd/Checkbox (re-export) Standard.
CronScheduler react-js-cron based Cron expression editor used by Job-Workflow style pages.
JsonEditor Monaco-based JSON editor with validation.
EditorField Monaco-based Generic code editor for blob fields.
Upload, UploadFile, RcFile, UploadChangeParam antd/Upload File upload widget.
SelectableSearch custom The simple-search "field selector + input" used by the List component header.

Data display

Export Purpose
Table, TableTypes, DefaultImage The tabular widget all list/related-list components render through. Heavy customisation surface — TableTypes.RenderType covers Source/Boolean/Date/Image/Enum/Link cell renderers.
NestedTable Tables-inside-tables (expanded row contents).
SortableList, SortableListTypes Drag-to-reorder list, used by the Ordered Related List.
List, ListItem, ListItemMeta antd/List re-export — for plain bullet lists, not the data List Component.
Tree antd/Tree re-export — full-tree display.
Tag, Badge Pill / chip primitives.
Avatar, AvatarGroup User/source avatars.
Image Lightbox-capable image.
Progress Bar / circle progress.
FieldBox A read-only "label + value" pair, used by Form when a field is read-only.

Pagination

Export Purpose
AdvancedPagination Page number + page-size + total count. Used at the bottom of List / Related List.
BasicPagination Just prev/next + page number.
Export Purpose
Tour, TourProps Step-by-step on-page guides (the new-feature tutorials).
notification Top-right toast notifications.
successMessage, errorMessage, infoMessage, loadingMessage, message Imperative toast API used across the shell.
Alert Inline banner.
Tooltip Standard.
Popconfirm Inline confirm-on-click.

Buttons

@agilecontent/ui ships four brand-styled button variants — all from the same ./components/buttons export:

Export When to use
PrimaryButton The page's main action (e.g. Save, Submit). Solid background, brand color.
SecondaryButton Important but non-primary actions (Cancel, Reset). Bordered.
LinkButton Inline navigational action that looks like a link.
TextButton Minimal text-only action (e.g. for toolbar items).

Plus Button (the plain antd one) is re-exported for fully custom cases.

Icons

The whole @ant-design/icons set is re-exported via the /icons subpath:

import { PlusOutlined, EditOutlined, SaveOutlined } from '@agilecontent/ui/icons';

This way every widget uses the same icon set without re-bundling it. There's also an Icon wrapper at the root that adds project styling conventions.

Theming

Export Purpose
UIProvider The root provider that supplies theme + locale + AntD ConfigProvider. The shell already wraps the whole app in it; federated widgets don't need to wrap themselves.
defaultTheme, Theme, DynamicTheme The default theme tokens (colors, typography, spacings). For a per-customer rebrand override the CSS variables instead — see Theming.
GlobalStyle Global CSS reset + base styles. The shell injects this once.
ConfigProvider Antd's ConfigProvider re-export.

Charts and special-purpose subpaths

Three subpath exports keep the main bundle small. Import only what you need:

// Charts
import { LineChart, BarChart, ... } from '@agilecontent/ui/chart';

// EPG primitives (used by the EPG built-in component)
import { Scheduler, Track, ... } from '@agilecontent/ui/epg';

// Icons (already mentioned)
import { PlusOutlined, ... } from '@agilecontent/ui/icons';

The chart subpath is a wrapper around the chart library; the EPG subpath is the timetable-style scheduler used by the EPG component (see packages/ui/src/epg.tsx).

Utilities

Export Purpose
dateFormatter, dateInstance, DateFormatter Centralised date formatting honouring shell locale + timezone. Use this so dates render consistently across widgets.
tablet, desktop, xDesktop, xxDesktop, mobileOnly, tabletEnds, desktopEnds, … Media-query breakpoint helpers (tabletStartWidth: 768px, xDesktopStartWidth: 1200px, xxDesktopStartWidth: 1600px). For use in styled-components.
filterTree, getAllKeysFromKey, getAllKeysFromValue, getAllItems, TreeStructure Tree-navigation helpers — for TreeSelect data preparation.
loadBaseStyles() Lazy-load antd reset + shaka-player CSS + cron-scheduler CSS. The shell calls this once on boot.

Player primitives

Player, PlayerRef, VideoEvents, DrmInfo, DrmSetup wrap shaka-player for the video-player surface used in catalog preview pages. DRM setup follows the DrmSetup shape — see source for the contract.

Quick reference — common patterns

Render a form field

import { Field, Input, Select } from '@agilecontent/ui';

<Field name="title" label="Title" rules={[{ required: true }]}>
  <Input placeholder="Movie title" />
</Field>

<Field name="status" label="Status">
  <Select
    options={[
      { label: 'Draft',     value: 'draft' },
      { label: 'Published', value: 'published' },
    ]}
  />
</Field>

Render a data table

import { Table, TableTypes } from '@agilecontent/ui';

<Table
  dataSource={data.items}
  columns={[
    { title: 'ID',     dataIndex: 'id', sorter: true },
    { title: 'Source', dataIndex: 'source',
      render: TableTypes.RenderType.Source },
    { title: 'Active', dataIndex: 'isActive',
      render: TableTypes.RenderType.Boolean },
  ]}
  pagination={false}
/>

Show a confirmation

import { Modal, successMessage } from '@agilecontent/ui';

const onDelete = async () => {
  Modal.confirm({
    title: 'Delete this record?',
    okText: 'Delete', okType: 'danger',
    onOk: async () => {
      await api.delete(id);
      successMessage('Deleted');
    },
  });
};

Style with theme variables

import styled from 'styled-components';

const Card = styled.div`
  background: var(--color-primary-background);
  color: var(--color-title);
  border: 1px solid var(--color-tertiary-background);

  &.is-active {
    border-color: var(--color-highlight);
  }
`;

Versioning and upgrades

@agilecontent/ui follows semver. The shell pins a specific minor version; federated remotes should match (or use a compatible range) to avoid an antd duplicate-version conflict at federation merge time. Check the shell's apps/cms/package.json for the canonical pinned version before upgrading a remote.

When the shell upgrades antd (e.g. 5.x5.y), the @agilecontent/ui major version usually bumps. Federated remotes need to bump in lockstep — there's no way to run two antd majors on the same page.

When to bypass @agilecontent/ui

Almost never. If you find yourself reaching for import { ... } from 'antd' directly inside a federated widget, stop and check:

  • Is the component you want already exported from @agilecontent/ui? (Most are — see the full list in src/index.tsx.)
  • If not, does it warrant adding a wrapper to @agilecontent/ui? File an issue against MibFrontEnd.
  • If your component is one-off and won't be used outside your remote, it's OK to use raw antd for that single case — but declare antd as a peer dependency in your remote's package.json and add it to the federation shared array so the deduplication still works.

The pattern to avoid is silently bundling a second antd into your remote. That's what breaks Modal.confirm, notification, and the shell's ConfigProvider.

See also