@middag-io/react
v0.18.0
Published
MIDDAG shared React UI library for Moodle, WordPress and other Inertia hosts
Downloads
4,693
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)
# Community (no auth needed)
npx create-middag-ui
# PRO (source maps, mock SPA, full exports)
npx @middag-io/create-middag-uiThe wizard detects your host platform (Moodle/WordPress), configures authentication, and scaffolds a ui/ directory with a working mock 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.
GitHub Packages access requires: membership in the middag-io organization on GitHub. The token alone is not sufficient — you must have read access to the
middag-io/middag-reactrepository.
Community vs PRO
| Content | Community | PRO |
|------------------------------------|-----------|-----|
| ESM bundle (dist-lib/) | Yes | Yes |
| TypeScript declarations (.d.ts) | Yes | Yes |
| Declaration maps (.d.ts.map) | Yes | Yes |
| TypeScript source (src/) | No | Yes |
| Mock SPA extensible (mock/) | No | Yes |
| IDE "go to definition" into source | No | Yes |
| Authentication required | No | Yes |
Both editions deliver the same runtime code. Community is sufficient for building plugins. PRO additionally includes TypeScript source for IDE navigation and the extensible mock SPA.
Usage
import { ContractPage, registerDefaults } from '@middag-io/react';
import type { PageContract } from '@middag-io/react';
// Register core shells, layouts, and all 17 blocks (5 lazy-loaded)
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,
ProductShell, StackLayout, SidebarLayout, DashboardLayout,
DenseTableBlock, MetricCardBlock, EmptyStateBlock,
} from '@middag-io/react';
registerShell('product', ProductShell);
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.tsx';
import { Button } from '@middag-io/react/reui/button.tsx';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
mock/ # Dev playground (standalone SPA, no server needed)
scripts/ # Tooling (sync, doctor)
.githooks/ # Git hooks (pre-commit, commit-msg, pre-push)
.changeset/ # Changesets config for automated versioningWhat 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 | | Mock build (dev playground) | AMD build (Moodle), ESM bundle (WP) |
Extension model
Extensions implement ExtensionPage (extends PageContract) for licensed features. The build gate excludes unlicensed extensions at compile time. Host plugins handle server-side license checks and only send contracts for active extensions.
Development
# Check environment
npm run doctor
# Mock build -- standalone SPA, no server needed
npm run dev:mock # http://localhost:5174
# Typecheck
npm run typecheck
# Lint
npm run lint:fix
# Build ESM lib
npm run buildHost switching (mock only)
The mock build includes a host switcher (M/W button in sidebar footer) to preview the UI inside Moodle Boost header or WordPress admin chrome.
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 | Full typecheck + lib build |
Versioning
Uses Changesets for automated versioning and changelog with GitHub-linked entries.
# After finishing a feature:
npx changeset # select patch/minor/major, write summary
git add . && git commit # include the .changeset/*.md file
# On merge to main:
# GitHub Action opens "Version Packages" PR
# Merge that PR to publish to GitHub PackagesCLI
The package includes a CLI for bootstrapping and maintaining the UI layer in consumer projects:
| Command | Description |
|-----------------------------------------|------------------------------------------------------------|
| npx create-middag-ui | Bootstrap ui/ (Community) |
| npx @middag-io/create-middag-ui | Bootstrap ui/ (PRO) |
| 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:mock | Dev server with hot reload (mock SPA) |
| npm run build | Build ESM lib to dist-lib/ |
| npm run build:mock | Build mock SPA to dist-mock/ |
| 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 run sync:examples | Sync ReUI examples from registry |
| npm run changeset | Create a changeset for versioning |
| npm run release | Build + publish (used by CI) |
Peer Dependencies
react^19.0.0react-dom^19.0.0@inertiajs/react^2.0.0@inertiajs/core^2.0.0
Mock Build Deployment (Cloudflare Pages)
The mock SPA is deployed automatically to Cloudflare Pages on every push to main.
Live URL: https://ui-demo.middag.io
First-time setup
Create the Cloudflare Pages project:
npx wrangler pages project create middag-react-mock --production-branch mainAdd repository secrets in GitHub (
Settings > Secrets and variables > Actions):CLOUDFLARE_API_TOKEN— API token with "Cloudflare Pages: Edit" permissionCLOUDFLARE_ACCOUNT_ID— your Cloudflare account ID (found in dashboard URL or Workers & Pages overview)
Push to
main— thedeploy-mock.ymlworkflow runs automatically.
Manual deploy (local)
npm run build:mock
npx wrangler pages deploy dist-mock --project-name=middag-react-mockCI/CD
| Workflow | Trigger | What it does |
|--------------------------------|-------------------------------------------|----------------------------------------------------------------|
| publish.yml | Push to main | Typecheck + Changesets publish to GitHub Packages + npm public |
| publish-create-middag-ui.yml | Push to main (packages/create-middag-ui/) | Publish to npm (Community) + GitHub Packages (PRO) |
| deploy-mock.yml | Push to main / manual | Build mock + deploy to Cloudflare Pages |
| sync-examples.yml | Monday 8:00 UTC / manual | Sync ReUI examples, opens PR if updates |
Required secrets
| Secret | Registry | Purpose |
|-------------------------|------------------|-------------------------------------------------|
| GITHUB_TOKEN | GitHub Packages | Publish @middag-io/react (auto-provided) |
| NPM_TOKEN | npm (public) | Publish @middag-io/react + create-middag-ui |
| CLOUDFLARE_API_TOKEN | Cloudflare Pages | Deploy mock SPA |
| CLOUDFLARE_ACCOUNT_ID | Cloudflare Pages | Account identifier |
License
Private — MIDDAG proprietary.
