@mdxui/app
v6.0.0
Published
Abstract application framework layer for building admin interfaces and SaaS applications with React. Provides a "Bring Your Own Backend" architecture where you implement the data and auth providers while the framework handles UI composition, navigation, t
Readme
@mdxui/app
Abstract application framework layer for building admin interfaces and SaaS applications with React. Provides a "Bring Your Own Backend" architecture where you implement the data and auth providers while the framework handles UI composition, navigation, theming, and resource management.
Installation
npm install @mdxui/app @mdxui/primitives
# or
pnpm add @mdxui/app @mdxui/primitives
# or
yarn add @mdxui/app @mdxui/primitivesQuick Start
import { App, AppShell, NavMain, NavUser, Resource } from '@mdxui/app'
import type { DataProvider, AuthProvider } from '@mdxui/app'
import { Users, Package } from 'lucide-react'
// 1. Implement your data provider
const dataProvider: DataProvider = {
getList: async (resource, params) => {
const response = await fetch(`/api/${resource}`)
const data = await response.json()
return { data: data.items, total: data.total }
},
getOne: async (resource, { id }) => {
const response = await fetch(`/api/${resource}/${id}`)
return { data: await response.json() }
},
getMany: async (resource, { ids }) => {
const response = await fetch(`/api/${resource}?ids=${ids.join(',')}`)
return { data: await response.json() }
},
create: async (resource, { data }) => {
const response = await fetch(`/api/${resource}`, {
method: 'POST',
body: JSON.stringify(data),
})
return { data: await response.json() }
},
update: async (resource, { id, data }) => {
const response = await fetch(`/api/${resource}/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
})
return { data: await response.json() }
},
delete: async (resource, { id }) => {
const response = await fetch(`/api/${resource}/${id}`, { method: 'DELETE' })
return { data: await response.json() }
},
}
// 2. Implement your auth provider
const authProvider: AuthProvider = {
login: async ({ username, password }) => {
await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
})
},
logout: async () => {
await fetch('/api/auth/logout', { method: 'POST' })
},
checkAuth: async () => {
const response = await fetch('/api/auth/me')
if (!response.ok) throw new Error('Not authenticated')
},
checkError: async (error) => {
if (error.message === 'Unauthorized') throw error
},
getIdentity: async () => {
const response = await fetch('/api/auth/me')
return response.json()
},
getPermissions: async () => {
const response = await fetch('/api/auth/permissions')
return response.json()
},
}
// 3. Define your resources
const resources = [
{ name: 'users', label: 'Users', icon: Users, list: UsersList, edit: UserEdit },
{ name: 'products', label: 'Products', icon: Package, list: ProductsList },
]
// 4. Build your app
function MyAdmin() {
return (
<App
config={{ name: 'My Admin', basePath: '/admin' }}
dataProvider={dataProvider}
authProvider={authProvider}
resources={resources}
>
<AppShell
config={{ name: 'My Admin' }}
nav={<NavMain />}
footer={<NavUser />}
>
{/* Your routes/content here */}
</AppShell>
</App>
)
}Core Concepts
Bring Your Own Backend
@mdxui/app doesn't dictate your backend. You implement two interfaces:
- DataProvider: 6-method interface for CRUD operations
- AuthProvider: 6-method interface for authentication
This lets you connect to any API, database, or backend service.
Resources
Resources are the core building blocks. Each resource represents an entity (users, products, orders) that can be listed, created, edited, and deleted.
const usersResource: ResourceDefinition = {
name: 'users', // Used in URLs and data provider calls
label: 'User', // Singular label
labelPlural: 'Users', // Plural label
icon: Users, // Lucide icon for navigation
list: UsersList, // List view component
show: UserShow, // Detail view component
create: UserCreate, // Create form component
edit: UserEdit, // Edit form component
}Provider Composition
The App component composes all necessary providers in the correct order:
ThemeProvider
└─ NavigationProvider
└─ DataProviderProvider
└─ AuthProvider
└─ ResourcesProvider
└─ AppProvider
└─ Your ContentAPI Reference
Providers
DataProviderProvider & useDataProvider
Provides data access throughout your application.
import { DataProviderProvider, useDataProvider } from '@mdxui/app'
// Wrap your app
<DataProviderProvider dataProvider={myDataProvider}>
<App />
</DataProviderProvider>
// Use in components
function UsersList() {
const dataProvider = useDataProvider()
useEffect(() => {
dataProvider.getList('users', {
pagination: { page: 1, perPage: 10 },
sort: { field: 'name', order: 'ASC' },
filter: { status: 'active' },
}).then(({ data, total }) => {
// Handle data
})
}, [])
}DataProvider Interface:
| Method | Params | Returns |
|--------|--------|---------|
| getList | { pagination?, sort?, filter? } | { data: T[], total?, pageInfo? } |
| getOne | { id } | { data: T } |
| getMany | { ids } | { data: T[] } |
| create | { data } | { data: T } |
| update | { id, data, previousData? } | { data: T } |
| delete | { id, previousData? } | { data: T } |
AuthProvider & useAuth
Manages authentication state and operations.
import { AuthProvider, useAuth } from '@mdxui/app'
// Wrap your app
<AuthProvider authProvider={myAuthProvider}>
<App />
</AuthProvider>
// Use in components
function LoginPage() {
const { login, isLoading } = useAuth()
const handleSubmit = async (data) => {
await login({ username: data.email, password: data.password })
}
}
function Dashboard() {
const { isAuthenticated, identity, logout, permissions } = useAuth()
if (!isAuthenticated) {
return <Redirect to="/login" />
}
return <div>Welcome, {identity?.fullName}!</div>
}AuthProvider Interface:
| Method | Purpose |
|--------|---------|
| login(params) | Authenticate user with credentials |
| logout() | End user session |
| checkAuth() | Verify current auth state (throws if not authenticated) |
| checkError(error) | Handle auth errors (e.g., 401 responses) |
| getIdentity() | Get current user info |
| getPermissions() | Get user permissions/roles |
useAuth Return Value:
interface AuthContextValue {
isAuthenticated: boolean
identity?: UserIdentity
permissions?: string[]
isLoading: boolean
login: (params: LoginParams) => Promise<void>
logout: () => Promise<void>
refreshAuth: () => Promise<void>
}NavigationProvider & useNavigation
Integrates with your routing library (react-router, Next.js, etc.).
import { NavigationProvider, useNavigation } from '@mdxui/app'
import { Link as RouterLink, useNavigate, useLocation } from 'react-router-dom'
// Create adapter for react-router
const navigationProvider = {
navigate: (to, options) => navigate(to, options),
getCurrentPath: () => location.pathname,
LinkComponent: ({ href, children, className }) => (
<RouterLink to={href} className={className}>{children}</RouterLink>
),
}
<NavigationProvider navigationProvider={navigationProvider}>
<App />
</NavigationProvider>
// Use in components
function MyComponent() {
const { navigate, getCurrentPath, Link } = useNavigation()
return (
<div>
<Link href="/dashboard">Go to Dashboard</Link>
<button onClick={() => navigate('/settings')}>Settings</button>
</div>
)
}Default Behavior: If no navigationProvider is passed, uses window.location for navigation and plain <a> tags for links.
ThemeProvider & useTheme
Manages light/dark theme with system preference detection.
import { ThemeProvider, useTheme } from '@mdxui/app'
<ThemeProvider defaultTheme="system" storageKey="my-app-theme">
<App />
</ThemeProvider>
// Use in components
function ThemeToggle() {
const { theme, setTheme, resolvedTheme, toggleTheme } = useTheme()
return (
<button onClick={toggleTheme}>
{resolvedTheme === 'dark' ? 'Switch to Light' : 'Switch to Dark'}
</button>
)
}Theme Options: 'light' | 'dark' | 'system'
useTheme Return Value:
interface ThemeContextValue {
theme: Theme // Current setting (may be 'system')
setTheme: (theme: Theme) => void
resolvedTheme: 'light' | 'dark' // Actual applied theme
toggleTheme: () => void
}ResourcesProvider & useResources
Global registry of all resources in the application.
import { ResourcesProvider, useResources } from '@mdxui/app'
<ResourcesProvider resources={[usersResource, productsResource]}>
<App />
</ResourcesProvider>
// Use in components
function ResourceNav() {
const { resources, getResource } = useResources()
return (
<nav>
{resources.map(r => (
<Link key={r.name} href={`/${r.name}`}>
{r.icon && <r.icon />}
{r.labelPlural ?? r.name}
</Link>
))}
</nav>
)
}useResources Return Value:
interface ResourcesContextValue {
resources: ResourceDefinition[]
getResource: (name: string) => ResourceDefinition | undefined
registerResource: (resource: ResourceDefinition) => void
unregisterResource: (name: string) => void
}ResourceProvider & useResource
Context for a single resource view (list, show, edit, create).
import { ResourceProvider, useResource } from '@mdxui/app'
<ResourceProvider resource="users" definition={usersResource} id={userId}>
<UserEdit />
</ResourceProvider>
// Use in components
function UserEdit() {
const { resource, id, hasEdit, definition } = useResource()
// resource = 'users'
// id = userId
// hasEdit = true (if edit component defined)
}useResource Return Value:
interface ResourceContextValue {
resource: string
definition?: ResourceDefinition
id?: Identifier
hasList: boolean
hasCreate: boolean
hasEdit: boolean
hasShow: boolean
}Components
App
Root component that composes all providers. Use this as the outermost wrapper.
import { App } from '@mdxui/app'
<App
config={{ name: 'Admin', basePath: '/admin', logo: <Logo /> }}
dataProvider={dataProvider}
authProvider={authProvider}
resources={resources}
navigationProvider={routerAdapter} // Optional
defaultTheme="system" // Optional
layout={CustomLayout} // Optional wrapper
>
{children}
</App>Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| config | AppConfig | Yes | App name, basePath, logo, description |
| dataProvider | DataProvider | Yes | Your data provider implementation |
| authProvider | AuthProvider | Yes | Your auth provider implementation |
| resources | ResourceDefinition[] | No | Resource definitions |
| navigationProvider | NavigationProvider | No | Router integration |
| defaultTheme | Theme | No | Default theme ('light', 'dark', 'system') |
| themeStorageKey | string | No | localStorage key for theme |
| layout | ComponentType | No | Custom layout wrapper |
Resource
Declarative resource registration. Can be used instead of or alongside the resources prop on App.
import { Resource } from '@mdxui/app'
<App dataProvider={dp} authProvider={ap} config={config}>
<Resource
name="users"
label="User"
labelPlural="Users"
icon={Users}
list={UsersList}
show={UserShow}
create={UserCreate}
edit={UserEdit}
/>
<Resource
name="products"
label="Product"
labelPlural="Products"
icon={Package}
list={ProductsList}
/>
</App>Resources are automatically registered on mount and unregistered on unmount.
AppShell
Main layout component with sidebar navigation.
import { AppShell, NavMain, NavUser, AppBreadcrumbs } from '@mdxui/app'
<AppShell
config={{ name: 'Admin' }}
navigation={navGroups}
user={currentUser}
header={<TeamSwitcher />}
nav={<NavMain />}
footer={<NavUser />}
pageHeader={<AppBreadcrumbs basePath="/admin" />}
collapsible="icon" // 'offcanvas' | 'icon' | 'none'
variant="inset" // 'sidebar' | 'floating' | 'inset'
>
{children}
</AppShell>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| config | AppConfig | - | App configuration |
| navigation | NavGroup[] | [] | Navigation groups |
| user | UserIdentity | - | Current user |
| header | ReactNode | - | Sidebar header content |
| nav | ReactNode | - | Sidebar navigation content |
| footer | ReactNode | - | Sidebar footer content |
| pageHeader | ReactNode | - | Page header (breadcrumbs) |
| collapsible | string | 'icon' | Collapse behavior |
| variant | string | 'inset' | Sidebar style variant |
NavMain
Renders navigation groups in the sidebar.
import { NavMain } from '@mdxui/app'
// Single group
<NavMain
label="Platform"
items={[
{ title: 'Dashboard', url: '/admin', icon: LayoutDashboard },
{
title: 'Settings',
url: '/admin/settings',
icon: Settings,
items: [
{ title: 'General', url: '/admin/settings/general' },
{ title: 'Team', url: '/admin/settings/team' },
],
},
]}
/>
// Multiple groups
<NavMain
groups={[
{ label: 'Main', items: [...] },
{ label: 'Settings', items: [...] },
]}
/>NavUser
User menu in the sidebar footer with dropdown actions.
import { NavUser } from '@mdxui/app'
<NavUser
accountPath="/admin/settings"
billingPath="/admin/settings/billing"
showThemeToggle
showNotifications
actions={[
{ label: 'API Keys', icon: Key, href: '/admin/settings/api-keys' },
{ label: 'Support', icon: HelpCircle, onClick: openSupport },
]}
/>PageHeader
Header area for pages with breadcrumbs, title, and actions.
import { PageHeader } from '@mdxui/app'
<PageHeader
breadcrumbs={<AppBreadcrumbs />}
title="Users"
description="Manage user accounts"
actions={
<Button>
<Plus className="mr-2 h-4 w-4" />
Add User
</Button>
}
/>AppBreadcrumbs
Auto-generates breadcrumbs from the current URL path.
import { AppBreadcrumbs } from '@mdxui/app'
// Auto-generate from path
<AppBreadcrumbs basePath="/admin" />
// With custom labels
<AppBreadcrumbs
basePath="/admin"
labels={{ 'api-keys': 'API Keys', team: 'Team Members' }}
/>
// Custom items
<AppBreadcrumbs
items={[
{ label: 'Dashboard', href: '/admin' },
{ label: 'Users', href: '/admin/users' },
{ label: 'John Doe' },
]}
/>TypeScript Types
All types are exported for use in your application:
import type {
// Core
Identifier,
RaRecord,
// Auth
UserIdentity,
AuthProvider,
LoginParams,
// Navigation
NavItem,
NavGroup,
BreadcrumbItemData,
UserMenuAction,
LinkProps,
NavigationProvider,
// Data
DataProvider,
GetListParams,
GetListResult,
GetOneParams,
GetOneResult,
GetManyParams,
GetManyResult,
CreateParams,
CreateResult,
UpdateParams,
UpdateResult,
DeleteParams,
DeleteResult,
// Resources
ResourceDefinition,
ResourceContextValue,
// Config
AppConfig,
Theme,
} from '@mdxui/app'Integration with @mdxui/admin
@mdxui/app provides the abstract framework layer. @mdxui/admin extends it with concrete UI components for admin interfaces:
import { App } from '@mdxui/app'
import { List, Datagrid, TextField, EditButton } from '@mdxui/admin'
function UsersList() {
return (
<List>
<Datagrid>
<TextField source="name" />
<TextField source="email" />
<EditButton />
</Datagrid>
</List>
)
}
<App dataProvider={dp} authProvider={ap} config={config}>
<Resource name="users" list={UsersList} />
</App>Integration with @mdxui/do
For applications using .do domains and worker-based backends:
import { App } from '@mdxui/app'
import { createDoDataProvider, DoProvider } from '@mdxui/do'
const doDataProvider = createDoDataProvider({
apiUrl: 'https://api.example.do',
})
<DoProvider>
<App
dataProvider={doDataProvider}
authProvider={doAuthProvider}
config={{ name: 'My App' }}
>
{children}
</App>
</DoProvider>Package Exports
The package provides subpath exports for tree-shaking:
// Main entry (all exports)
import { App, useAuth, useDataProvider } from '@mdxui/app'
// Individual components
import { App } from '@mdxui/app/app'
import { Resource } from '@mdxui/app/resource'
import { AppShell } from '@mdxui/app/shell'
import { NavMain } from '@mdxui/app/nav-main'
import { NavUser } from '@mdxui/app/nav-user'
import { PageHeader } from '@mdxui/app/page-header'
import { AppBreadcrumbs } from '@mdxui/app/breadcrumbs'
// Context providers and hooks
import { useAuth, useDataProvider, useNavigation } from '@mdxui/app/context'
// Type definitions only
import type { DataProvider, AuthProvider } from '@mdxui/app/types'License
MIT
