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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@cwncollab-org/mui-component-kit

v0.14.1

Published

> **Note**: This documentation was generated with the assistance of AI. While we strive for accuracy, please verify any code examples or implementation details in your specific use case.

Readme

Component Kit

Note: This documentation was generated with the assistance of AI. While we strive for accuracy, please verify any code examples or implementation details in your specific use case.

A React component library built with TypeScript and Vite. This package provides a set of reusable components built on top of Material-UI (MUI) and Tanstack Form for form handling.

Features

  • Built on Material-UI
  • Type-safe dialog management
  • Lazy loading support
  • Payload and result handling for dialogs
  • Form components with TanStack Form integration
  • Router-integrated tabs with URL synchronization

Usage Examples

Basic Dialog Usage

import { DialogsProvider, useDialogs } from '@cwncollab-org/component-kit'
import ExampleDialog from './examples/ExampleDialog'

function App() {
  return (
    <DialogsProvider>
      <AppPage />
    </DialogsProvider>
  )
}

function AppPage() {
  const { openDialog } = useDialogs()

  return (
    <Button
      variant='contained'
      onClick={() => openDialog(ExampleDialog)}
    >
      Open Basic Dialog
    </Button>
  )
}

ExampleDialog Implementation

import {
  Dialog,
  DialogContentText,
  DialogContent,
  DialogProps,
  DialogTitle,
} from '@mui/material'
import { DialogCloseButton } from '@cwncollab-org/component-kit'

export default function ExampleDialog({ open, onClose, ...rest }: DialogProps) {
  return (
    <Dialog open={open} onClose={onClose} {...rest}>
      <DialogTitle>Example Dialog</DialogTitle>
      <DialogCloseButton onClick={() => onClose?.({}, 'escapeKeyDown')} />
      <DialogContent>
        <DialogContentText>This is an example dialog</DialogContentText>
      </DialogContent>
    </Dialog>
  )
}

Note: The DialogCloseButton is an optional component that provides a close button (X) in the top-right corner of the dialog. It's positioned absolutely and styled to match Material-UI design patterns.

Dialog with Payload

import { DialogsProvider, useDialogs } from '@cwncollab-org/component-kit'
import ExampleDialogWithPayload from './examples/ExampleDialogWithPayload'

function AppPage() {
  const { openDialog } = useDialogs()
  const [name, setName] = useState('')

  return (
    <>
      <Input
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder='Enter your name'
      />
      <Button
        variant='contained'
        onClick={async () => {
          await openDialog(ExampleDialogWithPayload, { payload: { name } })
        }}
      >
        Open Dialog with Payload
      </Button>
    </>
  )
}

ExampleDialogWithPayload Implementation

import {
  Dialog,
  DialogTitle,
  DialogContentText,
  DialogContent,
} from '@mui/material'
import { DialogProps } from '@cwncollab-org/component-kit'

type Payload = {
  name: string
}

export default function ExampleDialogWithPayload({
  open,
  onClose,
  payload,
  ...rest
}: DialogProps<Payload>) {
  return (
    <Dialog open={open} onClose={onClose} {...rest}>
      <DialogTitle>Example Dialog</DialogTitle>
      <DialogContent>
        <DialogContentText>
          This is an example dialog with payload {JSON.stringify(payload)}
        </DialogContentText>
      </DialogContent>
    </Dialog>
  )
}

Dialog with Result

import { DialogsProvider, useDialogs } from '@cwncollab-org/component-kit'
import ExampleDialogWithResult from './examples/ExampleDialogWithResult'

function AppPage() {
  const { openDialog } = useDialogs()
  const [result, setResult] = useState('')

  return (
    <>
      <Button
        variant='contained'
        onClick={async () => {
          const result = await openDialog(ExampleDialogWithResult)
          setResult(JSON.stringify(result))
        }}
      >
        Open Dialog with Result
      </Button>
      <Typography variant='body1' sx={{ mt: 1 }}>
        Result: {result}
      </Typography>
    </>
  )
}

ExampleDialogWithResult Implementation

import {
  Dialog,
  DialogTitle,
  DialogContent,
  Input,
  DialogActions,
  Button,
} from '@mui/material'
import { DialogProps } from '@cwncollab-org/component-kit'
import { useState } from 'react'

type Payload = {
  name: string
}

type ResultData = {
  name: string
}

export default function ExampleDialogWithResult({
  open,
  onClose,
  payload,
  ...rest
}: DialogProps<Payload, ResultData>) {
  const [name, setName] = useState(payload?.name || '')
  return (
    <Dialog open={open} onClose={onClose} {...rest}>
      <DialogTitle>Example Dialog</DialogTitle>
      <DialogContent>
        <Input
          value={name}
          onChange={e => setName(e.target.value)}
          placeholder='Name'
        />
      </DialogContent>
      <DialogActions>
        <Button onClick={ev => onClose(ev, { success: true, data: { name } })}>
          Save
        </Button>
      </DialogActions>
    </Dialog>
  )
}

Lazy Loading Dialog

import { DialogsProvider, useDialogs } from '@cwncollab-org/component-kit'
import { lazy } from 'react'

const ExampleDialog2 = lazy(() => import('./examples/ExampleDialog2'))

function AppPage() {
  const { openDialog } = useDialogs()

  return (
    <Button
      variant='contained'
      onClick={() => openDialog(ExampleDialog2)}
    >
      Open Lazy Loaded Dialog
    </Button>
  )
}

ExampleDialog2 Implementation

import {
  Dialog,
  DialogTitle,
  DialogContentText,
  DialogContent,
  DialogProps,
} from '@mui/material'

export default function ExampleDialog2({
  open,
  onClose,
  ...rest
}: DialogProps) {
  return (
    <Dialog open={open} onClose={onClose} {...rest}>
      <DialogTitle>Example Dialog</DialogTitle>
      <DialogContent>
        <DialogContentText>This is an example dialog 2</DialogContentText>
      </DialogContent>
    </Dialog>
  )
}

Form Example with Zod Validation

Important: When using DatePicker or TimePicker components, you must wrap your application (or the specific form) with LocalizationProvider from @mui/x-date-pickers.

import { Box, Button, Paper, Stack, Typography, Radio, FormControlLabel } from '@mui/material'
import { LocalizationProvider } from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { useAppForm } from '@cwncollab-org/component-kit'
import { useState } from 'react'
import { z } from 'zod'

// Define your form schema with Zod
const formSchema = z.object({
  username: z.string()
    .min(1, 'Username is required')
    .max(20, 'Username must be less than 20 characters')
    .regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores'),
  email: z.string()
    .email('Invalid email address')
    .min(1, 'Email is required'),
  age: z.number()
    .min(18, 'You must be at least 18 years old')
    .max(120, 'Please enter a valid age'),
  subscribe: z.boolean()
    .refine(val => val === true, 'You must subscribe to continue'),
  role: z.string()
    .min(1, 'Please select a role'),
  priority: z.enum(['low', 'medium', 'high'])
    .refine(val => val !== undefined, 'Please select a priority'),
  birthDate: z.date()
    .min(new Date('1900-01-01'), 'Please enter a valid birth date')
    .max(new Date(), 'Birth date cannot be in the future'),
})

// Infer the form type from the schema
type FormValues = z.infer<typeof formSchema>

export function FormExample() {
  const [value, setValue] = useState<FormValues>({
    username: '',
    email: '',
    age: 18,
    subscribe: false,
    role: '',
    priority: undefined,
    birthDate: new Date(),
  })

  const form = useAppForm({
    defaultValues: {
      username: '',
      email: '',
      age: 18,
      subscribe: false,
      role: '',
      priority: undefined,
      birthDate: new Date(),
    },
    onSubmit: ({ value }) => {
      setValue(value as FormValues)
    },
    // Add Zod validation
    validators: { onChange: formSchema },
  })

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <Paper sx={{ p: 2 }}>
        <Box>
          <Typography>Form Example with Zod Validation</Typography>
        </Box>
        <Box
          component='form'
          sx={{ py: 2 }}
          onSubmit={e => {
            e.preventDefault()
            e.stopPropagation()
            form.handleSubmit()
          }}
        >
        <Stack spacing={2}>
          <form.AppField
            name='username'
            children={field => (
              <field.TextField 
                label='Username' 
                fullWidth 
              />
            )}
          />
          <form.AppField
            name='email'
            children={field => (
              <field.TextField 
                label='Email' 
                fullWidth
              />
            )}
          />
          <form.AppField
            name='age'
            children={field => (
              <field.TextField 
                label='Age' 
                type='number'
                fullWidth 
              />
            )}
          />
          <form.AppField
            name='birthDate'
            children={field => (
              <field.DatePicker
                label='Birth Date'
              />
            )}
          />
          <form.AppField
            name='role'
            children={field => (
              <field.Select
                label='Role'
                fullWidth
              >
                <MenuItem value=''>
                  <em>None</em>
                </MenuItem>
                <MenuItem value='admin'>Admin</MenuItem>
                <MenuItem value='user'>User</MenuItem>
              </field.Select>
            )}
          />
          <form.AppField
            name='subscribe'
            children={field => (
              <field.Checkbox 
                label='Subscribe to newsletter'
              />
            )}
          />
          <form.AppField
            name='priority'
            children={field => (
              <field.RadioGroup label='Priority'>
                <FormControlLabel
                  value='low'
                  control={<Radio />}
                  label='Low Priority'
                />
                <FormControlLabel
                  value='medium'
                  control={<Radio />}
                  label='Medium Priority'
                />
                <FormControlLabel
                  value='high'
                  control={<Radio />}
                  label='High Priority'
                />
              </field.RadioGroup>
            )}
          />
          <form.AppForm>
            <form.SubscribeButton type='submit' variant='contained'>
              Submit
            </form.SubscribeButton>
            <form.SubscribeButton
              type='reset'
              variant='outlined'
              onClick={() => form.reset()}
            >
              Reset
            </form.SubscribeButton>
          </form.AppForm>
          <Typography variant='body1'>{JSON.stringify(value)}</Typography>
        </Stack>
      </Box>
    </Paper>
    </LocalizationProvider>
  )
}

MultiSelect Component

The MultiSelect component provides a multiple selection dropdown with checkboxes, built on top of MUI's Select component and integrated with TanStack Form.

import { useAppForm } from '@cwncollab-org/component-kit'
import { z } from 'zod'

// Define your form schema
const formSchema = z.object({
  categories: z.array(z.string()).min(1, 'Please select at least one category'),
})

// Define your options
const categories = [
  { value: 'tech', label: 'Technology' },
  { value: 'sports', label: 'Sports' },
  { value: 'news', label: 'News' },
  { value: 'entertainment', label: 'Entertainment' },
  { value: 'science', label: 'Science' },
]

function MyForm() {
  const form = useAppForm({
    defaultValues: {
      categories: [],
    },
    validators: {
      onSubmit: formSchema,
    },
    onSubmit: ({ value }) => {
      console.log('Selected categories:', value.categories)
    },
  })

  return (
    <form.AppField
      name="categories"
      children={field => (
        <field.MultiSelect
          label="Categories"
          required
          options={categories}
          labelShrink
          size="small"
          fullWidth
          // Optional: Sort selected values by label or value
          sortSelected="label"
        />
      )}
    />
  )
}

MultiSelect Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | - | The label text for the select field | | labelShrink | boolean | false | Whether the label should shrink | | size | 'small' \| 'medium' | 'medium' | The size of the select field | | fullWidth | boolean | false | Whether the select should take full width | | options | Array<{ value: string, label: string }> \| string[] | [] | The options to display in the select | | sortSelected | 'label' \| 'value' \| false | false | Sort selected values by label or value | | slotProps | object | - | Props for underlying MUI components |

Autocomplete Component

The Autocomplete component provides a searchable dropdown with support for single and multiple selection, built on top of MUI's Autocomplete component and integrated with TanStack Form.

import { useAppForm } from '@cwncollab-org/component-kit'
import { z } from 'zod'

// Define your form schema
const formSchema = z.object({
  country: z.string().optional(),
  skills: z.array(z.string()).optional(),
})

// Define your options
const countries = [
  { value: 'us', label: 'United States' },
  { value: 'ca', label: 'Canada' },
  { value: 'uk', label: 'United Kingdom' },
  { value: 'de', label: 'Germany' },
  { value: 'fr', label: 'France' },
]

const skills = [
  { value: 'javascript', label: 'JavaScript' },
  { value: 'typescript', label: 'TypeScript' },
  { value: 'react', label: 'React' },
  { value: 'vue', label: 'Vue.js' },
  { value: 'angular', label: 'Angular' },
]

function MyForm() {
  const form = useAppForm({
    defaultValues: {
      country: undefined,
      skills: [],
    },
    validators: {
      onSubmit: formSchema,
    },
    onSubmit: ({ value }) => {
      console.log('Selected country:', value.country)
      console.log('Selected skills:', value.skills)
    },
  })

  return (
    <>
      {/* Single selection autocomplete */}
      <form.AppField
        name="country"
        children={field => (
          <field.Autocomplete
            label="Country"
            labelBehavior="shrink"
            size="small"
            fullWidth
            options={countries}
            placeholder="Select a country"
          />
        )}
      />

      {/* Multiple selection autocomplete */}
      <form.AppField
        name="skills"
        children={field => (
          <field.Autocomplete
            label="Skills"
            labelBehavior="shrink"
            size="small"
            fullWidth
            multiple
            options={skills}
            placeholder="Select skills"
          />
        )}
      />

      {/* SubscribeAutocomplete automatically disables during form submission */}
      <form.AppField
        name="country"
        children={field => (
          <field.SubscribeAutocomplete
            label="Country"
            labelBehavior="auto"
            size="small"
            fullWidth
            options={countries}
            placeholder="Select a country"
          />
        )}
      />
    </>
  )
}

Autocomplete Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | - | The label text for the autocomplete field | | labelBehavior | 'auto' \| 'shrink' \| 'static' | 'auto' | How the label should behave | | size | 'small' \| 'medium' | 'medium' | The size of the autocomplete field | | fullWidth | boolean | false | Whether the autocomplete should take full width | | options | Array<{ value: string, label: string }> \| string[] | [] | The options to display in the autocomplete | | multiple | boolean | false | Whether multiple values can be selected | | freeSolo | boolean | false | If true, the Autocomplete is free solo, meaning that the user input is not bound to provided options | | placeholder | string | - | Placeholder text when no option is selected | | slotProps | object | - | Props for underlying MUI components (autocomplete, textField, helperText) | | onChange | (value: string \| string[] \| null) => void | - | Callback fired when the value changes |

The Autocomplete component also accepts all standard MUI FormControl props except those that conflict with the managed props.

Label Behaviors:

  • 'auto': Default MUI behavior - label floats when focused or has value
  • 'shrink': Label is always in the shrunk (floating) position
  • 'static': Label appears as a static label above the input

SubscribeAutocomplete: The SubscribeAutocomplete component has the same props as Autocomplete but automatically disables the field when the form is submitting, providing better UX during form submission.

Material Router Table

The useMaterialRouterTable hook provides Material React Table integration with TanStack Router, enabling URL-synchronized table state including pagination, sorting, column visibility, and density settings.

import { useMaterialRouterTable } from '@cwncollab-org/component-kit'
import { MaterialReactTable } from 'material-react-table'
import { createFileRoute } from '@tanstack/react-router'

const columns = [
  {
    header: 'Name',
    accessorKey: 'name',
  },
  {
    header: 'Email',
    accessorKey: 'email',
  },
  {
    header: 'Role',
    accessorKey: 'role',
  },
]

const data = [
  { name: 'John Doe', email: '[email protected]', role: 'Admin' },
  { name: 'Jane Smith', email: '[email protected]', role: 'User' },
  // ... more data
]

function RouteComponent() {
  const table = useMaterialRouterTable({
    columns,
    data,
    initialState: {
      columnVisibility: {
        role: false, // Hide role column by default
      },
      pagination: { 
        pageIndex: 0, 
        pageSize: 20 
      },
      density: 'compact',
    },
  })

  return <MaterialReactTable table={table} />
}

export const Route = createFileRoute('/users')({
  component: RouteComponent,
})

useMaterialRouterTable Arguments

The hook accepts a single argument of type MRT_TableOptions<TData> with the following key properties:

| Property | Type | Description | |----------|------|-------------| | columns | MRT_ColumnDef<TData>[] | Column definitions for the table | | data | TData[] | Array of data objects to display | | initialState | object | Initial table state configuration | | initialState.pagination | { pageIndex: number, pageSize: number } | Initial pagination settings | | initialState.columnVisibility | Record<string, boolean> | Initial column visibility state | | initialState.density | 'compact' \| 'comfortable' \| 'spacious' | Initial table density | | initialState.sorting | Array<{ id: string, desc: boolean }> | Initial sorting configuration | | onPaginationChange | (state: MRT_PaginationState) => void | Optional pagination change handler | | onSortingChange | (state: MRT_SortingState) => void | Optional sorting change handler | | onDensityChange | (state: MRT_DensityState) => void | Optional density change handler | | onColumnVisibilityChange | (state: MRT_VisibilityState) => void | Optional column visibility change handler |

URL Synchronization: The hook automatically synchronizes the following table state with URL search parameters:

  • page - Current page number (1-based in URL, 0-based internally)
  • pageSize - Number of items per page
  • order - Column ID for sorting
  • desc - Sort direction (true for descending)
  • density - Table density setting
  • columns - Visible column keys (joined with -)

Features:

  • URL Persistence: Table state is preserved in the URL and survives page refreshes
  • Browser Navigation: Users can use back/forward buttons to navigate table states
  • Shareable URLs: Table states can be shared via URL
  • Type Safety: Full TypeScript support with generic data types
  • MRT Integration: Seamless integration with Material React Table features

The hook returns a configured Material React Table instance that can be passed directly to the MaterialReactTable component.

RadioGroup Component

The RadioGroup component provides radio button selection, built on top of MUI's FormControl and RadioGroup components and integrated with TanStack Form.

import { useAppForm } from '@cwncollab-org/component-kit'
import { Radio, FormControlLabel } from '@mui/material'
import { z } from 'zod'

// Define your form schema
const formSchema = z.object({
  priority: z.enum(['low', 'medium', 'high']),
})

// Define your options
const priorities = [
  { value: 'low', label: 'Low Priority' },
  { value: 'medium', label: 'Medium Priority' },
  { value: 'high', label: 'High Priority' },
]

function MyForm() {
  const form = useAppForm({
    defaultValues: {
      priority: undefined,
    },
    validators: {
      onSubmit: formSchema,
    },
    onSubmit: ({ value }) => {
      console.log('Selected priority:', value.priority)
    },
  })

  return (
    <form.AppField
      name="priority"
      children={field => (
        <field.RadioGroup label="Priority">
          {priorities.map(priority => (
            <FormControlLabel
              key={priority.value}
              value={priority.value}
              control={<Radio />}
              label={priority.label}
            />
          ))}
        </field.RadioGroup>
      )}
    />
    
    // Alternative: You can also use SubscribeRadioGroup which automatically
    // disables the radio group when the form is submitting
    <form.AppField
      name="priority"
      children={field => (
        <field.SubscribeRadioGroup label="Priority">
          {priorities.map(priority => (
            <FormControlLabel
              key={priority.value}
              value={priority.value}
              control={<Radio />}
              label={priority.label}
            />
          ))}
        </field.SubscribeRadioGroup>
      )}
    />
  )
}

RadioGroup Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | - | The label text for the radio group | | disabled | boolean | false | Whether the radio group is disabled | | children | ReactNode | - | Radio buttons (typically FormControlLabel components) |

The RadioGroup component also accepts all standard MUI RadioGroup props except name, value, and defaultValue which are managed by the form field.

DatePicker and TimePicker Components

The DatePicker and TimePicker components provide date and time selection functionality, built on top of MUI's DatePicker and TimePicker components from @mui/x-date-pickers, integrated with TanStack Form.

Important Requirements:

  • LocalizationProvider: You must wrap your application (or form) with LocalizationProvider from @mui/x-date-pickers
  • Date Adapter: You need to install and configure a date adapter (e.g., @mui/x-date-pickers/AdapterDayjs)
    • To use Buddhist Era date pickers, use AdapterDayjsWithBuddhistEra from this library as your date adapter.

Value Format Support: Both DatePicker and TimePicker support two value formats through the valueFormat prop:

  • 'adapter' (default): Uses the date adapter's native format (e.g., Dayjs objects)
  • 'Date': Converts to/from native JavaScript Date objects
import { useAppForm } from '@cwncollab-org/component-kit'
import { LocalizationProvider } from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { z } from 'zod'
import dayjs, { Dayjs } from 'dayjs'

// Define your form schema
const formSchema = z.object({
  // For native Date objects
  nativeDate: z.date(),
  nativeTime: z.date(),
  // For Dayjs objects (using adapter format)
  dayjsDate: z.custom<Dayjs>(),
})

function MyForm() {
  const form = useAppForm({
    defaultValues: {
      nativeDate: new Date(),
      nativeTime: new Date(),
      dayjsDate: dayjs(),
    },
    validators: {
      onSubmit: formSchema,
    },
    onSubmit: ({ value }) => {
      console.log('Form values:', value)
    },
  })

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <form.AppField
        name='nativeDate'
        children={field => (
          <field.SubscribeDatePicker
            label='Date (Native Date format)'
            valueFormat='Date'
            labelBehavior='shrink'
            fullWidth
            size='small'
          />
        )}
      />
      
      <form.AppField
        name='nativeTime'
        children={field => (
          <field.SubscribeTimePicker
            label='Time (Native Date format)'
            valueFormat='Date'
            labelBehavior='shrink'
            fullWidth
            size='small'
          />
        )}
      />
      
      <form.AppField
        name='dayjsDate'
        children={field => (
          <field.SubscribeDatePicker
            label='Date (Adapter format - Dayjs)'
            valueFormat='adapter'
            labelBehavior='shrink'
            fullWidth
            size='small'
          />
        )}
      />
    </LocalizationProvider>
  )
}

DatePicker Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | valueFormat | 'adapter' \| 'Date' | 'adapter' | Value format - adapter's native format or JavaScript Date | | labelBehavior | 'auto' \| 'shrink' \| 'static' | 'auto' | How the label should behave | | size | 'small' \| 'medium' | 'medium' | The size of the date picker field | | fullWidth | boolean | false | Whether the date picker should take full width | | required | boolean | false | Whether the date picker is required | | disabled | boolean | false | Whether the date picker is disabled | | slotProps | object | - | Props for underlying MUI components |

TimePicker Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | valueFormat | 'adapter' \| 'Date' | 'adapter' | Value format - adapter's native format or JavaScript Date | | labelBehavior | 'auto' \| 'shrink' \| 'static' | 'auto' | How the label should behave | | size | 'small' \| 'medium' | 'medium' | The size of the time picker field | | fullWidth | boolean | false | Whether the time picker should take full width | | required | boolean | false | Whether the time picker is required | | disabled | boolean | false | Whether the time picker is disabled | | slotProps | object | - | Props for underlying MUI components |

Both DatePicker and TimePicker accept all standard MUI DatePicker/TimePicker props except name, value, and defaultValue which are managed by the form field.

Label Behaviors:

  • 'auto': Default MUI behavior - label floats when focused or has value
  • 'shrink': Label is always in the shrunk (floating) position
  • 'static': Label appears as a static label above the input

Subscribe Components: The SubscribeDatePicker and SubscribeTimePicker components have the same props but automatically disable the field when the form is submitting, providing better UX during form submission.

MaskedTextField Component

The MaskedTextField component provides input masking functionality, built on top of MUI's TextField component and react-imask library, integrated with TanStack Form. This component is useful for formatting user input such as phone numbers, credit card numbers, social security numbers, and other structured data.

The component now supports the full react-imask API, allowing for advanced masking patterns including custom definitions, blocks, regular expressions, and more complex validation patterns.

import { useAppForm } from '@cwncollab-org/component-kit'
import { z } from 'zod'

// Define your form schema
const formSchema = z.object({
  phone: z.string().min(1, 'Phone number is required'),
  ssn: z.string().min(1, 'SSN is required'),
  creditCard: z.string().min(1, 'Credit card number is required'),
  zipCode: z.string().min(1, 'ZIP code is required'),
  date: z.string().min(1, 'Date is required'),
})

function MyForm() {
  const form = useAppForm({
    defaultValues: {
      phone: '',
      ssn: '',
      creditCard: '',
      zipCode: '',
      date: '',
    },
    validators: {
      onSubmit: formSchema,
    },
    onSubmit: ({ value }) => {
      console.log('Form values:', value)
    },
  })

  return (
    <>
      {/* Basic phone number mask */}
      <form.AppField
        name="phone"
        children={field => (
          <field.MaskedTextField
            mask="(000) 000-0000"
            label="Phone Number"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="(123) 456-7890"
          />
        )}
      />

      {/* Social Security Number mask */}
      <form.AppField
        name="ssn"
        children={field => (
          <field.MaskedTextField
            mask="000-00-0000"
            label="Social Security Number"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="123-45-6789"
          />
        )}
      />

      {/* Credit Card Number mask */}
      <form.AppField
        name="creditCard"
        children={field => (
          <field.MaskedTextField
            mask="0000 0000 0000 0000"
            label="Credit Card Number"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="1234 5678 9012 3456"
          />
        )}
      />

      {/* Date mask */}
      <form.AppField
        name="date"
        children={field => (
          <field.MaskedTextField
            mask="00/00/0000"
            label="Date (MM/DD/YYYY)"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="12/31/2023"
          />
        )}
      />

      {/* SubscribeMaskedTextField automatically disables during form submission */}
      <form.AppField
        name="phone"
        children={field => (
          <field.SubscribeMaskedTextField
            mask="(000) 000-0000"
            label="Phone Number"
            labelBehavior="auto"
            size="small"
            fullWidth
            placeholder="(123) 456-7890"
          />
        )}
      />
    </>
  )
}

Advanced Masking Examples

The MaskedTextField component now supports advanced react-imask features including custom definitions, blocks, regular expressions, and complex validation patterns:

import { useAppForm } from '@cwncollab-org/component-kit'
import { IMask } from 'react-imask'
import { z } from 'zod'

// Advanced masking patterns using react-imask features
function AdvancedMaskingExamples() {
  const form = useAppForm({
    defaultValues: {
      productCode: '',
      email: '',
      serialNumber: '',
      dateRange: '',
      time: '',
    },
    onSubmit: ({ value }) => {
      console.log('Advanced mask values:', value)
    },
  })

  return (
    <>
      {/* Custom definitions for product codes */}
      <form.AppField
        name="productCode"
        children={field => (
          <field.MaskedTextField
            mask="AA-####-**"
            definitions={{
              A: /[A-Z]/,          // Uppercase letters only
              '#': /[1-9]/,        // Digits 1-9 only
              '*': /[A-Z0-9]/,     // Alphanumeric uppercase
            }}
            label="Product Code"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="AB-1234-CD"
          />
        )}
      />

      {/* Email validation with regex pattern */}
      <form.AppField
        name="email"
        children={field => (
          <field.MaskedTextField
            mask={/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/}
            label="Email with Pattern Validation"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="[email protected]"
          />
        )}
      />

      {/* Serial number with mixed definitions */}
      <form.AppField
        name="serialNumber"
        children={field => (
          <field.MaskedTextField
            mask="SN-LLLNNN-CCC"
            definitions={{
              L: /[A-Z]/,      // Letters
              N: /[0-9]/,      // Numbers
              C: /[A-Z0-9]/,   // Alphanumeric
            }}
            label="Serial Number"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="SN-ABC123-XYZ"
          />
        )}
      />

      {/* Date range with blocks (advanced pattern) */}
      <form.AppField
        name="dateRange"
        children={field => (
          <field.MaskedTextField
            mask={Date}
            pattern="d{/}`m{/}`Y - d{/}`m{/}`Y*"
            blocks={{
              d: {
                mask: IMask.MaskedRange,
                from: 1,
                to: 31,
                maxLength: 2,
              },
              m: {
                mask: IMask.MaskedRange,
                from: 1,
                to: 12,
                maxLength: 2,
              },
              Y: {
                mask: IMask.MaskedRange,
                from: 1900,
                to: 9999,
                maxLength: 4,
              },
            }}
            label="Date Range"
            labelBehavior="shrink"
            size="small"
            fullWidth
            placeholder="DD/MM/YYYY - DD/MM/YYYY"
          />
        )}
      />

      {/* Time format with validation */}
      <form.AppField
        name="time"
        children={field => (
          <field.MaskedTextField
            mask="HH:MM"
            blocks={{
              HH: {
                mask: IMask.MaskedRange,
                from: 0,
                to: 23,
                maxLength: 2,
              },
              MM: {
                mask: IMask.MaskedRange,
                from: 0,
                to: 59,
                maxLength: 2,
              },
            }}
            label="Time (24h format)"
            labelBehavior="shrink"
            size="small"
            placeholder="14:30"
          />
        )}
      />
    </>
  )
}

MaskedTextField Props

The MaskedTextField component accepts all react-imask options as props, providing full access to the masking library's capabilities:

| Prop | Type | Default | Description | |------|------|---------|-------------| | mask | ReactMaskOpts['mask'] | - | The mask pattern - can be string, RegExp, function, or IMask class | | definitions | ReactMaskOpts['definitions'] | - | Custom character definitions for the mask pattern | | blocks | ReactMaskOpts['blocks'] | - | Block definitions for complex patterns with validation | | pattern | ReactMaskOpts['pattern'] | - | Pattern string when using blocks | | lazy | ReactMaskOpts['lazy'] | - | Show placeholder and fixed characters only on focus | | placeholderChar | ReactMaskOpts['placeholderChar'] | - | Character to show for unfilled mask positions | | label | string | - | The label text for the text field | | labelBehavior | 'auto' \| 'shrink' \| 'static' | 'auto' | How the label should behave | | size | 'small' \| 'medium' | 'medium' | The size of the text field | | fullWidth | boolean | false | Whether the text field should take full width | | placeholder | string | - | Placeholder text when the field is empty | | disabled | boolean | false | Whether the text field is disabled | | required | boolean | false | Whether the text field is required | | slotProps | object | - | Props for underlying MUI components |

The MaskedTextField component accepts all standard MUI TextField props and all react-imask options, providing full flexibility for complex masking scenarios.

Basic Mask Patterns:

  • 0 - any digit (0-9)
  • a - any letter (a-z, A-Z)
  • * - any character
  • [] - make input optional
  • {} - include fixed part in unmasked value
  • \ - escape character

Advanced Features:

  • Custom Definitions: Define your own character patterns using regular expressions
  • Blocks: Create complex validated input segments (like date ranges, time validation)
  • Regular Expressions: Use regex patterns for complex validation rules
  • IMask Classes: Access to all IMask functionality including MaskedRange, MaskedDate, etc.
  • Dynamic Masking: Conditional masks based on input content

Label Behaviors:

  • 'auto': Default MUI behavior - label floats when focused or has value
  • 'shrink': Label is always in the shrunk (floating) position
  • 'static': Label appears as a static label above the input

SubscribeMaskedTextField: The SubscribeMaskedTextField component has the same props as MaskedTextField but automatically disables the field when the form is submitting, providing better UX during form submission.

For more advanced masking scenarios, refer to the react-imask documentation as the MaskedTextField component provides full access to all react-imask features.

Router Tabs

The Router Tabs components provide tabbed navigation integrated with TanStack Router, allowing you to create tabs that are synced with the browser URL and support nested routing.

Basic Router Tabs Usage

import { Box } from '@mui/material'
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { RouterTab, RouterTabs } from '@cwncollab-org/component-kit'

export const Route = createFileRoute('/tabs-example')({
  component: RouteComponent,
})

function RouteComponent() {
  const match = Route.useMatch()
  
  return (
    <Box>
      <RouterTabs match={match}>
        <RouterTab value='/tabs-example/tab1' label='Tab 1' />
        <RouterTab value='/tabs-example/tab2' label='Tab 2' />
        <RouterTab value='/tabs-example/tab3' label='Tab 3' />
      </RouterTabs>
      <Outlet />
    </Box>
  )
}

Router Tabs with TabLabel Components

The TabLabel component allows you to add visual indicators like error states to your tabs:

import { RouterTab, RouterTabs, TabLabel } from '@cwncollab-org/component-kit'

function MyTabsComponent() {
  const match = Route.useMatch()
  const [hasError, setHasError] = useState(false)
  
  return (
    <RouterTabs match={match}>
      <RouterTab 
        value='/dashboard/overview' 
        label={<TabLabel label='Overview' />} 
      />
      <RouterTab 
        value='/dashboard/settings' 
        label={<TabLabel label='Settings' error={hasError} />} 
      />
      <RouterTab 
        value='/dashboard/profile' 
        label='Profile' 
      />
    </RouterTabs>
  )
}

Nested Router Tabs

Router Tabs support nested routing structures:

// Parent route: /tabs-example/tab3
function Tab3Component() {
  const match = Route.useMatch()
  
  return (
    <Box>
      <Typography variant='h6'>Tab 3 Content</Typography>
      <RouterTabs match={match}>
        <RouterTab value='/tabs-example/tab3/list' label='List View' />
        <RouterTab value='/tabs-example/tab3/123' label='Detail View' />
      </RouterTabs>
      <Outlet />
    </Box>
  )
}

RouterTabs Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | match | MakeRouteMatch | - | The route match object from TanStack Router used to determine active tab | | children | ReactNode | - | RouterTab components |

The RouterTabs component also accepts all standard MUI Tabs props except value which is automatically managed based on the current route.

RouterTab Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | value | ValidateToPath | - | The route path this tab should navigate to | | label | ReactNode | - | The label content for the tab (can be string or TabLabel component) |

The RouterTab component also accepts all standard MUI Tab props except value which is used for routing.

TabLabel Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | - | The text label for the tab | | error | boolean | false | Whether to show an error indicator (red dot) next to the label |

Features:

  • URL Synchronization: Tab selection is automatically synchronized with the browser URL
  • Nested Routing: Supports complex nested tab structures with child routes
  • Type Safety: Full TypeScript support with path validation
  • MUI Integration: Built on top of Material-UI Tabs with full styling support
  • Error Indicators: Visual error states with the TabLabel component

Common Dialog Patterns

Here are some common dialog patterns you can implement using the component kit:

Confirmation Dialog

import { useConfirmDialog } from '@cwncollab-org/component-kit'

function MyComponent() {
  const confirm = useConfirmDialog()

  const handleClick = async () => {
    const result = await confirm({
      title: 'Confirm',
      message: 'Are you sure you want to confirm?',
      confirmText: 'Confirm',
      cancelText: 'Cancel',
    })
    
    if (result.success) {
      // Proceed with the action
      console.log('User confirmed')
    } else {
      console.log('User cancelled')
    }
  }

  return (
    <Button onClick={handleClick}>
      Confirm Action
    </Button>
  )
}

Delete Confirmation Dialog

import { useConfirmDeleteDialog } from '@cwncollab-org/component-kit'

function MyComponent() {
  const confirmDelete = useConfirmDeleteDialog()

  const handleDelete = async () => {
    const result = await confirmDelete({
      title: 'Confirm Delete',
      message: 'Are you sure you want to delete this item?',
      confirmText: 'Delete',
      cancelText: 'Cancel',
    })

    if (result.success) {
      // Proceed with deletion
      console.log('Item deleted')
    } else {
      console.log('Deletion cancelled')
    }
  }

  return (
    <Button onClick={handleDelete} color="error">
      Delete Item
    </Button>
  )
}

Advanced Dialog Usage with Dialog Count

import { useDialogs, useConfirmDialog } from '@cwncollab-org/component-kit'

function DialogManager() {
  const { openDialog, dialogs } = useDialogs()
  const confirm = useConfirmDialog()

  return (
    <Stack spacing={2}>
      <Typography variant='body1'>
        Active dialogs: {dialogs.length}
      </Typography>
      
      <Button
        variant='contained'
        onClick={async () => {
          const result = await confirm({
            title: 'Multiple Dialogs',
            message: 'You can open multiple dialogs simultaneously.',
            confirmText: 'OK',
            cancelText: 'Cancel',
          })
          console.log(result)
        }}
      >
        Open Confirmation Dialog
      </Button>
    </Stack>
  )
}