npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-admin-zustand

v0.3.1

Published

React admin UI components with a default Zustand store

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.

npm version License: MIT


Table of Contents


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-zustand

Peer dependencies (must be in your project):

npm install react react-dom

Optional (for API helpers):

npm install axios

Prerequisites — 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:textarea

Generated 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 everything

2. With API + Routes (Full Stack Ready)

node scripts/generate-crud.js User --fields name:string,email:email,role:select,active:boolean --api --routes

Generated 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 everything

3. Custom API Base

node scripts/generate-crud.js Order --fields customer:string,total:number,status:select --api --routes --api-base /api/v2/orders

4. JavaScript (JSX)

node scripts/generate-crud.js BlogPost --fields title:string,content:textarea,published:boolean --jsx

5. Skip Detail, Custom Target

node scripts/generate-crud.js Invoice --fields number:string,amount:number --no-detail --target src/features

Generated 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 onSave handler 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>
  {/