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

@undefine-ui/design-system

v3.13.1

Published

Define design system React components and theme.

Downloads

831

Readme

@undefine-ui/design-system

React component library + MUI theme layer extracted from the Define design system.

Installation

pnpm add @undefine-ui/design-system
# peer deps you will already have in most React + MUI apps
pnpm add @mui/material @mui/x-data-grid @emotion/react @emotion/styled react react-dom react-hook-form

Note: Fonts (Work Sans and Geist) are bundled with the package and automatically loaded by the ThemeProvider. You don't need to install or import @fontsource packages separately.

Usage

Providers

Every consumer app needs the same provider stack that the showcase uses:

import { SettingsProvider, defaultSettings, ThemeProvider } from '@undefine-ui/design-system';

export function DesignSystemApp({ children }: { children: React.ReactNode }) {
  return (
    <SettingsProvider settings={defaultSettings}>
      <ThemeProvider>{children}</ThemeProvider>
    </SettingsProvider>
  );
}

Next.js App Router Setup

For Next.js 13+ with the App Router, install the additional MUI integration package and set up your providers:

pnpm add @mui/material-nextjs

1. Create a providers file (app/providers.tsx):

'use client';

import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import { SettingsProvider, defaultSettings, ThemeProvider } from '@undefine-ui/design-system';

type ProvidersProps = {
  children: React.ReactNode;
};

export function Providers({ children }: ProvidersProps) {
  return (
    <AppRouterCacheProvider options={{ key: 'css' }}>
      <SettingsProvider settings={defaultSettings}>
        <ThemeProvider>{children}</ThemeProvider>
      </SettingsProvider>
    </AppRouterCacheProvider>
  );
}

2. Update your root layout (app/layout.tsx):

import { getInitColorSchemeScript } from '@undefine-ui/design-system';
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>{getInitColorSchemeScript}</head>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Key points:

  • AppRouterCacheProvider ensures Emotion's CSS-in-JS works correctly with React Server Components and streaming SSR

  • getInitColorSchemeScript prevents flash-of-unstyled-content (FOUC) during hydration by setting the color scheme before React loads

  • suppressHydrationWarning on <html> prevents React warnings from the color scheme script

  • Adjust @mui/material-nextjs/v15-appRouter to match your Next.js version (v13, v14, v15, etc.)

  • SettingsProvider exposes the design-system preferences (mode, contrast, fonts, nav layout) through the useSettings hook. Provide your own settings object if you want different defaults or if you persist user choices.

  • ThemeProvider wraps MUI's CssVarsProvider with the Define theme (createTheme). It accepts any React children and automatically injects CssBaseline and loads the required fonts (Work Sans and Geist).

  • Both providers are exported from the package root so you can colocate them with your router/root layout.

Theming hooks

If you need to read or update theme settings at runtime:

import { useSettings } from '@undefine-ui/design-system';

const ThemeSwitcher = () => {
  const { colorScheme, onUpdateField } = useSettings();

  return (
    <ToggleButtonGroup
      value={colorScheme}
      onChange={(_, value) => value && onUpdateField('colorScheme', value)}
    >
      {/* buttons */}
    </ToggleButtonGroup>
  );
};

useSettings is a thin wrapper around React context, so it must be used inside SettingsProvider.

Component imports

import {
  CopyButton,
  CustomFormLabel,
  Field,
  Form,
  Icon,
  Logo,
  LoadingScreen,
  OTPInput,
  Table,
  Upload
} from '@undefine-ui/design-system';

Storybook (pnpm dev:storybook) documents each component and token surface.

Icon library

The package includes a comprehensive icon library with 70+ SVG icons organized by category.

Available icons:

  • Navigation: NavArrowLeft, NavArrowRight, NavArrowDown, NavArrowDownSolid, LongArrowUpLeftSolid, ArrowLeft, ArrowUpCircleSolid
  • User: User, UserSolid, Building, Bank, BadgeCheck
  • Form Controls: CheckboxDefault, CheckboxSelect, CheckboxIndeterminate, RadioDefault, RadioSelect
  • Actions: Search, Copy, Edit, Trash, XMark, XMarkSolid, CloudUpload, Download, Settings, Plus, Minus, PlusSquare, Attachment, FilterList, RefreshDouble, MoreHorizontal, Repeat, ExpandLines
  • Feedback: InfoCircle, InfoCircleSolid, CheckCircleSolid, HelpCircle, ClipboardCheck, Loader, BellNotification, Circle, ChatBubbleQuestionSolid
  • Visibility: Eye, EyeClosed, ZoomIn, ZoomOut
  • Date & Time: Calendar, Clock
  • Data: SortUp, SortDown, StatUp, StatDown
  • Toast: InfoToast, SuccessToast, WarningToast, ErrorToast
  • Files: FilePdf, FileAudio, FileVideo, FileDocument, FileGeneric
  • Location: MapPin, MapPinXMark
  • Media: ModernTv, MegaPhone
  • Commerce: SimpleCart
  • Communication: SendMail
  • System: KeyCommand

Usage:

import { Icon } from '@undefine-ui/design-system';

// Basic usage
<Icon icon="Search" sx={{ width: 24, height: 24 }} />

// With custom color
<Icon icon="InfoCircleSolid" sx={{ width: 32, height: 32, color: 'primary.main' }} />

// Different sizes
<Icon icon="Loader" sx={{ width: 16, height: 16 }} />
<Icon icon="Loader" sx={{ width: 48, height: 48 }} />

Props:

  • icon - Icon name (required, type: IconType)
  • sx - MUI sx prop for styling (size, color, etc.)
  • All BoxProps from MUI are supported

The Icon component renders SVG icons with full theme integration and accepts any MUI Box props for flexible styling.

DetailItem

A simple label-value display component for showing detail information. Commonly used in detail panels, drawers, and info sections to display structured data.

Usage:

import { DetailItem } from '@undefine-ui/design-system';

// Basic usage with value prop
<DetailItem label="Full Name" value="John Doe" />

// With custom children (for chips, badges, etc.)
<DetailItem label="Status">
  <Chip label="Active" color="success" size="small" />
</DetailItem>

// Multiple items in a list
<Stack spacing={2}>
  <DetailItem label="Booking ID" value="#001" />
  <DetailItem label="Date Created" value="23 Aug 2025" />
  <DetailItem label="Amount" value="N250,000" />
</Stack>

Props:

  • label - The label text displayed above the value (required)
  • value - The value text to display (optional if children provided)
  • children - Custom React content to render instead of value text

Image

A high-performance lazy-loading image component with responsive image support, fallback handling, and smooth loading transitions. Perfect for optimizing image delivery across different devices and screen sizes.

Usage:

import { Image } from '@undefine-ui/design-system';

// Basic usage with lazy loading
<Image
  src="/path/to/image.jpg"
  alt="Description"
  aspectRatio="16 / 9"
/>

// Responsive images with srcSet
<Image
  src="/image-1280w.jpg"
  srcSet="/image-320w.jpg 320w, /image-640w.jpg 640w, /image-1280w.jpg 1280w"
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt="Responsive image"
  aspectRatio="16 / 9"
/>

// Retina display support
<Image
  src="/image.jpg"
  srcSet="/image.jpg 1x, /[email protected] 2x, /[email protected] 3x"
  alt="High DPI image"
/>

// With fallback for error handling
<Image
  src="/primary-image.jpg"
  fallbackSrc="/fallback-image.jpg"
  alt="Image with fallback"
  aspectRatio="4 / 3"
/>

// Disable lazy loading for above-the-fold images
<Image
  src="/hero-image.jpg"
  alt="Hero image"
  lazy={false}
  aspectRatio="21 / 9"
/>

// Custom loading indicator
<Image
  src="/image.jpg"
  alt="Custom loading"
  loadingIndicator={<CircularProgress />}
/>

// With overlay content
<Image
  src="/image.jpg"
  alt="Image with overlay"
  overlay={
    <Box sx={{ p: 2, color: 'white' }}>
      <Typography variant="h5">Overlay Text</Typography>
    </Box>
  }
/>

// Custom object-fit and position
<Image
  src="/portrait.jpg"
  alt="Portrait"
  aspectRatio="1"
  fit="contain"
  position="top center"
/>

Props:

  • src - Image source URL (required)
  • alt - Alternative text for accessibility (required)
  • lazy - Enable lazy loading with Intersection Observer (default: true)
  • srcSet - Responsive image sources for different screen sizes/resolutions
  • sizes - Defines which image size to use at different viewport widths
  • fallbackSrc - Fallback URL when the main source fails to load
  • aspectRatio - Aspect ratio to prevent layout shift (e.g., "16 / 9", 1.5)
  • fit - Controls object-fit CSS property (default: "cover")
  • position - Controls object-position CSS property (default: "center")
  • overlay - Optional overlay content rendered above the image
  • withOverlay - Enables overlay even without content
  • loadingIndicator - Custom loading indicator component
  • renderError - Custom error fallback UI
  • observerMargin - Intersection Observer root margin (default: "200px")
  • imgSx - Additional styles for the image element
  • imgProps - Additional props for the underlying image element
  • onLoad - Callback fired when the image loads
  • onError - Callback fired when the image fails to load

Features:

  • Lazy loading: Uses Intersection Observer for efficient viewport detection
  • Responsive images: Full support for srcSet and sizes attributes
  • Fallback handling: Automatic retry with fallback source on error
  • Layout stability: Prevents layout shift with aspect ratio control
  • Smooth transitions: Fade-in animation when image loads
  • Custom loading states: Skeleton loader by default, customizable
  • Error handling: Graceful error UI with customization options
  • Overlay support: Render content on top of images
  • Browser fallback: Uses native loading="lazy" when IntersectionObserver unavailable
  • Flexible styling: Supports all MUI Box props for container and image

Performance Tips:

  • Use lazy={false} for above-the-fold images to avoid lazy loading delay
  • Always specify aspectRatio to prevent layout shift during loading
  • Use srcSet with appropriate sizes for optimal image delivery
  • Set observerMargin="400px" to preload images slightly before viewport

Toast Notifications

Toast notifications powered by Sonner with Define styling. Supports success, error, warning, info variants with optional descriptions and action buttons.

Setup:

Add the Toast component once at the root of your application:

import {
  SettingsProvider,
  defaultSettings,
  ThemeProvider,
  Toast
} from '@undefine-ui/design-system';

export function App({ children }: { children: React.ReactNode }) {
  return (
    <SettingsProvider settings={defaultSettings}>
      <ThemeProvider>
        <Toast />
        {children}
      </ThemeProvider>
    </SettingsProvider>
  );
}

Usage:

import { toast } from '@undefine-ui/design-system';

// Basic toast
toast('This is a basic toast');

// Variants
toast.success('Success Notification');
toast.error('Error Notification');
toast.warning('Warning Notification');
toast.info('Information Notification');
toast.loading('Loading...');

// With description
toast.success('Event created', {
  description: 'Your event has been created successfully'
});

// With action buttons
toast('Are you sure?', {
  description: 'This action cannot be undone',
  cancel: {
    label: 'Cancel',
    onClick: () => console.log('Cancelled')
  },
  action: {
    label: 'Confirm',
    onClick: () => console.log('Confirmed')
  }
});

// Promise-based (auto shows loading → success/error)
toast.promise(fetchData(), {
  loading: 'Saving changes...',
  success: 'Changes saved successfully',
  error: 'Failed to save changes'
});

Features:

  • Multiple variants: Success, error, warning, info, loading states
  • Action buttons: Cancel and action buttons with callbacks
  • Promise support: Auto-transition from loading to success/error
  • Dark themed: Consistent dark background with themed icons
  • Positioned: Top-right with expandable queue

File Upload (React Hook Form)

Form-ready file upload component integrated with React Hook Form. Supports single and multiple file uploads with automatic form state management, deduplication, and lifecycle callbacks.

Usage:

import { Form, Field } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';

const MyForm = () => {
  const methods = useForm({
    defaultValues: {
      avatar: null,
      documents: []
    }
  });

  return (
    <Form methods={methods} onSubmit={(data) => console.log(data)}>
      {/* Single file upload */}
      <Field.Upload name="avatar" helperText="Upload your profile picture" />

      {/* Multiple files upload */}
      <Field.Upload name="documents" multiple helperText="Upload multiple documents" />

      {/* With lifecycle callbacks */}
      <Field.Upload
        name="photos"
        multiple
        onDropComplete={(files) => toast.success(`${files.length} files added`)}
        onRemoveComplete={(file) => console.log('Removed:', file)}
        onRemoveAllComplete={() => toast.info('All files cleared')}
      />

      {/* Custom accept types */}
      <Field.Upload
        name="resume"
        accept={{
          'application/pdf': ['.pdf'],
          'application/msword': ['.doc', '.docx']
        }}
        helperText="PDF or Word documents only"
      />
    </Form>
  );
};

Props:

  • name - Form field name for react-hook-form (required)
  • multiple - Enable multiple file selection (default: false)
  • helperText - Helper text displayed below the upload area
  • accept - Accepted file types object (e.g., { 'image/*': [] })
  • maxSize - Maximum file size in bytes
  • maxFiles - Maximum number of files (for multiple mode)
  • disabled - Disable the upload area

Lifecycle Callbacks:

These callbacks are called after the internal form state is updated, allowing you to add side effects like notifications, analytics, or logging without breaking form integration:

  • onDropComplete - Called after files are added to form state (files: File[]) => void
  • onDeleteComplete - Called after a file is deleted from form state (single mode) () => void
  • onRemoveComplete - Called after a file is removed from form state (multiple mode) (file: File | string) => void
  • onRemoveAllComplete - Called after all files are removed from form state (multiple mode) () => void

Features:

  • Form integration: Automatically syncs with react-hook-form state
  • Validation: Displays validation errors from react-hook-form
  • Deduplication: Prevents adding duplicate files in multiple mode
  • Safe callbacks: Lifecycle callbacks extend (not override) internal form logic
  • Full control: Use the base Upload component directly if you need to override behavior

Date Pickers (React Hook Form)

Form-ready date picker components integrated with React Hook Form. Includes RHFDatePicker, RHFTimePicker, and RHFDateTimePicker for seamless form state management.

Usage:

import { Form, RHFDatePicker, RHFTimePicker, RHFDateTimePicker } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';

const MyForm = () => {
  const methods = useForm({
    defaultValues: {
      birthDate: null,
      meetingTime: null,
      eventStart: null
    }
  });

  return (
    <Form methods={methods} onSubmit={(data) => console.log(data)}>
      {/* Date Picker */}
      <RHFDatePicker name="birthDate" label="Birth Date" />

      {/* Time Picker */}
      <RHFTimePicker name="meetingTime" label="Meeting Time" />

      {/* DateTime Picker */}
      <RHFDateTimePicker name="eventStart" label="Event Start" />

      {/* Clearable variant */}
      <RHFDatePicker name="deadline" label="Deadline" clearable />
    </Form>
  );
};

Props (shared by all variants):

  • name - Form field name for react-hook-form (required)
  • label - Label for the picker input
  • helperText - Helper text displayed below the input
  • clearable - Enable clear button (default: false)
  • disabled - Disable the picker (default: false)
  • All MUI X DatePicker/TimePicker/DateTimePicker props are supported

Features:

  • Form integration: Automatically syncs with react-hook-form state
  • Validation: Displays validation errors from react-hook-form
  • Clearable: Optional clear button to reset the value
  • Theme-styled: Uses your theme's TextField styling
  • Click-to-open: Opens picker on text field click

DateRangePicker

A date range picker with preset options (Today, Last 7 Days, etc.), dual calendars for start/end date selection, and custom date range support. Includes both a standalone DateRangePicker component and a DateRangeDropdown for toolbar integration.

Usage:

import {
  DateRangePicker,
  DateRangeDropdown,
  defaultPresets,
  getDateRangeFromPreset,
  type DateRange,
  type DatePreset
} from '@undefine-ui/design-system';

// Standalone DateRangePicker
const [range, setRange] = useState<DateRange>({ start: null, end: null });
const [preset, setPreset] = useState<DatePreset>('last30days');

<DateRangePicker
  value={range}
  preset={preset}
  onChange={(newRange, newPreset) => {
    setRange(newRange);
    setPreset(newPreset);
  }}
  onCancel={() => console.log('Cancelled')}
/>

// DateRangeDropdown (for toolbars)
<DateRangeDropdown
  value={range}
  preset={preset}
  onChange={(newRange, newPreset) => {
    setRange(newRange);
    setPreset(newPreset);
  }}
/>

// With custom presets
const customPresets = [
  { value: 'today', label: 'Today' },
  { value: 'last7days', label: 'This Week' },
  { value: 'last30days', label: 'This Month' },
  { value: 'custom', label: 'Pick Dates' }
];

<DateRangePicker presets={customPresets} {...props} />

// Get date range programmatically
const last7DaysRange = getDateRangeFromPreset('last7days');
// { start: Date, end: Date }

DateRangePicker Props:

  • value - Currently selected date range { start: Date | null, end: Date | null }
  • preset - Currently selected preset ('today', 'yesterday', 'last7days', 'last30days', 'last6months', 'lastyear', 'alltime', 'custom')
  • onChange - Callback when date range changes (range, preset) => void
  • onCancel - Callback when cancel is clicked
  • presets - Custom preset options array (default: defaultPresets)
  • showPresets - Show preset radio buttons (default: true)
  • showActions - Show Cancel/Apply buttons (default: true)
  • dateFormat - Date format for input display (default: 'dd/MM/yyyy')
  • minDate / maxDate - Date constraints

DateRangeDropdown Props:

  • Inherits all DateRangePicker props
  • buttonLabel - Fallback label for the dropdown button (default: 'Date')
  • disabled - Disable the dropdown
  • popoverSx - Custom styles for the popover
  • Button label automatically updates to show selected preset or custom date range (e.g., "Dec 1 - Dec 15, 2024")

Exported Helpers:

  • defaultPresets - Default preset options array
  • getDateRangeFromPreset(preset) - Convert preset to actual date range

React Hook Form Integration (Field.DateRange):

import { Form, Field } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';

const MyForm = () => {
  const methods = useForm({
    defaultValues: {
      dateRange: { start: null, end: null, preset: 'today' }
    }
  });

  return (
    <Form methods={methods} onSubmit={(data) => console.log(data)}>
      <Field.DateRange name="dateRange" label="Select Date Range" />
    </Form>
  );
};

Field.DateRange Props:

  • name - Form field name (required, stores { start, end, preset })
  • label - Label for the text field
  • placeholder - Placeholder text (default: 'Select date range')
  • helperText - Helper text below the input
  • dateFormat - Date format for display (default: 'MMM d, yyyy')
  • presets - Custom preset options
  • disabled - Disable the field
  • size - TextField size ('small' | 'medium')
  • fullWidth - Full width mode (default: true)

Toolbar Components

A collection of components for building data table toolbars with search, filters, sorting, view switching, and date range selection.

Usage:

import {
  ToolbarButton,
  ToolbarSearchField,
  ToolbarViewSwitcher,
  ToolbarFilterButton,
  ToolbarSortButton,
  ToolbarSettingsButton,
  ToolbarTodayButton,
  ToolbarDatePickerButton,
  ToolbarSelectionButton,
  FilterDropdown,
  SortDropdown,
  DateRangeDropdown,
  type ViewOption,
  type SortOption,
  type SortDirection
} from '@undefine-ui/design-system';

// Base toolbar button with icon and label
<ToolbarButton icon="Filter" label="Filter" onClick={handleClick} />

// Base toolbar button with custom children (icon and label are ignored when children provided)
<ToolbarButton>
  <CustomIcon />
  <span>Custom Content</span>
</ToolbarButton>

// Specialized button components
<ToolbarFilterButton onClick={handleFilter} />
<ToolbarSortButton onClick={handleSort} />
<ToolbarSettingsButton onClick={handleSettings} />
<ToolbarTodayButton onClick={handleToday} />
<ToolbarDatePickerButton label="Last 30 days" onClick={handleDate} />

// Selection button - shows count with clear action
<ToolbarSelectionButton count={3} onClear={() => clearSelection()} />
<ToolbarSelectionButton count={5} label="items" onClear={handleClear} />
<ToolbarSelectionButton count={3} showClearButton={false} />

// Search field with clear button
<ToolbarSearchField
  value={search}
  onChange={(e) => setSearch(e.target.value)}
  onClear={() => setSearch('')}
  placeholder="Search..."
/>

// View switcher (year/month/week/day dropdown button)
const [view, setView] = useState<ViewOption>('week');
<ToolbarViewSwitcher value={view} onClick={() => setView('month')} />

// Filter dropdown - manages its own popover state, just pass children
<FilterDropdown>
  <Form methods={methods}>
    <Field.Text name="status" label="Status" size="small" select>
      <MenuItem value="active">Active</MenuItem>
      <MenuItem value="inactive">Inactive</MenuItem>
    </Field.Text>
    {/* Add more filter controls */}
  </Form>
</FilterDropdown>

// Sort dropdown with unified onChange callback
const [sortValue, setSortValue] = useState('name');
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');

const sortOptions: SortOption[] = [
  { value: 'name', label: 'Name' },
  { value: 'date', label: 'Date Created' }
];

<SortDropdown
  options={sortOptions}
  value={sortValue}
  direction={sortDirection}
  onChange={(value, direction) => {
    setSortValue(value);
    setSortDirection(direction);
  }}
/>

// Complete toolbar example
<Stack direction="row" spacing={1} alignItems="center">
  <ToolbarSearchField value={search} onChange={...} onClear={...} />
  <Box sx={{ flexGrow: 1 }} />
  <DateRangeDropdown value={range} preset={preset} onChange={...} />
  <FilterDropdown>{filterContent}</FilterDropdown>
  <SortDropdown options={sortOptions} value={sortValue} direction={sortDirection} onChange={...} />
  <Divider orientation="vertical" flexItem />
  <ToolbarViewSwitcher value={view} onClick={...} />
</Stack>

ToolbarButton Props:

  • icon - Icon name from the icon library (used when children is not provided)
  • label - Button label text (used when children is not provided)
  • children - Custom content to render. When provided, icon and label props are ignored
  • open - Whether the button is in open/active state
  • disabled - Disable the button
  • All standard MUI ButtonBase props

ToolbarSelectionButton Props:

  • count - Number of selected items (required)
  • label - Text after the count (default: 'selected')
  • onClear - Callback when the clear button is clicked
  • showClearButton - Whether to show the clear button (default: true)
  • All standard MUI ButtonBase props (except icon, open, children)

FilterDropdown Props:

  • children - Content to render inside the popover (use Form + Field components)
  • buttonLabel - Custom label for the filter button (default: 'Filter')
  • onClose - Called when the popover closes
  • disabled - Disable the dropdown
  • popoverSx - Custom styles for the popover paper
  • PopoverProps - Additional popover props

SortDropdown Props:

  • options - Array of sort options { value: string, label: string }[]
  • value - Currently selected sort field
  • direction - Current sort direction ('asc' or 'desc')
  • onChange - Callback when sort changes (value: string, direction: SortDirection) => void
  • buttonLabel - Custom label for the sort button (default: 'Sort')
  • ascLabel - Custom label for ascending (default: 'Ascending')
  • descLabel - Custom label for descending (default: 'Descending')
  • disabled - Disable the dropdown

ToolbarViewSwitcher Props:

  • value - Currently selected view ('year' | 'month' | 'week' | 'day')
  • open - Whether the button is in open/active state
  • All standard MUI ButtonBase props

Google Places Autocomplete

A Google Places address autocomplete component with structured address parsing, coordinates extraction, and React Hook Form integration. Perfect for address lookup, location selection, and form-based address capture.

Setup:

import { GooglePlacesProvider } from '@undefine-ui/design-system';

// Wrap your app with GooglePlacesProvider
function App() {
  return (
    <GooglePlacesProvider apiKey={process.env.GOOGLE_MAPS_API_KEY}>
      {/* Your app content */}
    </GooglePlacesProvider>
  );
}

Usage:

import {
  GooglePlacesAutocomplete,
  Field,
  type PlaceType,
  type PlaceDetails
} from '@undefine-ui/design-system';

// Basic usage
const [value, setValue] = useState<PlaceType | null>(null);

<GooglePlacesAutocomplete
  label="Search for a location"
  placeholder="Start typing..."
  onChange={setValue}
/>

// With place details (includes parsed address and coordinates)
<GooglePlacesAutocomplete
  label="Search address"
  fetchPlaceDetails
  onPlaceDetailsChange={(details) => {
    console.log(details?.parsedAddress?.city);    // "Lagos"
    console.log(details?.parsedAddress?.country); // "Nigeria"
    console.log(details?.coordinates?.latitude);  // 6.5245
  }}
/>

// With country restriction
<GooglePlacesAutocomplete
  label="US addresses only"
  componentRestrictions={{ country: 'us' }}
  onChange={setValue}
/>

React Hook Form Integration:

import { Form, Field } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';

const MyForm = () => {
  const methods = useForm({
    defaultValues: {
      location: null,
      address: ''
    }
  });

  return (
    <Form methods={methods} onSubmit={(data) => console.log(data)}>
      {/* Store full PlaceType object */}
      <Field.GooglePlacesAutocomplete name="location" label="Location" valueType="full" />

      {/* Store only description string */}
      <Field.GooglePlacesAutocomplete name="address" label="Address" valueType="description" />

      {/* Store PlaceDetails with parsed address */}
      <Field.GooglePlacesAutocomplete
        name="fullAddress"
        label="Full Address"
        valueType="details"
        fetchPlaceDetails
      />
    </Form>
  );
};

GooglePlacesAutocomplete Props:

  • label - Input label
  • placeholder - Input placeholder
  • value - Selected place (controlled)
  • onChange - Callback when selection changes (place: PlaceType | null) => void
  • fetchPlaceDetails - Fetch full place details on selection (default: false)
  • onPlaceDetailsChange - Callback with place details (details: PlaceDetails | null) => void
  • componentRestrictions - Country restrictions { country: string | string[] }
  • types - Place types to search for (e.g., ['address'], ['establishment'])
  • debounceMs - Debounce delay in milliseconds (default: 300)
  • error / errorMessage / helperText - Error and helper text display
  • All standard MUI TextField props

Field.GooglePlacesAutocomplete Props:

  • name - Form field name (required)
  • valueType - What to store in form: 'full' (PlaceType), 'description' (string), or 'details' (PlaceDetails)
  • onValueChange - Callback when value changes (raw PlaceType)
  • onPlaceDetailsChange - Callback when place details are fetched
  • All GooglePlacesAutocomplete props except value, onChange, error, errorMessage

PlaceDetails Structure:

interface PlaceDetails {
  placeId: string;
  description: string;
  formattedAddress?: string;
  lat?: number;
  lng?: number;

  // Structured parsed address
  parsedAddress?: {
    streetNumber?: string;
    street?: string;
    address?: string; // Full formatted address
    neighborhood?: string;
    city?: string;
    state?: string;
    stateCode?: string;
    country?: string;
    countryCode?: string;
    postalCode?: string;
    county?: string;
  };

  // Coordinates
  coordinates?: {
    latitude: number;
    longitude: number;
  };
}

Exported Utilities:

  • GooglePlacesProvider - Context provider for API key
  • useGooglePlacesAutocomplete - Hook for custom implementations
  • parseAddressComponents - Parse raw Google address components into structured format

OTP Input

A one-time password input component with keyboard navigation, paste support, and validation. Perfect for email/SMS verification, PIN codes, and security codes.

Usage:

import { OTPInput, Field } from '@undefine-ui/design-system';

// Basic usage
<OTPInput
  length={6}
  onChange={(otp) => console.log(otp)}
  onComplete={(otp) => console.log('Complete:', otp)}
  helperText="Enter the 6-digit code"
/>

// With React Hook Form
<Field.OTP
  name="verificationCode"
  length={6}
  helperText="Enter the code sent to your email"
/>

// 4-digit PIN
<Field.OTP
  name="pin"
  length={4}
  helperText="Enter your PIN"
/>

// Error state
<OTPInput
  length={6}
  error
  helperText="Invalid code. Please try again."
/>

Props:

  • length - Number of OTP input fields (default: 6)
  • onChange - Callback fired when OTP value changes (otp: string) => void
  • onComplete - Callback fired when all fields are filled (otp: string) => void
  • error - Show error state (default: false)
  • helperText - Helper text displayed below the input
  • containerProps - Props passed to the container Box component

Features:

  • Keyboard navigation: Arrow keys to move between fields, Backspace to clear
  • Paste support: Automatically fills all fields from clipboard
  • Auto-focus: Moves to next field after entering a digit
  • Validation: Only accepts numeric input
  • Error handling: Visual error states with helper text
  • Responsive: Adapts input size on mobile devices

Hook Form Integration:

The Field.OTP component automatically integrates with React Hook Form, providing validation and error handling out of the box.

FormLabel

A custom form label component with support for optional text, tooltips, and custom icons. Perfect for creating consistent form field labels across your application.

Usage:

import { CustomFormLabel } from '@undefine-ui/design-system';

// Basic label
<CustomFormLabel label="Email Address" />

// Optional field label
<CustomFormLabel label="Phone Number" optional />

// With tooltip
<CustomFormLabel
  label="API Key"
  tooltip="Your API key can be found in the developer settings."
/>

// Optional with tooltip
<CustomFormLabel
  label="Company Name"
  optional
  tooltip="Enter your company name if applicable."
/>

// With custom icon
<CustomFormLabel
  label="Password"
  tooltip="Password must be at least 8 characters long."
  icon={<Icon icon="HelpCircle" sx={{ width: 16, height: 16, color: 'primary.main' }} />}
/>

// Complete form example
<Box>
  <CustomFormLabel label="Full Name" />
  <TextField fullWidth placeholder="Enter your full name" size="small" />
</Box>

Props:

  • label - Label text to display (required)
  • optional - Show "(Optional)" text after label (default: false)
  • tooltip - Tooltip text shown on hover of info icon
  • icon - Custom icon for tooltip (default: InfoCircle icon)
  • sx - MUI sx prop for styling
  • All MUI FormLabelProps are supported (except children)

Features:

  • Optional indicator: Shows "(Optional)" text for non-required fields
  • Tooltip support: Info icon with tooltip for additional context
  • Custom icons: Replace default info icon with custom icons
  • Flexible styling: Supports all MUI FormLabel props and sx styling
  • Theme integration: Uses theme variables for consistent styling

Dialog

Custom dialog components with close button and feedback variations. Includes a base CustomDialog with a close button positioned on the right, and a FeedbackDialog for success, error, and confirmation states.

Usage:

import { CustomDialog, FeedbackDialog } from '@undefine-ui/design-system';

// Basic CustomDialog with close button
<CustomDialog open={open} onClose={handleClose}>
  <Typography variant="h6">Custom Dialog Title</Typography>
  <Typography variant="body2">
    This is a custom dialog with a close button on the right.
  </Typography>
</CustomDialog>

// Success feedback dialog
<FeedbackDialog
  open={open}
  onClose={handleClose}
  image="/path/to/success-image.png"
  title="Board added successfully"
  description="Your new board has been created."
  actions={
    <>
      <Button variant="contained" fullWidth onClick={handleGoToBoard}>
        Go to board
      </Button>
      <Button variant="text" color="inherit" fullWidth onClick={handleClose}>
        Close
      </Button>
    </>
  }
/>

// Confirmation dialog with horizontal actions
<FeedbackDialog
  open={open}
  onClose={handleClose}
  title="Confirm deletion"
  description="Are you sure you want to delete this item?"
  actions={
    <Stack direction="row" spacing={1.5} sx={{ width: '100%' }}>
      <Button variant="outlined" fullWidth onClick={handleClose}>
        Cancel
      </Button>
      <Button variant="contained" color="error" fullWidth onClick={handleDelete}>
        Delete
      </Button>
    </Stack>
  }
  feedbackSlotProps={{
    actions: { flexDirection: 'row' }
  }}
/>

// Custom styling
<FeedbackDialog
  open={open}
  onClose={handleClose}
  title="Welcome aboard! 🎉"
  description="Your account has been created successfully."
  actions={
    <Button variant="contained" fullWidth>Get Started</Button>
  }
  feedbackSlotProps={{
    title: { fontSize: '1.5rem', color: 'primary.main' },
    description: { maxWidth: 280 }
  }}
/>

CustomDialog Props:

  • open - Controls dialog visibility (required)
  • onClose - Callback when close button is clicked
  • All MUI DialogProps are supported

FeedbackDialog Props:

  • open - Controls dialog visibility (required)
  • onClose - Callback when close button is clicked
  • image - URL for the feedback image
  • title - Title text to display
  • description - Description text below the title
  • actions - Action elements (buttons, etc.)
  • feedbackSlotProps - Custom styles for image, title, description, and actions slots

Features:

  • Close button: Positioned on the top-right for easy dismissal
  • Flexible content: CustomDialog accepts any children content
  • Feedback states: FeedbackDialog supports success, error, and confirmation patterns
  • Customizable: Style individual elements via feedbackSlotProps
  • Theme integration: Uses theme variables for consistent styling

Lightbox

A full-featured image gallery lightbox component with zoom, pan, navigation, thumbnails, and keyboard support. Perfect for image galleries, product showcases, and media viewers.

Usage:

import { Lightbox, useLightbox, type LightboxSlide } from '@undefine-ui/design-system';

// Define your slides
const slides: LightboxSlide[] = [
  {
    src: '/images/photo1.jpg',
    thumbnail: '/images/photo1-thumb.jpg',
    alt: 'Photo 1',
    title: 'Mountain Landscape',
    description: 'A beautiful mountain view at sunset'
  },
  {
    src: '/images/photo2.jpg',
    thumbnail: '/images/photo2-thumb.jpg',
    alt: 'Photo 2'
  }
];

// Using the useLightbox hook (recommended)
const MyGallery = () => {
  const lightbox = useLightbox(slides);

  return (
    <>
      <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 100px)', gap: 1 }}>
        {slides.map((slide, index) => (
          <Box
            key={index}
            component="img"
            src={slide.thumbnail}
            alt={slide.alt}
            onClick={() => lightbox.onOpen(index)}
            sx={{ cursor: 'pointer', width: 100, height: 75, objectFit: 'cover' }}
          />
        ))}
      </Box>
      <Lightbox
        open={lightbox.open}
        onClose={lightbox.onClose}
        slides={lightbox.slides}
        index={lightbox.selected}
        onIndexChange={lightbox.onSetSelected}
      />
    </>
  );
};

// Controlled usage
const [open, setOpen] = useState(false);
const [index, setIndex] = useState(0);

<Lightbox
  open={open}
  onClose={() => setOpen(false)}
  slides={slides}
  index={index}
  onIndexChange={setIndex}
/>

// Without thumbnails
<Lightbox
  open={open}
  onClose={handleClose}
  slides={slides}
  showThumbnails={false}
/>

// Without zoom functionality
<Lightbox
  open={open}
  onClose={handleClose}
  slides={slides}
  enableZoom={false}
/>

// Minimal UI (no thumbnails, no counter, no zoom)
<Lightbox
  open={open}
  onClose={handleClose}
  slides={slides}
  showThumbnails={false}
  showCounter={false}
  enableZoom={false}
/>

// Custom zoom levels
<Lightbox
  open={open}
  onClose={handleClose}
  slides={slides}
  zoomLevels={[0.5, 1, 6]} // [min, default, max]
/>

// Disable looping
<Lightbox
  open={open}
  onClose={handleClose}
  slides={slides}
  loop={false}
/>

Lightbox Props:

  • open - Controls lightbox visibility (required)
  • onClose - Callback when lightbox is closed (required)
  • slides - Array of slide objects LightboxSlide[] (required)
  • index - Current slide index (default: 0)
  • onIndexChange - Callback when index changes (index: number) => void
  • showThumbnails - Show thumbnail strip at bottom (default: true)
  • showCounter - Show slide counter in toolbar (default: true)
  • enableZoom - Enable zoom functionality (default: true)
  • loop - Enable infinite navigation (default: true)
  • zoomLevels - Zoom levels as [min, default, max] (default: [1, 1, 4])

LightboxSlide Interface:

interface LightboxSlide {
  src: string; // Full-size image URL (required)
  thumbnail?: string; // Thumbnail URL (falls back to src)
  alt?: string; // Alt text for accessibility
  title?: string; // Title displayed below image
  description?: string; // Description displayed below title
}

useLightbox Hook:

The useLightbox hook provides a convenient way to manage lightbox state:

const lightbox = useLightbox(slides);

// Returns:
{
  open: boolean;              // Whether lightbox is open
  selected: number;           // Current slide index
  slides: LightboxSlide[];    // Slides array
  onOpen: (index?: number) => void;  // Open at specific index
  onClose: () => void;        // Close lightbox
  onSetSelected: (index: number) => void; // Change slide
  setSlides: (slides: LightboxSlide[]) => void; // Update slides
}

Keyboard Navigation:

  • / - Navigate between slides
  • Escape - Close lightbox

Mouse/Touch Controls:

  • Scroll wheel - Zoom in/out when zoom is enabled
  • Double-click - Toggle between default and max zoom
  • Drag - Pan around when zoomed in
  • Click thumbnails - Jump to specific slide
  • Click arrows - Navigate to previous/next slide

Features:

  • Zoom & Pan: Mouse wheel zoom and drag-to-pan when zoomed
  • Keyboard navigation: Arrow keys and Escape support
  • Thumbnail strip: Quick navigation with visual thumbnails
  • Slide counter: Shows current position (e.g., "2 / 10")
  • Captions: Optional title and description per slide
  • Loop navigation: Seamlessly cycle through images
  • Loading states: Skeleton placeholders while images load
  • Responsive: Full-screen overlay with proper mobile handling
  • Accessible: Proper ARIA labels and keyboard support

Drawer

A drawer component with a fixed header containing a title and close button. The content area is scrollable while the header remains fixed. Built on top of MUI Drawer with enhanced styling and layout.

Usage:

import { CustomDrawer } from '@undefine-ui/design-system';

// Basic drawer
<CustomDrawer open={open} onClose={handleClose} title="Drawer Title">
  <Typography variant="body2">
    This is the drawer content. It scrolls independently while the header stays fixed.
  </Typography>
</CustomDrawer>

// Drawer with details layout
<CustomDrawer open={open} onClose={handleClose} title="Booking details">
  <Stack spacing={3}>
    <Box>
      <Typography variant="body2" color="text.body">Status</Typography>
      <Chip label="Confirm" color="success" size="small" />
    </Box>
    <Box>
      <Typography variant="body2" color="text.body">Booking ID</Typography>
      <Typography variant="subtitle2">#001</Typography>
    </Box>
    <Box>
      <Typography variant="body2" color="text.body">Amount</Typography>
      <Typography variant="subtitle2">N250,000</Typography>
    </Box>
  </Stack>
</CustomDrawer>

// Left-anchored drawer
<CustomDrawer
  open={open}
  onClose={handleClose}
  title="Left Drawer"
  anchor="left"
>
  <Typography variant="body2">
    This drawer opens from the left side.
  </Typography>
</CustomDrawer>

// Drawer with form
<CustomDrawer open={open} onClose={handleClose} title="Time slot">
  <Stack spacing={3}>
    <TextField label="Date" size="small" fullWidth />
    <TextField label="Time" size="small" fullWidth />
    <Button variant="contained" fullWidth>Save</Button>
  </Stack>
</CustomDrawer>

Props:

  • open - Controls drawer visibility (required)
  • title - Title text displayed in the fixed header
  • onClose - Callback when close button is clicked
  • anchor - Side from which the drawer appears (default: 'right')
  • slotProps - Custom props for MUI Drawer slots (paper, etc.)
  • All MUI DrawerProps are supported

Features:

  • Fixed header: Title and close button stay visible while scrolling
  • Scrollable content: Content area scrolls independently
  • Responsive width: Full width on mobile, 400px on larger screens
  • Multiple anchors: Supports left, right, top, and bottom positioning
  • Close button: XMark icon button for easy dismissal
  • Theme integration: Uses theme variables for consistent styling

Empty Content

A flexible empty state component for displaying placeholder content when data is unavailable. Perfect for empty lists, search results, or any state where content hasn't been loaded yet.

Usage:

import { EmptyContent } from '@undefine-ui/design-system';

// Basic usage
<EmptyContent title="No data" />

// With description
<EmptyContent
  title="No results found"
  description="Try adjusting your search or filter to find what you're looking for."
/>

// With image
<EmptyContent
  title="No notifications"
  description="You're all caught up!"
  imgUrl="/assets/icons/empty/ic-notification.svg"
/>

// Filled variant (with background)
<EmptyContent
  filled
  title="No items in cart"
  description="Add items to your cart to see them here."
/>

// With action button
<EmptyContent
  title="No projects yet"
  description="Create your first project to get started."
  action={
    <Button variant="contained">Create Project</Button>
  }
/>

// Custom styling with slotProps
<EmptyContent
  title="Custom Styled"
  description="Customize title and description styles."
  slotProps={{
    title: { color: 'primary.main', fontWeight: 700 },
    description: { color: 'text.secondary', maxWidth: 300 }
  }}
/>

Props:

  • title - Title text to display (default: 'No data')
  • description - Description text below the title
  • imgUrl - URL for an optional image to display
  • filled - Show filled background variant with border (default: false)
  • action - Optional action element (button, link, etc.)
  • slotProps - Custom styles for img, title, and description slots
  • sx - MUI sx prop for container styling
  • All StackProps from MUI are supported

Features:

  • Flexible layout: Centers content vertically and horizontally
  • Filled variant: Adds background color and dashed border
  • Image support: Display custom illustrations or icons
  • Action slot: Add buttons or links for user interaction
  • Customizable: Style individual elements via slotProps
  • Theme integration: Uses theme variables for consistent styling

Logo assets

The package exports two Logo components:

<Logo />

Renders logo images hosted on Cloudinary by default. The component automatically selects the appropriate variant based on props:

| Variant flag combo | Asset served | | -------------------------- | ----------------------------------- | | isFull={false} (default) | Single logo (60px width) | | isFull | Full logo with text (120px width) | | isWhite | White variant for dark backgrounds | | isBlack | Black variant for light backgrounds | | isWhite + isFull | White full logo | | isBlack + isFull | Black full logo |

All logo assets are served from Cloudinary and don't require any local files in your host app.

<Logo isFull isWhite href="/dashboard" />

Props:

  • isFull - Use full logo with text (default: false)
  • isWhite - Use white variant (default: false)
  • isBlack - Use black variant (default: false)
  • disableLink - Render without link wrapper (default: false)
  • href - Link destination (default: '/')
  • LinkComponent - Custom link component (default: 'a')
  • src - Override logo source URL
  • alt - Image alt text (default: 'Undefine UI logo')
  • sx - MUI sx prop for styling

<AnimatedLogo />

An animated SVG version of the logo perfect for splash screens and loading states. Features a smooth infinite animation sequence with staggered timing for visual interest.

import { AnimatedLogo } from '@undefine-ui/design-system';

<AnimatedLogo />;

The animated logo automatically plays on mount with:

  • Background fade-in
  • Left bars sliding in from the left
  • D letter scaling in with a bounce effect
  • Continuous infinite loop

Ideal for splash screens, loading overlays, or brand storytelling moments.

ApexCharts Styling

The design system provides styling utilities for ApexCharts integration, ensuring charts match the Define theme.

Installation:

pnpm add apexcharts react-apexcharts

Usage:

import ReactApexChart from 'react-apexcharts';
import { useTheme, styled } from '@mui/material/styles';
import { apexChartsStyles, getDefaultChartOptions } from '@undefine-ui/design-system';

// Create a styled wrapper
const ChartWrapper = styled('div')(({ theme }) => ({
  ...apexChartsStyles(theme)
}));

function LineChart() {
  const theme = useTheme();

  const chartOptions = {
    ...getDefaultChartOptions(theme),
    xaxis: {
      ...getDefaultChartOptions(theme).xaxis,
      categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']
    }
  };

  const chartSeries = [
    { name: 'Bookings', data: [10, 41, 35, 51, 49, 62, 69] },
    { name: 'Revenue', data: [20, 35, 50, 30, 45, 55, 40] }
  ];

  return (
    <ChartWrapper>
      <ReactApexChart type="line" series={chartSeries} options={chartOptions} height={350} />
    </ChartWrapper>
  );
}

Available exports:

  • apexChartsStyles(theme) - CSS styles to apply to a wrapper element (tooltips, legends, labels)
  • getDefaultChartOptions(theme) - Default ApexCharts options with theme colors, typography, and grid styling

Export surface

Everything is re-exported from src/index.ts. Key groups:

| Group | Exports | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Components | CopyButton, CustomDialog, CustomDrawer, EmptyContent, FeedbackDialog, HookForm helpers (Form, Field, RHFSwitch, etc.), Icon, Image, Lightbox, Logo, LoadingScreen, OTPInput, Table, Upload | | Hooks | useBoolean, useCopyToClipboard, useEventListener, useLightbox, useLocalStorage, useResponsive, useSettings, useSetState, useScrollOffsetTop, usePopover, useCountdown | | Contexts | SettingsProvider, SettingsContext, defaultSettings, SettingsValueProps | | Theme | ThemeProvider, createTheme, colorSchemes, components, palette, radius, shadows, customSpacing, utilities such as varAlpha, bgGradient, hideScrollX/Y | | Utilities | changeCase helpers, formatNumber, fullname-utils, generic helpers |

You can also import the theme pieces directly to compose your own MUI theme or to extend tokens in Storybook.

Customising the theme

  • Call createTheme(settings) to get the configured Theme object (useful for tests or SSR).
  • Theme tokens live under src/theme/core. If you need to override palette/typography/etc., spread the exports into your own extendTheme call.
  • updateCoreWithSettings and updateComponentsWithSettings are exported for advanced scenarios (e.g., you want to override the default settings object before rendering).

Scripts

  • pnpm build – bundle ESM/CJS output + .d.ts into dist/
  • pnpm storybook – run Storybook locally
  • pnpm build-storybook – static Storybook output
  • pnpm test – run Vitest (once specs are added)