react-admin-zustand
v0.3.1
Published
React admin UI components with a default Zustand store
Maintainers
Readme
react-admin-zustand
A fully dynamic, headless-friendly React admin dashboard package with Zustand as the default state manager. Ships 45+ components, full TypeScript support, works with both JSX and TSX, and is ready to publish on npm.
Table of Contents
- Features
- Installation
- Prerequisites — Google Material Icons
- Quick Start
- Component Reference
- Component Usage Examples
- Admin
- AdminLayout
- Sidebar
- Header
- Icon
- LoginPage
- Dashboard
- Button
- Input
- Select
- TextArea
- Switch
- SearchBar
- Card
- Modal
- ConfirmDialog
- Alert
- Badge
- Avatar
- Tabs
- Dropdown
- DataTable
- NotificationCenter
- Chart
- StatsCard & StatsGrid
- FormBuilder
- Toast & ToastContainer
- Loader
- Skeleton
- ProgressBar
- Tooltip
- Pagination
- Breadcrumb
- PageHeader
- Toolbar
- Divider & Spacer
- Timeline
- EmptyState
- FileUpload
- UserList
- UserCard
- UserForm
- RoleManager
- SettingsPanel
- ProfilePage
- ErrorBoundary
- Hooks
- Store API
- HTTP / API Integration (fetch & axios)
- Full App Examples
- CRUD Generator CLI
- Theming
- Utilities
- TypeScript Types
- Publishing to npm
- License
Features
- 🏗️ 45+ Admin Components — Layout, DataTable, Forms, Modals, Login, Icons, User Management, Roles, Settings, and more
- 🐻 Zustand State Management — Built-in store with full CRUD for users, roles, permissions, notifications, activities
- 🎯 Google Material Icons — Built-in
<Icon>component supports Material Icons, Material Icons Outlined, Material Symbols (all variants) - 🔐 Login Page — Ready-to-use login page with email/password, Google/GitHub/Microsoft social login, forgot password, register
- 📦 Tree-shakable — ESM + CJS outputs, import only what you need
- 🎨 Default CSS included — Ready-to-use styles with CSS variables for theming (light/dark)
- 🔒 TypeScript-first — Full type definitions for every component and store action
- ⚛️ JSX & TSX — Works in JavaScript and TypeScript React projects
- 🧩 Composable — Use individual components or the full admin layout
- 🌐 SSR-safe — No direct DOM access at module scope
- 🔌 Custom Store — Pass your own Zustand store or factory, or use the built-in one
- 🌍 API Support — Built-in helpers for fetch, axios, or any HTTP client
🆕 New in v0.3.0
- 🎯 Auto-collapse Sidebar — Sidebar minimizes by default, expands on hover for space-saving layouts
- 🔍 Advanced DataTable Filtering — Multi-column filters with filter chips, bulk actions bar, page number pagination
- 🎨 Enhanced Table Design — Improved visual styling, export functionality, better row actions
- 🔔 Notification Center — Bell dropdown with unread count, mark-as-read, clear all functionality
- ⌨️ Keyboard Shortcuts Hook — Global hotkey support with
useKeyboardShortcuts - 🗂️ Collapsible Cards — Save space with expandable/collapsible card sections
- 📱 Mobile Responsive — Auto-overlay sidebar on mobile, improved responsive design
- 📊 Complete Chart Library — Bar, Line, Pie, Doughnut, Area charts with interactive features
- 👤 Profile Page Component — Ready-to-use profile management with password change
- 🛠️ JavaScript Store Support — Full store functionality for vanilla JS projects
Installation
npm install react-admin-zustand
# or
yarn add react-admin-zustand
# or
pnpm add react-admin-zustandPeer dependencies (must be in your project):
npm install react react-domOptional (for API helpers):
npm install axiosPrerequisites — Google Material Icons
The <Icon> component renders icons using Google Material Icons / Material Symbols font. Add one or more of these links in your HTML <head> (e.g., public/index.html or index.html):
<!-- Material Icons (filled — default) -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<!-- Material Icons Outlined -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet" />
<!-- Material Icons Round -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet" />
<!-- Material Icons Sharp -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Sharp" rel="stylesheet" />
<!-- Material Symbols Outlined (newer, variable font) -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<!-- Material Symbols Rounded -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded" rel="stylesheet" />Tip: For most projects, just add the first one (Material Icons). You can browse all icon names at fonts.google.com/icons.
Also import the package CSS:
import 'react-admin-zustand/admin.css';Quick Start
TSX (TypeScript)
import React from 'react';
import { Admin, Dashboard, AdminLayout, Header, Sidebar, Icon } from 'react-admin-zustand';
import 'react-admin-zustand/admin.css';
const menuItems = [
{ id: 'dashboard', label: 'Dashboard', icon: <Icon name="dashboard" size="sm" />, path: '/' },
{ id: 'users', label: 'Users', icon: <Icon name="people" size="sm" />, path: '/users' },
{ id: 'roles', label: 'Roles', icon: <Icon name="admin_panel_settings" size="sm" />, path: '/roles' },
{ id: 'settings', label: 'Settings', icon: <Icon name="settings" size="sm" />, path: '/settings' },
];
function App() {
return (
<Admin theme="light">
<AdminLayout
header={<Header title="My Admin" showSearch user={{ name: 'Admin' }} />}
sidebar={<Sidebar items={menuItems} />}
>
<Dashboard greeting="Welcome back, Admin!" />
</AdminLayout>
</Admin>
);
}
export default App;JSX (JavaScript)
import React from 'react';
import { Admin, Dashboard, AdminLayout, Header, Sidebar, Icon } from 'react-admin-zustand';
import 'react-admin-zustand/admin.css';
function App() {
return (
<Admin theme="light">
<AdminLayout
header={<Header title="My Admin" />}
sidebar={
<Sidebar items={[
{ id: '1', label: 'Home', icon: <Icon name="home" size="sm" />, path: '/' },
]} />
}
>
<Dashboard />
</AdminLayout>
</Admin>
);
}
export default App;Component Reference
🏗️ Core
| Component | Description |
|-----------|-------------|
| <Admin> | Root provider. Wraps your app with Zustand store + ErrorBoundary |
| <AdminLayout> | Full page layout with sidebar, header, footer slots |
| <AdminStoreProvider> | Low-level store provider (used internally by <Admin>) |
📐 Layout
| Component | Description |
|-----------|-------------|
| <Sidebar> | Collapsible sidebar with nested menu items, badges, role-based visibility |
| <Header> | Top bar with logo, search, user dropdown, notification bell |
| <Breadcrumb> | Navigation breadcrumb trail |
| <PageHeader> | Page title + subtitle + breadcrumbs + action buttons |
| <Toolbar> | Horizontal toolbar with alignment options |
📊 Data Display
| Component | Description |
|-----------|-------------|
| <DataTable> | Full-featured data table with sorting, pagination, selection, search, row actions, export |
| <StatsCard> | Single KPI card with value, change indicator, icon |
| <StatsGrid> | Grid of StatsCards |
| <Card> | Generic card container with title, subtitle, header, footer, actions |
| <Avatar> | User avatar with initials fallback and status indicator |
| <Badge> | Status badge / notification count badge |
| <Timeline> | Activity timeline with user avatars and timestamps |
| <EmptyState> | Empty data placeholder with icon, title, description, action |
| <Skeleton> | Loading placeholder (text, circle, rect variants) |
📝 Forms
| Component | Description |
|-----------|-------------|
| <FormBuilder> | Dynamic form generator from field config array with validation |
| <Input> | Text input with label, icon, error, helper text |
| <Select> | Dropdown select |
| <TextArea> | Multi-line text input with character count |
| <Switch> | Toggle switch |
| <SearchBar> | Debounced search input with clear button |
| <FileUpload> | Drag-and-drop file upload with size/type validation |
💬 Feedback
| Component | Description |
|-----------|-------------|
| <Modal> | Dialog modal with sizes, ESC/overlay close |
| <ConfirmDialog> | Pre-built confirmation dialog (delete, warning, etc.) |
| <Toast> | Single toast notification |
| <ToastContainer> | Positioned toast stack |
| <Alert> | Inline alert (success, error, warning, info) with dismiss |
| <Loader> | Animated SVG spinner with text, overlay, fullscreen modes |
| <ProgressBar> | Animated progress bar with label |
| <Tooltip> | Hover tooltip (top, bottom, left, right) |
🧭 Navigation
| Component | Description |
|-----------|-------------|
| <Tabs> | Tab navigation (line, card, pill variants) |
| <Dropdown> | Dropdown menu with items, icons, danger items, dividers |
| <Pagination> | Page navigation with page sizes |
| <Button> | Button with variants, sizes, loading, icon support |
🔧 Utility
| Component | Description |
|-----------|-------------|
| <Icon> | Google Material Icons / Material Symbols with size, color, spin, rotate, fill |
| <Divider> | Horizontal/vertical divider with optional text |
| <Spacer> | Vertical/horizontal spacing |
| <ErrorBoundary> | React error boundary with fallback UI |
👤 Domain Components
| Component | Description |
|-----------|-------------|
| <LoginPage> | Complete login page with social login, forgot password, register |
| <Dashboard> | Pre-built dashboard with stats grid + activity timeline |
| <UserList> | User list with table/grid/list views, search, selection |
| <UserCard> | Single user card with avatar, status, actions |
| <UserForm> | User create/edit form with validation |
| <RoleManager> | Role CRUD interface with permissions |
| <SettingsPanel> | Settings form with sections |
Component Usage Examples
Admin
The root provider component. Wraps your app with the Zustand store and error boundary.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| store | StoreApi<AdminStore> | auto | External Zustand store |
| initialState | Partial<AdminStoreState> | {} | Initial store state |
| theme | 'light' \| 'dark' \| 'system' | 'light' | Color theme |
| className | string | — | CSS class |
| children | ReactNode | — | Child components |
import { Admin, createAdminStore } from 'react-admin-zustand';
// 1. Basic — auto-creates store
<Admin><YourApp /></Admin>
// 2. With initial data
<Admin initialState={{ users: [{ id: '1', name: 'Alice' }] }}><YourApp /></Admin>
// 3. Dark theme
<Admin theme="dark"><YourApp /></Admin>
// 4. External store
const myStore = createAdminStore({ settings: { theme: 'dark' } });
<Admin store={myStore}><YourApp /></Admin>AdminLayout
Full-page layout with sidebar, header, and footer slots.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| sidebar | ReactNode | — | Sidebar element |
| header | ReactNode | — | Header element |
| footer | ReactNode | — | Footer element |
| variant | 'sidebar' \| 'topbar' \| 'minimal' | 'sidebar' | Layout style |
| children | ReactNode | — | Main content |
import { AdminLayout, Header, Sidebar } from 'react-admin-zustand';
// Standard sidebar layout
<AdminLayout
header={<Header title="Dashboard" />}
sidebar={<Sidebar items={menuItems} />}
footer={<div>© 2026 MyApp</div>}
>
<p>Main content goes here</p>
</AdminLayout>
// Topbar layout (no sidebar)
<AdminLayout variant="topbar" header={<Header title="App" />}>
<p>Content</p>
</AdminLayout>
// Minimal layout
<AdminLayout variant="minimal">
<p>Clean layout with no chrome</p>
</AdminLayout>Sidebar
🆕 Auto-collapse sidebar with hover-to-expand for clean, space-saving layouts.
Collapsible sidebar with nested menu items, badges, and role-based visibility.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| items | MenuItem[] | required | Menu items |
| header | ReactNode | — | Top section (logo, etc.) |
| footer | ReactNode | — | Bottom section |
| collapsed | boolean | false | Collapsed mode |
| onToggle | () => void | — | Toggle callback |
| width | number | 260 | Expanded width (px) |
| collapsedWidth | number | 64 | Collapsed width (px) |
| activeItemId | string | — | Active item highlight |
| onItemClick | (item: MenuItem) => void | — | Item click callback |
| autoCollapse | boolean | false | Auto-collapse by default, expand on hover |
| expandOnHover | boolean | true | Enable hover expansion when autoCollapse is true |
| hoverExpandDelay | number | 0 | Delay in ms before expanding on hover |
| overlay | boolean | false | Show sidebar as overlay (for mobile) |
| onOverlayClose | () => void | — | Callback when overlay backdrop is clicked |
import { Sidebar, Icon } from 'react-admin-zustand';
import { useState } from 'react';
function MySidebar() {
const [activeId, setActiveId] = useState('dashboard');
const items = [
{ id: 'dashboard', label: 'Dashboard', icon: <Icon name="dashboard" size="sm" />, path: '/' },
{ id: 'users', label: 'Users', icon: <Icon name="people" size="sm" />, path: '/users', badge: 5 },
{
id: 'settings', label: 'Settings', icon: <Icon name="settings" size="sm" />,
children: [
{ id: 'general', label: 'General', path: '/settings/general' },
{ id: 'security', label: 'Security', path: '/settings/security' },
],
},
{ id: 'disabled', label: 'Disabled', disabled: true },
];
return (
<Sidebar
items={items}
autoCollapse={true} // ← Auto-collapse with hover expand
expandOnHover={true}
hoverExpandDelay={100}
activeItemId={activeId}
onItemClick={(item) => setActiveId(item.id)}
header={<div style={{ padding: 16, fontWeight: 700 }}>🚀 MyApp</div>}
footer={<div style={{ padding: 12, fontSize: 12 }}>v1.0.0</div>}
/>
);
}Header
Top bar with logo, search, user dropdown, and notification bell.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| title | string | — | App title |
| logo | ReactNode | — | Logo element |
| showSearch | boolean | false | Show search input |
| onSearch | (query: string) => void | — | Search callback |
| searchPlaceholder | string | 'Search...' | Search placeholder |
| user | Partial<User> | — | Current user (shows avatar + dropdown) |
| onLogout | () => void | — | Logout callback |
| notifications | Notification[] | — | Shows bell with unread count |
| actions | ReactNode | — | Custom action buttons |
import { Header, Icon, Button } from 'react-admin-zustand';
// Basic
<Header title="Admin Panel" />
// Full featured
<Header
title="Admin"
logo={<Icon name="admin_panel_settings" size="lg" color="#3b82f6" />}
showSearch
onSearch={(q) => console.log('Search:', q)}
user={{ name: 'John Doe', avatar: '/avatar.jpg' }}
onLogout={() => console.log('Logout')}
notifications={[
{ id: '1', type: 'info', title: 'New user signed up', read: false, createdAt: new Date().toISOString() },
]}
actions={<Button size="sm" icon={<Icon name="add" size="sm" />}>New</Button>}
/>Icon
Renders Google Material Icons or Material Symbols. Supports all icon variants, sizes, colors, spin, rotate, and Material Symbols variable font settings.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| name | string | required | Icon name (e.g. 'dashboard', 'person', 'settings') |
| provider | IconProvider | 'material' | Font family variant |
| size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number | 'md' | Size (preset or px) |
| color | string | inherit | CSS color |
| spin | boolean | false | Spin animation |
| rotate | number | — | Rotation in degrees |
| button | boolean | false | Render as clickable button |
| onClick | (e) => void | — | Click handler |
| fill | boolean | false | Filled (Material Symbols only) |
| weight | number | 400 | Font weight (Material Symbols) |
| grade | number | 0 | Grade (Material Symbols) |
| opticalSize | number | auto | Optical size (Material Symbols) |
Provider options:
| Provider | Font Family | Requires |
|----------|-------------|----------|
| 'material' | Material Icons (filled) | ?family=Material+Icons |
| 'material-outlined' | Material Icons Outlined | ?family=Material+Icons+Outlined |
| 'material-round' | Material Icons Round | ?family=Material+Icons+Round |
| 'material-sharp' | Material Icons Sharp | ?family=Material+Icons+Sharp |
| 'material-symbols' | Material Symbols Outlined | ?family=Material+Symbols+Outlined |
| 'material-symbols-rounded' | Material Symbols Rounded | ?family=Material+Symbols+Rounded |
| 'material-symbols-sharp' | Material Symbols Sharp | ?family=Material+Symbols+Sharp |
import { Icon } from 'react-admin-zustand';
// Basic icons
<Icon name="home" />
<Icon name="dashboard" />
<Icon name="settings" />
<Icon name="person" />
<Icon name="delete" />
<Icon name="edit" />
<Icon name="search" />
<Icon name="notifications" />
<Icon name="mail" />
<Icon name="favorite" />
// Sizes
<Icon name="star" size="xs" /> {/* 14px */}
<Icon name="star" size="sm" /> {/* 18px */}
<Icon name="star" size="md" /> {/* 24px — default */}
<Icon name="star" size="lg" /> {/* 32px */}
<Icon name="star" size="xl" /> {/* 48px */}
<Icon name="star" size={64} /> {/* custom 64px */}
// Colors
<Icon name="favorite" color="#ef4444" />
<Icon name="check_circle" color="#10b981" />
<Icon name="warning" color="#f59e0b" />
<Icon name="info" color="#3b82f6" />
// Outlined variant
<Icon name="home" provider="material-outlined" />
<Icon name="settings" provider="material-outlined" />
// Rounded variant
<Icon name="home" provider="material-round" />
// Material Symbols with variable font settings
<Icon name="home" provider="material-symbols" fill weight={700} />
<Icon name="settings" provider="material-symbols-rounded" fill={false} weight={300} />
// As a clickable button
<Icon name="close" button onClick={() => console.log('clicked')} />
<Icon name="delete" button color="#ef4444" onClick={handleDelete} />
// Spinning (loading indicator)
<Icon name="refresh" spin />
<Icon name="sync" spin color="#3b82f6" />
// Rotated
<Icon name="arrow_forward" rotate={90} /> {/* points down */}
<Icon name="arrow_forward" rotate={180} /> {/* points left */}
// Use in other components
<Button icon={<Icon name="add" size="sm" />}>Add User</Button>
<Input icon={<Icon name="search" size="sm" />} placeholder="Search..." />
<StatsCard title="Users" value={42} icon={<Icon name="people" size="lg" color="#3b82f6" />} />Popular icon names reference:
| Category | Icons |
|----------|-------|
| Navigation | home, menu, arrow_back, arrow_forward, close, expand_more, chevron_right |
| Action | add, edit, delete, save, search, refresh, download, upload, share |
| Content | content_copy, content_paste, filter_list, sort, view_list, view_module |
| Social | person, people, group, person_add, person_remove, share |
| Communication | mail, chat, phone, message, notifications, campaign |
| File | folder, file_present, cloud_upload, cloud_download, attachment |
| Alert | warning, error, info, check_circle, help, report |
| Media | play_arrow, pause, stop, skip_next, volume_up, image, photo_camera |
| Maps | location_on, map, directions, navigation, my_location |
| Device | laptop, phone_android, tablet, watch, desktop_windows |
| Toggle | star, favorite, bookmark, visibility, visibility_off, lock, lock_open |
LoginPage
A complete, production-ready login page with email/password, social login (Google, GitHub, Microsoft), remember me, forgot password, and register.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onLogin | (creds) => void \| Promise | — | Email/password submit |
| onGoogleLogin | () => void \| Promise | — | Google login callback |
| onGithubLogin | () => void \| Promise | — | GitHub login callback |
| onMicrosoftLogin | () => void \| Promise | — | Microsoft login callback |
| onSocialLogin | (provider: string) => void | — | Generic social login |
| onForgotPassword | () => void | — | Forgot password callback |
| onRegister | () => void | — | Register / sign up callback |
| title | string | 'Sign In' | Page title |
| subtitle | string | 'Welcome back!...' | Subtitle text |
| logo | ReactNode | — | Logo element |
| showRemember | boolean | true | Show remember me checkbox |
| showSocial | boolean | true | Show social login buttons |
| showForgotPassword | boolean | true | Show forgot password link |
| showRegister | boolean | true | Show register / sign up link |
| loading | boolean | false | External loading state |
| error | string | — | External error message |
| success | string | — | Success message |
| variant | 'default' \| 'split' \| 'minimal' \| 'gradient' | 'default' | Page style |
| backgroundImage | string | — | Background image URL (for split variant) |
| appName | string | — | App name shown in footer copyright |
| submitLabel | string | 'Sign In' | Submit button text |
| socialProviders | Array<{ id, label, icon?, onClick? }> | auto | Custom social provider buttons |
| extraFields | ReactNode | — | Extra form fields (e.g. 2FA code) |
| footer | ReactNode | — | Custom footer content |
import { LoginPage, Icon, Input } from 'react-admin-zustand';
// 1. Basic login page
<LoginPage
onLogin={async ({ email, password }) => {
const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) });
if (!res.ok) throw new Error('Invalid credentials');
}}
/>
// 2. Full featured with social login
<LoginPage
title="Welcome Back"
subtitle="Sign in to your admin dashboard"
logo={<Icon name="admin_panel_settings" size="xl" color="#3b82f6" />}
onLogin={async ({ email, password, remember }) => await loginAPI(email, password, remember)}
onGoogleLogin={() => signInWithGoogle()}
onGithubLogin={() => signInWithGitHub()}
onMicrosoftLogin={() => signInWithMicrosoft()}
onForgotPassword={() => navigate('/forgot-password')}
onRegister={() => navigate('/register')}
showSocial
showRemember
showForgotPassword
showRegister
appName="MyAdmin"
/>
// 3. Split layout (image left, form right)
<LoginPage
variant="split"
backgroundImage="/login-bg.jpg"
title="Admin Portal"
onLogin={handleLogin}
onGoogleLogin={handleGoogle}
appName="Enterprise Admin"
/>
// 4. Gradient background
<LoginPage
variant="gradient"
title="Sign In"
logo={<img src="/logo-white.svg" alt="Logo" width={48} />}
onLogin={handleLogin}
showSocial={false}
/>
// 5. Minimal (bordered, no shadow)
<LoginPage variant="minimal" title="Login" onLogin={handleLogin} showSocial={false} showRemember={false} />
// 6. With external error/loading state
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
<LoginPage
loading={loading}
error={error}
onLogin={async (creds) => {
setLoading(true); setError('');
try { await api.login(creds); }
catch (e) { setError(e.message); }
finally { setLoading(false); }
}}
/>
// 7. Custom social providers
<LoginPage
onLogin={handleLogin}
socialProviders={[
{ id: 'google', label: 'Sign in with Google', icon: <Icon name="g_translate" />, onClick: () => googleAuth() },
{ id: 'apple', label: 'Sign in with Apple', icon: <Icon name="apple" />, onClick: () => appleAuth() },
]}
/>
// 8. With 2FA extra field
<LoginPage
onLogin={handleLogin}
extraFields={<Input label="2FA Code" name="code" placeholder="Enter 6-digit code" icon={<Icon name="security" size="sm" />} fullWidth />}
/>
// 9. Using with axios
import axios from 'axios';
<LoginPage
onLogin={async ({ email, password }) => {
const { data } = await axios.post('/api/auth/login', { email, password });
localStorage.setItem('token', data.token);
window.location.href = '/dashboard';
}}
onGoogleLogin={async () => { window.location.href = '/api/auth/google'; }}
/>Dashboard
Pre-built dashboard with stats grid and activity timeline. Auto-reads from store if no props passed.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| stats | StatItem[] | auto from store | Custom stat cards |
| activities | ActivityItem[] | auto from store | Activity timeline |
| greeting | string | auto | Welcome message |
| user | Partial<User> | — | Current user |
| children | ReactNode | — | Custom main content |
import { Dashboard, Icon } from 'react-admin-zustand';
// Auto from store
<Dashboard greeting="Welcome back, Admin!" />
// Custom stats
<Dashboard
stats={[
{ title: 'Revenue', value: '$12,450', icon: <Icon name="attach_money" size="lg" />, change: 12.5, color: '#10b981' },
{ title: 'Orders', value: 356, icon: <Icon name="shopping_cart" size="lg" />, change: -2.3, color: '#3b82f6' },
{ title: 'Users', value: '1,234', icon: <Icon name="people" size="lg" />, change: 8.1, color: '#8b5cf6' },
{ title: 'Tickets', value: 23, icon: <Icon name="support_agent" size="lg" />, change: 0, color: '#f59e0b' },
]}
/>Button
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | 'primary' \| 'secondary' \| 'danger' \| 'warning' \| 'success' \| 'ghost' \| 'link' | 'primary' | Style |
| size | 'xs' \| 'sm' \| 'md' \| 'lg' | 'md' | Size |
| disabled | boolean | false | Disabled |
| loading | boolean | false | Loading spinner |
| icon | ReactNode | — | Icon element |
| iconPosition | 'left' \| 'right' | 'left' | Icon placement |
| fullWidth | boolean | false | Full width |
| type | 'button' \| 'submit' \| 'reset' | 'button' | Button type |
import { Button, Icon } from 'react-admin-zustand';
<Button variant="primary" icon={<Icon name="save" size="sm" />}>Save</Button>
<Button variant="danger" icon={<Icon name="delete" size="sm" />}>Delete</Button>
<Button variant="success" icon={<Icon name="check" size="sm" />}>Approve</Button>
<Button variant="ghost" icon={<Icon name="more_vert" size="sm" />} />
<Button loading>Saving...</Button>
<Button disabled>Disabled</Button>
<Button fullWidth size="lg">Full Width</Button>Input
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| label | string | — | Label text |
| type | string | 'text' | Input type |
| icon | ReactNode | — | Icon element |
| error | string | — | Error message |
| helperText | string | — | Helper text |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Size |
| required | boolean | false | Required |
| onValueChange | (value: string) => void | — | Simple value callback |
import { Input, Icon } from 'react-admin-zustand';
<Input label="Email" type="email" icon={<Icon name="mail" size="sm" />} placeholder="[email protected]" required />
<Input label="Password" type="password" icon={<Icon name="lock" size="sm" />} />
<Input label="Search" icon={<Icon name="search" size="sm" />} placeholder="Search..." />
<Input label="Phone" icon={<Icon name="phone" size="sm" />} />
<Input label="Name" error="Name is required" />
<Input label="Username" helperText="3-20 characters" />Select
import { Select } from 'react-admin-zustand';
<Select
label="Role"
options={[
{ label: 'Admin', value: 'admin' },
{ label: 'Editor', value: 'editor' },
{ label: 'Viewer', value: 'viewer' },
]}
onChange={(val) => console.log(val)}
/>TextArea
import { TextArea } from 'react-admin-zustand';
<TextArea label="Bio" rows={4} placeholder="Tell us about yourself..." maxLength={500} />Switch
import { Switch } from 'react-admin-zustand';
<Switch label="Enable notifications" onChange={(v) => console.log(v)} />
<Switch label="Dark mode" checked={isDark} onChange={setIsDark} />SearchBar
import { SearchBar } from 'react-admin-zustand';
<SearchBar placeholder="Search users..." onChange={(q) => filterUsers(q)} debounceMs={300} clearable />Card
🆕 Collapsible cards for organized, space-saving layouts.
import { Card, Button, Icon } from 'react-admin-zustand';
<Card
title="User Details"
subtitle="Manage user information"
actions={<Button size="sm" icon={<Icon name="edit" size="sm" />}>Edit</Button>}
footer={<div>Last updated: 5 min ago</div>}
hoverable
>
<p>Card content here</p>
</Card>
{/* Collapsible card */}
<Card
title="Advanced Settings"
collapsible
defaultCollapsed={true}
>
<p>This content is hidden by default and can be expanded by clicking the chevron.</p>
<Button>Some Action</Button>
</Card>
{/* Controlled collapsible card */}
<Card
title="Dashboard Widgets"
collapsible
isCollapsed={collapsed}
onCollapseChange={setCollapsed}
>
<WidgetList />
</Card>Card Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| collapsible | boolean | false | Enable collapse/expand toggle |
| defaultCollapsed | boolean | false | Start collapsed |
| isCollapsed | boolean | — | Controlled collapsed state |
| onCollapseChange | (collapsed: boolean) => void | — | Callback when collapse state changes |
Modal
import { Modal, Button, Icon } from 'react-admin-zustand';
<Modal
open={isOpen}
onClose={() => setOpen(false)}
title="Edit User"
size="lg"
footer={
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
<Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
<Button icon={<Icon name="save" size="sm" />}>Save</Button>
</div>
}
>
<p>Modal body content</p>
</Modal>ConfirmDialog
import { ConfirmDialog } from 'react-admin-zustand';
<ConfirmDialog
open={showConfirm}
title="Delete User"
message="Are you sure? This action cannot be undone."
confirmLabel="Delete"
variant="danger"
onConfirm={async () => { await deleteUser(id); setShowConfirm(false); }}
onCancel={() => setShowConfirm(false)}
/>Alert
import { Alert, Icon } from 'react-admin-zustand';
<Alert type="success" message="User created successfully!" />
<Alert type="error" title="Error" message="Failed to save." dismissible />
<Alert type="warning" message="Trial expires in 3 days." action={{ label: 'Upgrade', onClick: () => {} }} />
<Alert type="info" message="New version available." icon={<Icon name="info" size="sm" />} />Badge
import { Badge, Icon } from 'react-admin-zustand';
<Badge variant="success">Active</Badge>
<Badge variant="danger">Expired</Badge>
<Badge count={5} variant="danger"><Icon name="notifications" /></Badge>
<Badge dot variant="success"><Icon name="chat" /></Badge>Avatar
import { Avatar } from 'react-admin-zustand';
<Avatar src="/user.jpg" name="John Doe" size="lg" status="online" />
<Avatar name="Alice Smith" size="md" /> {/* Shows "AS" initials */}Tabs
import { Tabs, Icon } from 'react-admin-zustand';
<Tabs
variant="card"
items={[
{ key: 'overview', label: 'Overview', icon: <Icon name="dashboard" size="sm" />, content: <div>Overview</div> },
{ key: 'users', label: 'Users', icon: <Icon name="people" size="sm" />, content: <div>Users</div>, badge: 12 },
{ key: 'settings', label: 'Settings', icon: <Icon name="settings" size="sm" />, content: <div>Settings</div> },
]}
/>Dropdown
import { Dropdown, Button, Icon } from 'react-admin-zustand';
<Dropdown
trigger={<Button icon={<Icon name="more_vert" size="sm" />} variant="ghost">Actions</Button>}
items={[
{ key: 'edit', label: 'Edit', icon: <Icon name="edit" size="sm" />, onClick: () => {} },
{ key: 'duplicate', label: 'Duplicate', icon: <Icon name="content_copy" size="sm" />, onClick: () => {} },
{ key: 'divider', label: '', divider: true },
{ key: 'delete', label: 'Delete', icon: <Icon name="delete" size="sm" />, danger: true, onClick: () => {} },
]}
/>DataTable
🆕 Advanced multi-column filtering with filter chips, bulk actions bar, page number pagination, and export functionality.
import { DataTable, Badge, Button, Icon } from 'react-admin-zustand';
const users = [
{ id: 1, name: 'John Doe', email: '[email protected]', status: 'active', role: 'admin' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', status: 'inactive', role: 'user' },
];
<DataTable
columns={[
{
key: 'name',
title: 'Name',
sortable: true,
filterable: true,
filterType: 'text'
},
{
key: 'email',
title: 'Email',
ellipsis: true,
filterable: true,
filterType: 'text'
},
{
key: 'status',
title: 'Status',
filterable: true,
filterType: 'select',
filterOptions: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' }
],
render: (v) => <Badge variant={v === 'active' ? 'success' : 'warning'}>{v}</Badge>
},
]}
data={users}
rowKey="id"
selectable
searchable
filterable // ← Enable column-level filtering
striped
hoverable
pagination={{ page: 1, pageSize: 10, total: 100 }}
onPageChange={(page) => setPage(page)}
pageNumberCount={7} // ← Show 7 page number buttons
// Bulk actions for selected rows
bulkActions={(selectedRows) => (
<>
<Button variant="secondary" size="sm">
Export ({selectedRows.length})
</Button>
<Button variant="danger" size="sm">
Delete ({selectedRows.length})
</Button>
</>
)}
// Export functionality
onExport={(data) => {
const csv = data.map(row => Object.values(row).join(',')).join('\n');
downloadCSV(csv, 'users.csv');
}}
exportLabel="Export CSV"
// Row actions
actions={(record) => (
<>
<Button variant="ghost" size="xs" icon={<Icon name="edit" size="sm" />} onClick={() => edit(record)} />
<Button variant="ghost" size="xs" icon={<Icon name="delete" size="sm" />} onClick={() => remove(record)} />
</>
)}
/>New DataTable Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| filterable | boolean | false | Enable column-level filtering UI |
| filters | TableFilter[] | — | Controlled active filters |
| onFiltersChange | (filters: TableFilter[]) => void | — | Filter change callback (controlled mode) |
| bulkActions | ReactNode \| (rows: T[]) => ReactNode | — | Bulk actions when rows selected |
| onExport | (data: T[]) => void | — | Export button callback |
| exportLabel | string | 'Export' | Export button label |
| pageNumberCount | number | 5 | Number of page buttons to show |
Column Filtering:
// Add filterable: true and filterType to any column
const columns = [
{
key: 'status',
title: 'Status',
filterable: true,
filterType: 'select', // 'text' | 'select' | 'date' | 'number'
filterOptions: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' }
]
}
];NotificationCenter
🆕 Notification bell dropdown with unread count badge, mark-as-read, and clear all functionality.
import { NotificationCenter, useAdminStore } from 'react-admin-zustand';
function Header() {
const notifications = useAdminStore((s) => s.notifications);
const markRead = useAdminStore((s) => s.markNotificationRead);
const clearAll = useAdminStore((s) => s.clearNotifications);
return (
<header style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<h1>My Admin</h1>
<NotificationCenter
notifications={notifications}
onRead={(id) => markRead(id)}
onClear={clearAll}
onItemClick={(notification) => {
console.log('Clicked:', notification);
markRead(notification.id);
}}
maxVisible={8}
emptyText="All caught up! 🎉"
/>
</header>
);
}Chart
🆕 Complete chart library with 5 chart types: Bar, Line, Pie, Doughnut, and Area charts.
import { Chart } from 'react-admin-zustand';
// Bar Chart
<Chart
config={{
type: 'bar',
data: [
{ label: 'Jan', value: 45 },
{ label: 'Feb', value: 52 },
{ label: 'Mar', value: 38 },
{ label: 'Apr', value: 61 },
],
title: 'Monthly Sales',
height: 300,
colors: ['#3b82f6', '#10b981', '#f59e0b'],
}}
onDataPointClick={(point, index) => console.log('Clicked:', point)}
/>
// Pie Chart
<Chart
config={{
type: 'pie',
data: [
{ label: 'Desktop', value: 156, color: '#3b82f6' },
{ label: 'Mobile', value: 84, color: '#10b981' },
{ label: 'Tablet', value: 23, color: '#f59e0b' },
],
showLegend: true,
}}
/>
// Doughnut Chart
<Chart
config={{
type: 'doughnut',
data: [
{ label: 'Active', value: 75, color: '#10b981' },
{ label: 'Inactive', value: 25, color: '#ef4444' },
],
title: 'User Status',
}}
/>StatsCard & StatsGrid
import { StatsCard, StatsGrid, Icon } from 'react-admin-zustand';
<StatsCard title="Revenue" value="$12,450" icon={<Icon name="attach_money" size="lg" />} change={12.5} color="#10b981" />
<StatsGrid columns={4} stats={[
{ title: 'Users', value: 1234, icon: <Icon name="people" size="lg" />, change: 8, color: '#3b82f6' },
{ title: 'Revenue', value: '$52K', icon: <Icon name="trending_up" size="lg" />, change: 15, color: '#10b981' },
{ title: 'Orders', value: 89, icon: <Icon name="shopping_cart" size="lg" />, change: -3, color: '#f59e0b' },
{ title: 'Tickets', value: 12, icon: <Icon name="support" size="lg" />, change: 0, color: '#8b5cf6' },
]} />FormBuilder
import { FormBuilder } from 'react-admin-zustand';
<FormBuilder
fields={[
{ name: 'name', label: 'Name', required: true, validation: { required: 'Required', minLength: 3 } },
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'description', label: 'Description', type: 'textarea', rows: 4 },
{ name: 'price', label: 'Price', type: 'number', min: 0 },
{ name: 'category', label: 'Category', type: 'select', options: [
{ label: 'Electronics', value: 'electronics' },
{ label: 'Clothing', value: 'clothing' },
]},
{ name: 'inStock', label: 'In Stock', type: 'switch' },
{ name: 'image', label: 'Image', type: 'file', accept: 'image/*' },
]}
onSubmit={(values) => console.log(values)}
columns={2}
submitLabel="Create"
showReset
/>Toast & ToastContainer
import { ToastContainer, useAdminStore, Button, Icon } from 'react-admin-zustand';
function App() {
const notifications = useAdminStore((s) => s.notifications);
const addNotification = useAdminStore((s) => s.addNotification);
const removeNotification = useAdminStore((s) => s.removeNotification);
return (
<>
<Button icon={<Icon name="check_circle" size="sm" />} onClick={() =>
addNotification({ type: 'success', title: 'Saved!', message: 'Changes saved.' })
}>Show Toast</Button>
<ToastContainer notifications={notifications} onDismiss={removeNotification} position="top-right" />
</>
);
}Loader
import { Loader } from 'react-admin-zustand';
<Loader text="Loading..." />
<Loader size="lg" overlay text="Saving..." />
<Loader fullScreen text="Please wait..." />Skeleton
import { Skeleton } from 'react-admin-zustand';
<Skeleton count={3} />
<Skeleton variant="circle" width={48} height={48} />
<Skeleton variant="rect" width="100%" height={200} />ProgressBar
import { ProgressBar } from 'react-admin-zustand';
<ProgressBar value={75} label="Upload Progress" showValue />
<ProgressBar value={30} color="#ef4444" size="lg" animate />Tooltip
import { Tooltip, Button, Icon } from 'react-admin-zustand';
<Tooltip content="Save your changes" position="top">
<Button icon={<Icon name="save" size="sm" />}>Save</Button>
</Tooltip>Pagination
import { Pagination } from 'react-admin-zustand';
<Pagination page={1} pageSize={10} total={150} onPageChange={setPage} showTotal />Breadcrumb
import { Breadcrumb, Icon } from 'react-admin-zustand';
<Breadcrumb
items={[
{ label: 'Home', path: '/', icon: <Icon name="home" size="xs" /> },
{ label: 'Users', path: '/users' },
{ label: 'John Doe' },
]}
separator={<Icon name="chevron_right" size="xs" />}
/>PageHeader
import { PageHeader, Button, Icon } from 'react-admin-zustand';
<PageHeader
title="Users"
subtitle="Manage your team members"
breadcrumbs={[{ label: 'Home', path: '/' }, { label: 'Users' }]}
actions={
<>
<Button variant="secondary" icon={<Icon name="file_download" size="sm" />}>Export</Button>
<Button icon={<Icon name="person_add" size="sm" />}>Add User</Button>
</>
}
backButton
onBack={() => history.back()}
/>Toolbar
import { Toolbar, Button, Icon } from 'react-admin-zustand';
<Toolbar align="between">
<Button variant="ghost" icon={<Icon name="arrow_back" size="sm" />}>Back</Button>
<div style={{ display: 'flex', gap: 8 }}>
<Button variant="secondary">Cancel</Button>
<Button icon={<Icon name="save" size="sm" />}>Save</Button>
</div>
</Toolbar>Divider & Spacer
import { Divider, Spacer } from 'react-admin-zustand';
<Divider />
<Divider text="OR" />
<Spacer size={24} />
<Spacer axis="horizontal" size={16} />Timeline
import { Timeline, Icon } from 'react-admin-zustand';
<Timeline items={[
{ id: '1', action: 'Created new user', user: { name: 'Alice' }, timestamp: '2026-02-19T10:00:00Z', icon: <Icon name="person_add" size="sm" color="#3b82f6" /> },
{ id: '2', action: 'Updated settings', user: { name: 'Bob' }, timestamp: '2026-02-19T09:30:00Z', icon: <Icon name="settings" size="sm" color="#8b5cf6" /> },
]} />EmptyState
import { EmptyState, Button, Icon } from 'react-admin-zustand';
<EmptyState
icon={<Icon name="inbox" size="xl" color="#9ca3af" />}
title="No users found"
description="Get started by adding your first user"
action={<Button icon={<Icon name="person_add" size="sm" />}>Add User</Button>}
/>FileUpload
import { FileUpload } from 'react-admin-zustand';
<FileUpload onUpload={(files) => console.log(files)} accept="image/*,.pdf" multiple maxSize={5 * 1024 * 1024} maxFiles={3} dragDrop />UserList
import { UserList } from 'react-admin-zustand';
<UserList view="grid" searchable selectable onAdd={() => {}} onEdit={(u) => {}} onDelete={(u) => {}} />UserCard
import { UserCard } from 'react-admin-zustand';
<UserCard user={{ id: '1', name: 'Alice', email: '[email protected]', status: 'active' }} showActions onEdit={edit} onDelete={remove} />UserForm
import { UserForm } from 'react-admin-zustand';
<UserForm
onSubmit={(data) => store.addUser(data)}
onCancel={() => setShowForm(false)}
roles={[{ id: 'admin', name: 'Admin' }, { id: 'user', name: 'User' }]}
/>RoleManager
import { RoleManager, useAdminStore } from 'react-admin-zustand';
<RoleManager
onAddRole={(role) => store.addRole(role)}
onUpdateRole={(id, patch) => store.updateRole(id, patch)}
onDeleteRole={(id) => store.removeRole(id)}
/>SettingsPanel
import { SettingsPanel } from 'react-admin-zustand';
<SettingsPanel
sections={[
{ key: 'profile', title: 'Profile', fields: [
{ name: 'displayName', label: 'Display Name', required: true },
{ name: 'bio', label: 'Bio', type: 'textarea' },
]},
{ key: 'security', title: 'Security', fields: [
{ name: 'password', label: 'New Password', type: 'password' },
{ name: 'twoFactor', label: 'Enable 2FA', type: 'switch' },
]},
]}
onSave={(settings) => console.log(settings)}
/>ProfilePage
🆕 Complete profile management with avatar upload, profile editing, and password change functionality.
import { ProfilePage } from 'react-admin-zustand';
<ProfilePage
user={{
name: 'John Doe',
email: '[email protected]',
avatar: '/avatars/john.jpg',
status: 'active',
roles: ['admin', 'user'],
phone: '+1234567890',
createdAt: '2023-01-15T10:30:00Z',
}}
onUpdateProfile={async (data) => {
// Update user profile
await updateUserProfile(data);
console.log('Profile updated:', data);
}}
onChangePassword={async (passwords) => {
// Change user password
const { currentPassword, newPassword } = passwords;
await changeUserPassword(currentPassword, newPassword);
console.log('Password changed successfully');
}}
onUploadAvatar={async (file) => {
// Upload avatar image
const formData = new FormData();
formData.append('avatar', file);
const response = await uploadAvatar(formData);
return response.url; // Return the new avatar URL
}}
allowPasswordChange={true}
allowProfileEdit={true}
requiredFields={['name', 'email']}
/>ProfilePage Features:
- ✅ Profile information editing (name, email, phone)
- ✅ Avatar upload with preview
- ✅ Password change with validation
- ✅ Status and roles display
- ✅ Account creation date
- ✅ Tabbed interface (Profile / Password)
- ✅ Form validation and error handling
- ✅ Loading states and success/error messages
ProfilePage Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| user | Partial<User> | — | User data to display |
| onUpdateProfile | (data: Partial<User>) => Promise<void> | — | Profile update handler |
| onChangePassword | (data: {currentPassword, newPassword, confirmPassword}) => Promise<void> | — | Password change handler |
| onUploadAvatar | (file: File) => Promise<string> | — | Avatar upload handler |
| allowPasswordChange | boolean | true | Show password change tab |
| allowProfileEdit | boolean | true | Allow profile editing |
| requiredFields | string[] | ['name', 'email'] | Required profile fields |
| loading | boolean | false | Loading state |
| error | string | — | Error message to display |
| success | string | — | Success message to display |
ErrorBoundary
import { ErrorBoundary } from 'react-admin-zustand';
<ErrorBoundary fallback={<div>Something went wrong.</div>} onError={(error) => logToService(error)}>
<MyComponent />
</ErrorBoundary>Hooks
useKeyboardShortcuts
🆕 Global keyboard shortcuts hook for adding hotkeys to your admin interface.
import { useKeyboardShortcuts } from 'react-admin-zustand';
import { useState } from 'react';
function AdminApp() {
const [searchOpen, setSearchOpen] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(true);
useKeyboardShortcuts([
{
key: 'ctrl+k',
handler: () => setSearchOpen(true),
description: 'Open global search'
},
{
key: 'ctrl+b',
handler: () => setSidebarOpen(prev => !prev),
description: 'Toggle sidebar'
},
{
key: 'escape',
handler: () => {
setSearchOpen(false);
// Close any open modals, dropdowns, etc.
},
description: 'Close overlays'
},
{
key: 'ctrl+shift+n',
handler: () => createNewItem(),
description: 'Create new item'
}
]);
return (
<div>
{/* Your admin interface */}
{searchOpen && <GlobalSearch onClose={() => setSearchOpen(false)} />}
</div>
);
}Supported Key Combinations:
- Single keys:
'escape','enter','space','f1'etc. - With modifiers:
'ctrl+k','shift+n','alt+t','meta+s' - Multiple modifiers:
'ctrl+shift+n','ctrl+alt+d'
Store API
Create Store
import { createAdminStore } from 'react-admin-zustand';
const store = createAdminStore();
const store = createAdminStore({ users: [...], settings: { theme: 'dark' } });useAdminStore Hook
const users = useAdminStore((s) => s.users);
const loading = useAdminStore((s) => s.loading);
const addUser = useAdminStore((s) => s.addUser);All Store Actions
// User CRUD
store.getState().addUser({ name: 'Bob', email: '[email protected]' });
store.getState().updateUser(id, { name: 'Updated' });
store.getState().removeUser(id);
store.getState().getUser(id);
store.getState().selectUser(id);
store.getState().setUsers([...]);
store.getState().fetchUsers(async () => fetch('/api/users').then(r => r.json()));
// Role CRUD
store.getState().addRole({ name: 'Admin', permissions: ['read', 'write'] });
store.getState().updateRole(id, { name: 'Super Admin' });
store.getState().removeRole(id);
// Notifications
store.getState().addNotification({ type: 'success', title: 'Saved!' });
store.getState().markNotificationRead(id);
store.getState().removeNotification(id);
store.getState().clearNotifications();
// Settings
store.getState().setSettings({ theme: 'dark' });
store.getState().resetSettings();
// UI
store.getState().toggleSidebar();
store.getState().setLoading(true);
store.getState().setError('Failed');
// Table
store.getState().setPagination({ page: 2, pageSize: 25 });
store.getState().setSort({ key: 'name', direction: 'asc' });
store.getState().addFilter({ key: 'status', value: 'active' });
### 🆕 JavaScript Store (for vanilla JS projects)
For projects using plain JavaScript (without TypeScript), use the JS-specific store:
```js
import { createAdminStoreJS, useAdminStoreJS } from 'react-admin-zustand';
// Create store for JavaScript projects
const store = createAdminStoreJS({
users: [
{ id: 1, name: 'John Doe', email: '[email protected]', status: 'active' }
],
settings: { theme: 'dark', sidebarCollapsed: false }
});
// Use in React components
function MyComponent() {
const users = useAdminStoreJS((state) => state.users);
const addUser = useAdminStoreJS((state) => state.addUser);
const handleAddUser = () => {
addUser({
name: 'New User',
email: '[email protected]',
roles: ['user']
});
};
return (
<div>
<h2>Users: {users.length}</h2>
<button onClick={handleAddUser}>Add User</button>
</div>
);
}
// All the same methods work without type checking
store.getState().addNotification({
type: 'info',
title: 'Hello from JS!',
message: 'This store works perfectly with vanilla JavaScript'
});
store.getState().addChart({
type: 'bar',
data: [
{ label: 'Q1', value: 100 },
{ label: 'Q2', value: 150 },
{ label: 'Q3', value: 120 }
],
title: 'Quarterly Sales'
});JavaScript Store Features:
- ✅ All TypeScript store functionality without types
- ✅ No TypeScript compilation required
- ✅ Same API, just more flexible parameter types
- ✅ Built-in chart data management
- ✅ Profile and user management
- ✅ Perfect for prototyping and JS-only projects
store.getState().removeFilter('status');
// Reset store.getState().reset();
---
## HTTP / API Integration (fetch & axios)
### Raw Data
```ts
const store = createAdminStore({ users: [{ id: '1', name: 'Alice' }] });
store.getState().addUser({ name: 'Bob' });fetch API
useEffect(() => {
fetchUsers(async () => {
const res = await fetch('/api/users');
return res.json();
});
}, []);axios with helpers
import axios from 'axios';
import { createUserApi, createRoleApi } from 'react-admin-zustand';
const api = axios.create({ baseURL: '/api', headers: { Authorization: `Bearer ${token}` } });
const userApi = createUserApi(api, '/users');
const roleApi = createRoleApi(api, '/roles');
await userApi.fetchAll(store);
await userApi.create(store, { name: 'Bob', email: '[email protected]' });
await userApi.update(store, id, { name: 'Updated' });
await userApi.remove(store, id);In React components
function UsersPage() {
const store = useAdminStore();
const users = useAdminStore((s) => s.users);
const loading = useAdminStore((s) => s.loading);
useEffect(() => { userApi.fetchAll(store); }, [store]);
if (loading) return <Loader text="Loading..." />;
return <UserList users={users} />;
}Full App Examples
Complete Admin with Login + Axios
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {
Admin, AdminLayout, LoginPage, Header, Sidebar, Dashboard,
UserList, UserForm, RoleManager, SettingsPanel, Modal,
Icon, Button, useAdminStore, createUserApi,
} from 'react-admin-zustand';
import 'react-admin-zustand/admin.css';
const api = axios.create({ baseURL: '/api' });
const userApi = createUserApi(api, '/users');
function App() {
const [loggedIn, setLoggedIn] = useState(false);
const [page, setPage] = useState('dashboard');
const store = useAdminStore();
useEffect(() => { if (loggedIn) userApi.fetchAll(store); }, [loggedIn]);
if (!loggedIn) {
return (
<LoginPage
variant="split"
title="Admin Portal"
logo={<Icon name="admin_panel_settings" size="xl" color="#3b82f6" />}
onLogin={async ({ email, password }) => {
const { data } = await api.post('/auth/login', { email, password });
api.defaults.headers.common.Authorization = `Bearer ${data.token}`;
setLoggedIn(true);
}}
onGoogleLogin={() => window.location.href = '/api/auth/google'}
appName="MyAdmin"
/>
);
}
return (
<AdminLayout
header={<Header title="Admin" logo={<Icon name="admin_panel_settings" color="#3b82f6" />} showSearch user={{ name: 'Admin' }} onLogout={() => setLoggedIn(false)} />}
sidebar={<Sidebar items={[
{ id: 'dashboard', label: 'Dashboard', icon: <Icon name="dashboard" size="sm" /> },
{ id: 'users', label: 'Users', icon: <Icon name="people" size="sm" /> },
{ id: 'roles', label: 'Roles', icon: <Icon name="admin_panel_settings" size="sm" /> },
{ id: 'settings', label: 'Settings', icon: <Icon name="settings" size="sm" /> },
]} activeItemId={page} onItemClick={(item) => setPage(item.id)} />}
>
{page === 'dashboard' && <Dashboard greeting="Welcome back!" />}
{page === 'users' && <UserList view="table" searchable onDelete={async (u) => await userApi.remove(store, u.id)} />}
{page === 'roles' && <RoleManager />}
{page === 'settings' && <SettingsPanel />}
</AdminLayout>
);
}
export default function Root() {
return <Admin theme="light"><App /></Admin>;
}CRUD Generator CLI
Scaffold a complete CRUD module with a single command. Generates up to 8 files — Store, List, Form, Detail, Page, API, Routes, and index — all wired to react-admin-zustand components.
Installation
The CLI is included with the package. After installing react-admin-zustand, you can use it via:
# Via npx (works after npm install)
npx react-admin-crud <ResourceName> [options]
# Via npm script
npm run generate -- <ResourceName> [options]
# Direct
node scripts/generate-crud.js <ResourceName> [options]Options
| Option | Description | Default |
|--------|-------------|---------|
| --fields <fields> | Comma-separated name:type pairs | name:string |
| --target <dir> | Output directory | src/modules |
| --tsx | Generate .tsx files | ✅ default |
| --jsx | Generate .jsx files | — |
| --api | Generate API integration file (axios) | — |
| --api-base <url> | API base path | /api/<resources> |
| --routes | Generate routes file (React Router v6) | — |
| --no-detail | Skip detail/view component | — |
| --overwrite | Overwrite existing files | — |
| --help | Show help | — |
Field Types
| Type | Form Input | TypeScript |
|------|-----------|------------|
| string | Text input | string |
| number | Number input | number |
| boolean | Switch toggle | boolean |
| email | Email input | string |
| password | Password input | string |
| date | Date picker | string |
| textarea | Multi-line textarea | string |
| select | Dropdown select | string |
| file | File input | string |
| color | Color picker | string |
| range | Range slider | number |
Examples
1. Basic CRUD (Store + List + Form + Detail + Page)
node scripts/generate-crud.js Product --fields name:string,price:number,description:textareaGenerated files in src/modules/Product/:
Product/
├── ProductStore.ts # Zustand store with full CRUD + async + pagination
├── ProductList.tsx # DataTable list with search, delete confirm, empty state
├── ProductForm.tsx # FormBuilder create/edit form with validation
├── ProductDetail.tsx # Detail view with field display, edit/delete buttons
├── ProductPage.tsx # Full page combining List ↔ Form ↔ Detail views
└── index.ts # Re-exports everything2. With API + Routes (Full Stack Ready)
node scripts/generate-crud.js User --fields name:string,email:email,role:select,active:boolean --api --routesGenerated files in src/modules/User/:
User/
├── UserStore.ts # Zustand store
├── UserList.tsx # List with DataTable
├── UserForm.tsx # Create/edit form
├── UserDetail.tsx # Detail view
├── UserPage.tsx # Full CRUD page
├── UserApi.ts # axios API integration (fetchAll, create, update, remove)
├── UserRoutes.tsx # React Router route config + menu item
└── index.ts # Re-exports everything3. Custom API Base
node scripts/generate-crud.js Order --fields customer:string,total:number,status:select --api --routes --api-base /api/v2/orders4. JavaScript (JSX)
node scripts/generate-crud.js BlogPost --fields title:string,content:textarea,published:boolean --jsx5. Skip Detail, Custom Target
node scripts/generate-crud.js Invoice --fields number:string,amount:number --no-detail --target src/featuresGenerated File Details
Store (ProductStore.ts)
Full Zustand store with:
- CRUD:
add,update,remove,getById,setAll - Selection:
select,selectById - Async:
fetchAll(fetcher) - UI state:
loading,error,searchQuery - Pagination:
page,pageSize,total,setPagination reset()
import { createProductStore } from './modules/Product';
const store = createProductStore();
store.getState().add({ name: 'Widget', price: 9.99 });
store.getState().update('id-1', { price: 12.99 });
store.getState().remove('id-1');
store.getState().fetchAll(async () => fetch('/api/products').then(r => r.json()));List (ProductList.tsx)
Uses: DataTable, SearchBar, PageHeader, ConfirmDialog, EmptyState, Loader, Alert, Icon, Button
- Searchable with debounced filtering
- Row actions: View, Edit, Delete (with confirmation)
- Empty state with call-to-action
- Loading state
- Error display
- Accepts external data or reads from store
import { ProductList } from './modules/Product';
<ProductList
onAdd={() => navigate('/products/new')}
onView={(item) => navigate(`/products/${item.id}`)}
onEdit={(item) => navigate(`/products/${item.id}/edit`)}
/>Form (ProductForm.tsx)
Uses: FormBuilder, Card, Alert
- Auto-generates form fields from your
--fields - Create mode (no initial) and Edit mode (with initial)
- Supports external
onSavehandler for API calls - Validation, loading state, reset button
import { ProductForm } from './modules/Product';
// Create
<ProductForm onDone={() => navigate('/products')} onCancel={() => navigate(-1)} />
// Edit
<ProductForm initial={product} onDone={() => navigate('/products')} />
// With API
<ProductForm
initial={product}
onSave={async (data) => await productApi.update(store, product.id, data)}
onDone={() => navigate('/products')}
/>Detail (ProductDetail.tsx)
Uses: Card, Button, Icon, Badge, Toolbar
- Displays all fields in a responsive grid
- Edit and Delete action buttons
- Back navigation
- Created/Updated timestamps
import { ProductDetail } from './modules/Product';
<ProductDetail
item={product}
onEdit={(item) => navigate(`/products/${item.id}/edit`)}
onDelete={(item) => handleDelete(item)}
onBack={() => navigate('/products')}
/>Page (ProductPage.tsx)
Combines List ↔ Form ↔ Detail with internal state-based view switching. No router required.
import { ProductPage } from './modules/Product';
// Standalone (manages its own views internally)
<ProductPage />
// Or with React Router
<Route path="/products/*" element={<ProductPage />} />API (ProductApi.ts) — generated with --api
Axios-compatible HTTP client integration. Methods:
| Method | HTTP | Description |
|--------|------|-------------|
| fetchAll(store, params?) | GET /api/products | Fetch all + populate store |
| fetchById(store, id) | GET /api/products/:id | Fetch one + select in store |
| create(store, data) | POST /api/products | Create + add to store |
| update(store, id, data) | PUT /api/products/:id | Update + patch store |
| remove(store, id) | DELETE /api/products/:id | Delete + remove from store |
import axios from 'axios';
import { createProductApi, createProductStore } from './modules/Product';
const api = axios.create({ baseURL: '/api', headers: { Authorization: `Bearer ${token}` } });
const productApi = createProductApi(api);
const store = createProductStore();
// Fetch all products
await productApi.fetchAll(store);
// Create
const created = await productApi.create(store, { name: 'New Product', price: 19.99 });
// Update
await productApi.update(store, created.id, { name: 'Updated Product' });
// Delete
await productApi.remove(store, created.id);
// With query params (filtering, pagination)
await productApi.fetchAll(store, { page: 2, limit: 25, status: 'active' });Routes (ProductRoutes.tsx) — generated with --routes
Drop-in route config for React Router v6+. Each CRUD operation has its own route.
Generated routes:
| Route | Component | Description |
|-------|-----------|-------------|
| /product | <ProductPage /> | All-in-one page |
| /product/list | <ProductList /> | List only |
| /product/new | <ProductForm /> | Create form |
| /product/:id | <ProductDetail /> | View detail |
| /product/:id/edit | <ProductForm /> | Edit form |
Usage with React Router v6:
import { Routes, Route } from 'react-router-dom';
import { productRouteConfig, productMenuItem } from './modules/Product';
// Option 1: Use route config array
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter([
...productRouteConfig,
// ...other routes
]);
<RouterProvider router={router} />
// Option 2: Use JSX routes
<Routes>
{/