@middag-io/react
v0.37.1
Published
MIDDAG shared React UI library for Moodle, WordPress and other Inertia hosts
Readme
@middag-io/react
Shared React UI library for the MIDDAG ecosystem. Consumed by Moodle (local_middag) and WordPress host plugins via Inertia.js.
Stack
- React 19, TypeScript, Tailwind CSS v4
- Inertia.js (peer dependency — all hosts use Inertia)
- ReUI components (Radix-based)
- TanStack Table, @xyflow/react, @dnd-kit
Install
Quick start (recommended)
npx create-middag-uiThe wizard detects your host platform (Moodle/WordPress), configures authentication, and scaffolds a ui/ directory with a working build.
Manual install
npm public (no auth needed):
npm install @middag-io/react react react-dom @inertiajs/react @inertiajs/coreGitHub Packages (includes TypeScript source for IDE navigation):
# Add to global ~/.npmrc
echo "@middag-io:registry=https://npm.pkg.github.com" >> ~/.npmrc
echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc
npm install @middag-io/react react react-dom @inertiajs/react @inertiajs/coreCreate a token at github.com/settings/tokens with read:packages scope. See Authentication for details.
Distribution
@middag-io/react is the complete rendering engine — every block, shell, layout, contract and registry you need to build plugins. Two ways to install it:
- npm (public, no auth): compiled ESM bundle + type declarations + declaration maps.
- GitHub Packages (auth): adds the TypeScript
src/for IDE "go to definition". Requires aread:packagestoken (see above).
MIDDAG also offers a commercial Pro tier (
@middag-io/react-pro) — additional blocks, an application shell and extra themes, used for internal products and consulting. Not needed to build with this library.
Usage
import { ContractPage, registerDefaults } from '@middag-io/react';
import type { PageContract } from '@middag-io/react';
// Register core shells, layouts, and the standard Community blocks
registerDefaults();
// Render a contract-driven page
<ContractPage contract={myContract} />Selective registration (IIFE consumers)
WordPress and other IIFE consumers should import only the blocks they need to avoid bundling heavy dependencies (@xyflow/react, @dnd-kit, zod, react-hook-form) via inlineDynamicImports:
import {
registerShell, registerLayout, registerBlock,
BasicShell, StackLayout, SidebarLayout, DashboardLayout,
DenseTableBlock, MetricCardBlock, EmptyStateBlock,
} from '@middag-io/react';
registerShell('product', BasicShell);
registerLayout('stack', StackLayout);
registerLayout('sidebar', SidebarLayout);
registerLayout('dashboard', DashboardLayout);
registerBlock('dense_table', DenseTableBlock);
registerBlock('metric_card', MetricCardBlock);
registerBlock('empty_state', EmptyStateBlock);Lazy block loading
Blocks can defer data loading until they mount (useful for tabbed pages):
// PHP sends block with empty data + lazyProp metadata:
{
type: 'dense_table',
key: 'invoices',
data: {},
meta: { lazyProp: 'invoices' }
}
// PHP also sends a top-level Inertia prop (initially null):
// 'invoices' => null
// When the block mounts, it auto-fetches via router.reload({ only: ['invoices'] })
// Radix Tabs unmounts inactive tabs, so lazy blocks only fetch when their tab activatesReUI components
Consumers can import ReUI primitives for custom components:
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@middag-io/react/reui/tabs';
import { Button } from '@middag-io/react/reui/button';Custom blocks
import { registerBlock, type BlockProps } from '@middag-io/react';
function ChartBlock({ block }: BlockProps<{ labels: string[]; values: number[] }>) {
return <div>{/* your chart */}</div>;
}
registerBlock('chart', ChartBlock);Contract validation
import { validatePageContract } from '@middag-io/react';
const errors = validatePageContract(contractFromBackend);
if (errors) {
console.error('Invalid contract:', errors);
}Zod schemas are exported for consumers who want to extend validation or generate JSON Schema for PHP consumers.
i18n with host-specific resolver
import { I18nProvider } from '@middag-io/react';
// Moodle: inject Moodle string resolver
<I18nProvider asyncResolver={moodleGetStrings}>
<App />
</I18nProvider>
// WordPress: inject WP i18n resolver
<I18nProvider asyncResolver={wpGetStrings}>
<App />
</I18nProvider>Architecture
src/
app/ # ContractPage, registries (shell/layout/block), providers, LazyBlock
base/ # Shells, layouts, blocks, hooks, theme, form, partials, registries (field/icon/cell)
components/
reui/ # ReUI primitives (Radix-based, source of truth)
examples/ # ReUI component examples (synced from registry)
contracts/ # TypeScript types (PageContract, BlockData, etc.)
lib/ # Generic utilities and hooks
assets/ # Fonts, lottie animations
index.ts # Barrel export
demo/ # Demo harness (standalone SPA — `npm run dev`)
docs/ # Guides, reference and the public JSON schemas
scripts/ # Tooling (contracts codegen, docs payload, sync, doctor)
.githooks/ # Git hooks (pre-commit, commit-msg, pre-push)What belongs here vs host plugins
| Here (@middag-io/react) | Host plugin (Moodle/WP) | |-----------------------------|-------------------------------------| | Shells, layouts, blocks | Extensions with host-specific pages | | ContractPage renderer | Inertia server-side adapter | | Theme system | License validation (server-side) | | i18n provider (generic) | i18n resolver (host-specific) | | Type contracts | API endpoints, DB queries | | ReUI examples (reference) | Custom components |
Development
# Check environment
npm run doctor
# Demo harness -- standalone SPA, no server needed
npm run dev # http://localhost:5173
# Typecheck
npm run typecheck
# Lint
npm run lint:fix
# Tests
npm test
# Build ESM lib
npm run buildThe demo harness consumes the engine from src/ via Vite aliases (instant HMR), mocks @inertiajs/* with self-contained adapters in demo/adapters/, and renders PageContracts end to end.
ReUI component examples
Examples from the ReUI registry are synced to src/components/examples/. They serve as reference for component usage and are excluded from the published NPM package.
# Sync all examples from ReUI registry (auto-discovers new components)
npm run sync:examples
# Preview what would be synced
npm run sync:examples -- --dry-run
# Check if new examples are available (used by CI)
npm run sync:examples -- --checkA GitHub Action runs weekly (Mondays 8:00 UTC) to check for new examples and opens a PR automatically if updates are available.
Git Hooks
Configured via .githooks/ (activated by npm run prepare):
| Hook | Trigger | Protection |
|--------------|--------------|-------------------------------------------|
| pre-commit | git commit | Typecheck + lint on staged files |
| commit-msg | git commit | Enforces Conventional Commits format |
| pre-push | git push | Typecheck + contract drift + lib build |
Versioning
release-please owns versioning: it scans Conventional Commits on main, opens a release PR (version bump + CHANGELOG.md) and, on merge, cuts the tag + GitHub release and publishes. Commit types decide the bump (0.x rule: feat/breaking = minor, fix = patch).
CLI
The package includes a CLI for bootstrapping and maintaining the UI layer in consumer projects:
| Command | Description |
|-----------------------------------------|------------------------------------------------------------|
| npx create-middag-ui | Bootstrap ui/ |
| npx @middag-io/react doctor | Validate consumer project setup (deps, configs, peer deps) |
| npx @middag-io/react dev | Start mock dev server from consumer project |
| npx @middag-io/react add-block <type> | Scaffold a new block type with component + mock factory |
| npx @middag-io/react upgrade | Check for updates and run codemods |
Scripts
| Command | Description |
|---------------------------|------------------------------------------------|
| npm run doctor | Validate development environment |
| npm run dev | Demo harness (demo/) |
| npm run build | Build ESM lib to dist-lib/ |
| npm run typecheck | TypeScript type check |
| npm run lint | ESLint check |
| npm run lint:fix | ESLint auto-fix |
| npm run format | Prettier format |
| npm run format:check | Prettier check |
| npm test | Vitest run (all tests) |
| npm run gen:contracts | Regenerate contract types from JSON schemas |
| npm run sync:examples | Sync ReUI examples from registry |
| npm run docs:build | Build + verify the public docs payload |
Peer Dependencies
react^19.0.0react-dom^19.0.0@inertiajs/react^3.0.0@inertiajs/core^3.0.0
CI/CD
| Workflow | Trigger | What it does |
|------------------------------|------------------------------------|-----------------------------------------------------------------|
| ci.yml | Pull requests to main | Static checks + contract drift + tests/coverage + build |
| publish.yml | Push to main | release-please → publish to GitHub Packages + npm public → docs |
| notify-schemas-updated.yml | Push to main (docs/public/schemas) | Notifies downstream consumers of schema changes |
| sync-examples.yml | Monday 8:00 UTC / manual | Sync ReUI examples, opens PR if updates |
License
Apache-2.0 OSS. The rendering engine, the standard Community blocks, shells, layouts, contracts and registries. Free to use, fork and ship. See LICENSE.
The commercial Pro tier (
@middag-io/react-pro— additional blocks, an application shell, extra themes) is a separate, proprietary package distributed via GitHub Packages. It is not part of this repository and is not needed to build with this library.
