Skip to main content

Overview

CellSelector displays options as a grid of clickable cells with icons, titles, and optional subtitles. It provides visual feedback with hover states, selection highlights, and supports disabled states.

Basic Usage

import { CellSelector } from '@adoptaunabuelo/react-components';
import { useState } from 'react';
import { Home, Briefcase, Users } from 'lucide-react';

function App() {
  const [selected, setSelected] = useState([]);

  const options = [
    {
      id: '1',
      title: 'Home',
      subtitle: 'Personal space',
      icon: <Home size={24} />
    },
    {
      id: '2',
      title: 'Work',
      subtitle: 'Office',
      icon: <Briefcase size={24} />
    },
    {
      id: '3',
      title: 'Team',
      subtitle: 'Shared',
      icon: <Users size={24} />
    }
  ];

  return (
    <CellSelector
      type="single"
      options={options}
      selectedOptions={selected}
      onChange={setSelected}
    />
  );
}

Selection Modes

Single Selection

Only one cell can be selected at a time (radio behavior).
<CellSelector
  type="single"
  options={[
    { id: 'option1', title: 'Option 1', icon: <Icon1 /> },
    { id: 'option2', title: 'Option 2', icon: <Icon2 /> },
    { id: 'option3', title: 'Option 3', icon: <Icon3 /> }
  ]}
  selectedOptions={selected}
  onChange={(items) => {
    // items will always contain exactly one item
    setSelected(items);
  }}
/>

Multiple Selection

Multiple cells can be selected simultaneously (checkbox behavior).
<CellSelector
  type="multiple"
  options={[
    { id: 'react', title: 'React', icon: <ReactIcon /> },
    { id: 'vue', title: 'Vue', icon: <VueIcon /> },
    { id: 'angular', title: 'Angular', icon: <AngularIcon /> }
  ]}
  selectedOptions={selectedFrameworks}
  onChange={(items) => {
    // items contains array of all selected options
    setSelectedFrameworks(items);
  }}
/>

With Subtitles

Add descriptive text below each cell.
import { Cloud, Server, Database } from 'lucide-react';

<CellSelector
  type="single"
  options={[
    {
      id: 'saas',
      title: 'SaaS',
      subtitle: 'Software as a Service',
      icon: <Cloud size={28} />
    },
    {
      id: 'paas',
      title: 'PaaS',
      subtitle: 'Platform as a Service',
      icon: <Server size={28} />
    },
    {
      id: 'iaas',
      title: 'IaaS',
      subtitle: 'Infrastructure as a Service',
      icon: <Database size={28} />
    }
  ]}
  selectedOptions={serviceType}
  onChange={setServiceType}
/>

Disabled Options

Disable specific cells to prevent selection.
<CellSelector
  type="multiple"
  options={[
    {
      id: 'basic',
      title: 'Basic',
      subtitle: 'Free',
      icon: <BasicIcon />
    },
    {
      id: 'pro',
      title: 'Pro',
      subtitle: '$29/month',
      icon: <ProIcon />,
      disabled: true  // Grayed out and not clickable
    },
    {
      id: 'enterprise',
      title: 'Enterprise',
      subtitle: 'Contact sales',
      icon: <EnterpriseIcon />,
      disabled: true
    }
  ]}
  selectedOptions={plan}
  onChange={setPlan}
/>

Design Variants

// Design 1: No border (default)
<CellSelector
  design="design1"
  options={options}
  selectedOptions={selected}
  onChange={setSelected}
/>

// Design 2: With border
<CellSelector
  design="design2"
  options={options}
  selectedOptions={selected}
  onChange={setSelected}
/>

Props

options
Array<OptionProps>
required
Array of cell options:
{
  id: string;           // Unique identifier
  title: string;        // Main text displayed in cell
  subtitle?: string;    // Optional text below cell
  icon?: ReactElement;  // Icon displayed in cell
  disabled?: boolean;   // Disable interaction
}
type
'single' | 'multiple'
default:"single"
Selection mode:
  • single: Only one cell can be selected (radio behavior)
  • multiple: Multiple cells can be selected (checkbox behavior)
selectedOptions
Array<OptionProps>
Currently selected options (controlled component pattern).
onChange
(items: Array<OptionProps>) => void
Callback fired when selection changes. Receives array of selected options.
onClick
(item: OptionProps) => void
Callback fired when an individual cell is clicked. Called before onChange.
design
'design1' | 'design2'
default:"design1"
Visual design variant:
  • design1: No border on unselected cells
  • design2: Border on all cells
style
CSSProperties
Custom styles applied to the container.
cellStyle
CSSProperties
Custom styles applied to each cell.

Advanced Examples

With Custom Styling

<CellSelector
  type="multiple"
  options={options}
  selectedOptions={selected}
  onChange={setSelected}
  style={{
    gap: '24px',
    justifyContent: 'flex-start'
  }}
  cellStyle={{
    backgroundColor: '#f8f9fa',
    borderRadius: '16px'
  }}
/>

With Click Tracking

const handleCellClick = (item: OptionProps) => {
  console.log('Cell clicked:', item.id);
  trackEvent('cell_selection', { cellId: item.id });
};

<CellSelector
  type="single"
  options={options}
  selectedOptions={selected}
  onClick={handleCellClick}
  onChange={setSelected}
/>

Dynamic Options

const [options, setOptions] = useState([]);

useEffect(() => {
  fetchCategories().then(categories => {
    const cellOptions = categories.map(cat => ({
      id: cat.id,
      title: cat.name,
      subtitle: `${cat.count} items`,
      icon: <CategoryIcon type={cat.type} />,
      disabled: cat.count === 0
    }));
    setOptions(cellOptions);
  });
}, []);

<CellSelector
  type="multiple"
  options={options}
  selectedOptions={selected}
  onChange={setSelected}
/>

Conditional Rendering

function ProductSelector() {
  const [selected, setSelected] = useState([]);
  const [showDetails, setShowDetails] = useState(false);

  return (
    <>
      <CellSelector
        type="single"
        options={products}
        selectedOptions={selected}
        onChange={(items) => {
          setSelected(items);
          setShowDetails(items.length > 0);
        }}
      />
      {showDetails && (
        <ProductDetails product={selected[0]} />
      )}
    </>
  );
}

Behavior Notes

  • Single selection: Clicking same cell deselects it, then selects the new one
  • Multiple selection: Clicking toggles selection on/off
  • Disabled cells: Cannot be clicked, reduced opacity (0.3)
  • Hover effect: Background color changes on hover (except disabled cells)
  • Active state: Scale down to 0.95 on click for tactile feedback
  • Grid layout: Uses flex-wrap with justify-content: space-around
  • Cell size: Each cell is 95px min-width, 72px height

Styling Notes

  • Selected cells have 2px border in primary color
  • Unselected cells have 1px border (design2) or no border (design1)
  • Hover background: Color.line.soft for unselected, Color.background.primaryLow for selected
  • Disabled cells: 30% opacity with default cursor
  • Cell spacing: 4px margin around each cell
  • Title font: Poppins, 14px, medium weight
  • Subtitle font: 14px with centered alignment

Accessibility

  • Each cell has a unique role attribute with cell index
  • Container has role="container" attribute
  • Visual focus states on hover and active
  • Disabled state prevents interaction
  • Icons should have appropriate alt text or aria-labels

Build docs developers (and LLMs) love