Mantine Picker

Undolog

@gfazioli/mantine-picker

A Mantine component that allows you to create a picker effect with a list of elements.

Installation

yarn add @gfazioli/mantine-picker

After installation import package styles at the root of your application:

import '@gfazioli/mantine-picker/styles.css';

You can import styles within a layer @layer mantine-picker by importing @gfazioli/mantine-picker/styles.layer.css file.

import '@gfazioli/mantine-picker/styles.layer.css';

Usage

The Picker component allows you to create a picker effect with any list of items. It's inspired by the iOS picker and allows you to create a similar effect with any list of items.

  • You can drag up and down to select the item you want
  • You may also use the mouse wheel to scroll through the items
  • Of course, you can also click on the item to select it
  • Finally, you can use the keyboard to navigate through the items
RomeMilanNaplesBerlinMadridBarcelona
Value: Rome
Rotate y
Wheel sensitivity
Momentum
Deceleration rate
Item height
Perspective
Max rotation
Cylinder radius
Visible items
Min item opacity
Min item scale
Max blur amount
Mask height
Mask intensity
import { useState } from 'react';
import { Picker, type PickerProps } from '@gfazioli/mantine-picker';
import { Code, Stack } from '@mantine/core';

function Demo(props: PickerProps) {
  const [value, setValue] = useState('Rome');

  return (
    <Stack align="center" justify="space-between" h={300}>
      <Picker
        value={value}
        data={[
          'Rome',
          'Milan',
          'Naples',
          'Berlin',
          'Madrid',
          'Barcelona',
          'Paris',
          'London',
          'New York',
          'Los Angeles',
        ]}
        onChange={setValue}
      />
      <Code>Value: {value}</Code>
    </Stack>
  );
}

onChange

You can use the onChange prop to get the selected item. It is called whenever the selected value changes, allowing you to handle the selection logic in your application. Of course, you can also use the value prop to control the selected item.

OctoberNovemberDecemberJanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctober
Value: April
import { useState } from 'react';
import { Picker } from '@gfazioli/mantine-picker';
import { Code, Select, Stack } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string | null>(
    new Date().toLocaleString('en-US', { month: 'long' })
  );
  const data = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ];

  return (
    <Stack align="center" justify="space-between" h={400}>
      <Picker onChange={setValue} value={value} data={data} />
      <Code>Value: {value}</Code>
      <Select data={data} label="Select value" placeholder="Select value" onChange={setValue} />
    </Stack>
  );
}

renderItem

You can use the renderItem prop to customize the rendering of each item. This prop receives the item as arguments and should return the JSX to be rendered.

gray

violet

pink

brown

cyan

magenta

lime

olive

maroon

red

green

blue

yellow

orange

purple

black

white

gray

violet

gray
violet
pink
brown
cyan
magenta
lime
olive
maroon
red
green
blue
yellow
orange
purple
black
white
gray
violet
Value:
import { useState } from 'react';
import { Picker } from '@gfazioli/mantine-picker';
import { Badge, Code, ColorSwatch, Group, Stack, Text } from '@mantine/core';

function Demo(props: PickerProps) {
  const [value, setValue] = useState<string | number>('');

  const data = [
    'red',
    'green',
    'blue',
    'yellow',
    'orange',
    'purple',
    'black',
    'white',
    'gray',
    'violet',
    'pink',
    'brown',
    'cyan',
    'magenta',
    'lime',
    'olive',
    'maroon',
  ];

  function renderItem(item: string | number) {
    return (
      <Group>
        <ColorSwatch size={16} color={item as string} />
        <Text>{item}</Text>
      </Group>
    );
  }

  return (
    <Stack align="center" justify="space-between" h={400}>
      <Group>
        <Picker w={200} onChange={setValue} data={data} renderItem={renderItem} />
        <Picker
          w={200}
          onChange={setValue}
          data={data}
          renderItem={(item) => <Badge color={item as string}>{item}</Badge>}
        />
      </Group>
      <Code>Value: {value}</Code>
    </Stack>
  );
}

Left and Right Sections

You can use the leftSection and rightSection props to add custom content to the left and right sides of the picker. This is useful for adding icons, buttons, or any other content you want.

👉
56789100123456
👈
56789100123456
56789100123456
import { Picker } from '@gfazioli/mantine-picker';
import { Group, Stack } from '@mantine/core';

function Demo(props: PickerProps) {
  const data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  return (
    <Stack>
      <Group justify="space-between">
        <Picker w={200} data={data} leftSection="👉" />
        <Picker w={200} data={data} rightSection="👈" />
        <Picker w={200} data={data} leftSection="↑" rightSection="↓" />
      </Group>
    </Stack>
  );
}

As you can see, by using leftSection and rightSection props you can add any content you want to the left and right sides of the picker. In these cases the sections are fixed and do not scroll with the items. They will also inside the picker.

Alternatively, you can use a simple <Group/> component to add any left (center) and right section you want.

👉

56789100123456

👈

56789100123456

:

56789100123456
import { Picker } from '@gfazioli/mantine-picker';
import { Group, Stack, Text } from '@mantine/core';

function Demo(props: PickerProps) {
  const data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  return (
    <Stack>
      <Group justify="space-between">
        <Group>
          <Text>👉</Text>
          <Picker w={200} data={data} />
          <Text>👈</Text>
        </Group>
        <Group>
          <Picker w={100} data={data} />
          <Text>:</Text>
          <Picker w={100} data={data} />
        </Group>
      </Group>
    </Stack>
  );
}

Styles the Picker

You can change the default styles of the items by using the same props you use in the <Text/> component.

PhiladelphiaSan AntonioSan DiegoDallasSan JoseNew YorkLos AngelesChicagoHoustonPhoenixPhiladelphia
PhiladelphiaSan AntonioSan DiegoDallasSan JoseNew YorkLos AngelesChicagoHoustonPhoenixPhiladelphia
PhiladelphiaSan AntonioSan DiegoDallasSan JoseNew YorkLos AngelesChicagoHoustonPhoenixPhiladelphia
import { Picker } from '@gfazioli/mantine-picker';
import { Group, Stack } from '@mantine/core';

function Demo() {
  const data = [
    'New York',
    'Los Angeles',
    'Chicago',
    'Houston',
    'Phoenix',
    'Philadelphia',
    'San Antonio',
    'San Diego',
    'Dallas',
    'San Jose',
  ];

  return (
    <Stack>
      <Group justify="space-between">
        <Picker w={200} data={data} tt="uppercase" />
        <Picker w={200} data={data} size="xl" />
        <Picker
          w={200}
          data={data}
          variant="gradient"
          gradient={{ from: 'blue', to: 'red', deg: 90 }}
          style={{
            textShadow: '0 4px 2px rgba(0, 0, 0, 0.4)',
          }}
        />
      </Group>
    </Stack>
  );
}

readOnly

In addition to the disabled prop, you can use the readOnly prop to make the picker read-only. This will prevent the user from changing the selected item, but will still allow you to change it programmatically. It can be useful when you want to display the selected item without allowing the user to change it.

56789012345
012345
import { useState } from 'react';
import { Picker } from '@gfazioli/mantine-picker';
import { Button, Group, Stack } from '@mantine/core';
import { useInterval } from '@mantine/hooks';

function Demo() {
  const length = 10;
  const data = Array.from({ length }, (_, i) => i);

  const [seconds, setSeconds] = useState(0);
  const interval = useInterval(() => {
    setSeconds((s) => (s === length - 1 ? 0 : s + 1));
  }, 1000);

  const pickerProps: PickerProps = {
    w: 60,
    withDividers: false,
    withHighlight: false,
    withMask: false,
    itemHeight: 24,
    readOnly: true,
    data,
  };

  return (
    <Stack align="center" justify="space-between" h={200}>
      <Group>
        <Picker
          {...pickerProps}
          maxBlurAmount={1.5}
          minItemOpacity={0.5}
          visibleItems={3}
          value={seconds}
        />
        <Picker
          {...pickerProps}
          loop={false}
          c="red"
          minItemScale={0.1}
          visibleItems={1}
          animationDuration={700}
          value={seconds}
        />
      </Group>

      <Button onClick={interval.toggle}>{interval.active ? 'Stop' : 'Start'}</Button>
    </Stack>
  );
}

Example: Time Picker

The Picker component can be used as a Time Picker. You can use the renderItem prop to customize the rendering of each item and the onChange prop to handle the selection logic. In this example, we will create a simple time picker that allows you to select the hour and minute.

🕑

04050607080910111213141516171819202122230001020304

:

01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758590001

min

Selected time: 16:31

10110001020304050607080910

:

01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758590001
ampm

Selected time: 04:31 pm

import { useState } from 'react';
import { Picker } from '@gfazioli/mantine-picker';
import { Group, Stack, Text } from '@mantine/core';

function Demo() {
  const initialHour12 = +(new Date().getHours() as number) % 12;

  const [hours24, setHours24] = useState(new Date().getHours().toString());
  const [hours12, setHours12] = useState(
    initialHour12 < 10 ? `0${initialHour12}` : `${initialHour12}`
  );
  const [minutes24, setMinutes24] = useState(new Date().getMinutes());
  const [minutes12, setMinutes12] = useState(new Date().getMinutes());
  const [amPm, setAmPm] = useState(new Date().getHours() >= 12 ? 'pm' : 'am');

  const hours24Data = Array.from({ length: 24 }, (_, i) => (i < 10 ? `0${i}` : `${i}`));
  const hours12Data = Array.from({ length: 12 }, (_, i) => (i < 10 ? `0${i}` : `${i}`));
  const minutesData = Array.from({ length: 60 }, (_, i) => (i < 10 ? `0${i}` : i));

  const pickerProps: Omit<PickerProps, 'data'> = {
    w: 50,
    withDividers: false,
    withHighlight: false,
    loop: true,
    maxRotation: 90,
    itemHeight: 30,
    visibleItems: 5,
    withMask: false,
  };

  return (
    <Group justify="center" grow>
      <Stack align="center" justify="space-between" h={200}>
        <Group gap={0}>
          <Text>🕑</Text>
          <Picker
            {...pickerProps}
            rotateY={-10}
            data={hours24Data}
            value={hours24}
            onChange={setHours24}
          />
          <Text>:</Text>
          <Picker
            {...pickerProps}
            rotateY={10}
            data={minutesData}
            value={minutes24}
            onChange={setMinutes24}
          />
          <Text>min</Text>
        </Group>
        <Text>Selected time: {`${hours24}:${minutes24}`}</Text>
      </Stack>

      <Stack align="center" justify="space-between" h={200}>
        <Group gap={0}>
          <Picker
            {...pickerProps}
            rotateY={-10}
            data={hours12Data}
            value={hours12}
            onChange={setHours12}
          />
          <Text>:</Text>
          <Picker
            {...pickerProps}
            rotateY={10}
            data={minutesData}
            value={minutes12}
            onChange={setMinutes12}
          />
          <Picker
            {...pickerProps}
            rotateY={10}
            data={['am', 'pm']}
            loop={false}
            value={amPm}
            onChange={setAmPm}
          />
        </Group>
        <Text>Selected time: {`${hours12}:${minutes12} ${amPm}`}</Text>
      </Stack>
    </Group>
  );
}

Example: Date Picker

The Picker component can be used as a Date Picker.

293031010203040506070809101112131415161718192021222324252627282930
OctoberNovemberDecemberJanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctober
199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203019701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995

Selected date: 14 April 2025

import { useState } from 'react';
import { Picker, PickerProps } from '@gfazioli/mantine-picker';
import { Group, Stack, Text } from '@mantine/core';

function DatePicker() {
  const days = Array.from({ length: 31 }, (_, i) => (i < 9 ? `0${i + 1}` : `${i + 1}`));
  const months = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ];
  // years start from 1970 to 2030
  const years = Array.from({ length: 61 }, (_, i) => (i + 1970).toString());

  const today =
    (new Date().getDate() as number) < 10 ? `0${new Date().getDate()}` : new Date().getDate();

  const [day, setDay] = useState(today.toString());
  const [month, setMonth] = useState(months[new Date().getMonth()]);
  const [year, setYear] = useState(new Date().getFullYear().toString());

  const pickerProps: Omit<PickerProps, 'data'> = {
    withDividers: false,
    withHighlight: false,
    loop: true,
    maxRotation: 90,
    itemHeight: 30,
    visibleItems: 5,
    withMask: false,
    cylinderRadius: 3,
  };

  return (
    <Stack align="center" justify="space-between" h={200}>
      <Group gap={0}>
        <Picker {...pickerProps} w={60} value={day} data={days} rotateY={-10} onChange={setDay} />
        <Picker {...pickerProps} w={80} value={month} data={months} onChange={setMonth} />
        <Picker {...pickerProps} w={60} value={year} data={years} rotateY={10} onChange={setYear} />
      </Group>
      <Text>Selected date: {`${day} ${month} ${year}`}</Text>
    </Stack>
  );
}

Example: Slot Machine

As funny as it may sound, the Picker component can be used to create a slot machine effect.

🍌🍍🥝🍏🍎🍐🍑🍒🍋🍊🍉🍇🍓🍈🍌
🍌🍍🥝🍏🍎🍐🍑🍒🍋🍊🍉🍇🍓🍈🍌
🍌🍍🥝🍏🍎🍐🍑🍒🍋🍊🍉🍇🍓🍈🍌

🎉 Jackpot! 🎉

import { useState } from 'react';
import { Picker } from '@gfazioli/mantine-picker';
import { Button, Group, Paper, Stack, Text } from '@mantine/core';
import { MantineDemo } from '@mantinex/demo';

function SlotMachine() {
  const slotMachineData = [
    '🍒',
    '🍋',
    '🍊',
    '🍉',
    '🍇',
    '🍓',
    '🍈',
    '🍌',
    '🍍',
    '🥝',
    '🍏',
    '🍎',
    '🍐',
    '🍑',
  ];

  const pickerNumber = 3;
  const [values, setValues] = useState(Array(pickerNumber).fill(slotMachineData[0]));
  const [spinning, setSpinning] = useState(false);

  const pickerSize = 80;
  const groupGap = 4;
  const paperPadding = 4;
  const paperWidth = pickerSize * pickerNumber + paperPadding * 2 + groupGap * pickerNumber;

  const spin = () => {
    setSpinning(true);
    const newValues = Array.from(
      { length: pickerNumber },
      () => slotMachineData[Math.floor(Math.random() * slotMachineData.length)]
    );

    setValues(newValues);
    setSpinning(false);
  };

  return (
    <Stack align="center" justify="space-between" h={400}>
      <Paper radius={16} withBorder w={paperWidth} p={paperPadding}>
        <Group gap={groupGap}>
          {Array.from({ length: pickerNumber }).map((_, index) => (
            <Picker
              key={index}
              w={pickerSize}
              loop
              withMask
              data={slotMachineData}
              value={values[index]}
              readOnly
              itemHeight={50}
              visibleItems={5}
              withHighlight={false}
              maxBlurAmount={8}
              maskHeight={20}
              size="32px"
            />
          ))}
        </Group>
      </Paper>
      <Button onClick={spin} disabled={spinning}>
        {spinning ? 'Spinning...' : 'Spin'}
      </Button>
      <Text>{values.every((val, _, arr) => val === arr[0]) ? '🎉 Jackpot! 🎉' : 'Try Again!'}</Text>
    </Stack>
  );
}