Command Palette

Search for a command to run...

1.2k
Blog
PreviousNext

React Wheel Picker

iOS-like wheel picker for React with smooth inertia scrolling and infinite loop support.

Loading...
import type { WheelPickerOption } from "@/components/ncdai/wheel-picker";
import { WheelPicker, WheelPickerWrapper } from "@/components/ncdai/wheel-picker";
 
const createArray = (length: number, add = 0): WheelPickerOption<number>[] =>
  Array.from({ length }, (_, i) => {
    const value = i + add;
    return {
      label: value.toString().padStart(2, "0"),
      value: value,
    };
  });
 
const hourOptions = createArray(12, 1);
const minuteOptions = createArray(60);
const meridiemOptions: WheelPickerOption[] = [
  { label: "AM", value: "AM" },
  { label: "PM", value: "PM" },
];
 
export function WheelPickerDemo() {
  return (
    <div className="w-56">
      <WheelPickerWrapper>
        <WheelPicker options={hourOptions} defaultValue={9} infinite />
        <WheelPicker options={minuteOptions} defaultValue={41} infinite />
        <WheelPicker options={meridiemOptions} defaultValue="AM" />
      </WheelPickerWrapper>
    </div>
  );
}

About

The Wheel Picker component is built on top of React Wheel Picker.

  • 📱 Natural touch scrolling with smooth inertia effect
  • 🖱️ Mouse drag and scroll support for desktop
  • 🔄 Infinite loop scrolling
  • 🎨 Unstyled components for complete style customization
  • ⚡️ Easy installation via shadcn CLI

Installation

pnpm dlx shadcn add @ncdai/wheel-picker

Usage

import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@/components/ncdai/wheel-picker";
const options: WheelPickerOption[] = [
  {
    label: "React",
    value: "react",
  },
  {
    label: "Vue",
    value: "vue",
  },
  {
    label: "Angular",
    value: "angular",
  },
  {
    label: "Svelte",
    value: "svelte",
  },
];
 
export function WheelPickerDemo() {
  const [value, setValue] = useState("react");
 
  return (
    <WheelPickerWrapper>
      <WheelPicker options={options} value={value} onValueChange={setValue} />
    </WheelPickerWrapper>
  );
}

See the React Wheel Picker documentation for more information.

Examples

Multiple Pickers, Infinite Loop

Loading...
import type { WheelPickerOption } from "@/components/ncdai/wheel-picker";
import { WheelPicker, WheelPickerWrapper } from "@/components/ncdai/wheel-picker";
 
const createArray = (length: number, add = 0): WheelPickerOption<number>[] =>
  Array.from({ length }, (_, i) => {
    const value = i + add;
    return {
      label: value.toString().padStart(2, "0"),
      value: value,
    };
  });
 
const hourOptions = createArray(12, 1);
const minuteOptions = createArray(60);
const meridiemOptions: WheelPickerOption[] = [
  { label: "AM", value: "AM" },
  { label: "PM", value: "PM" },
];
 
export function WheelPickerDemo() {
  return (
    <div className="w-56">
      <WheelPickerWrapper>
        <WheelPicker options={hourOptions} defaultValue={9} infinite />
        <WheelPicker options={minuteOptions} defaultValue={41} infinite />
        <WheelPicker options={meridiemOptions} defaultValue="AM" />
      </WheelPickerWrapper>
    </div>
  );
}

React Hook Form

Loading...
"use client";
 
import { zodResolver } from "@hookform/resolvers/zod";
import type { SubmitHandler } from "react-hook-form";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
 
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import type { WheelPickerOption } from "@/components/ncdai/wheel-picker";
import { WheelPicker, WheelPickerWrapper } from "@/components/ncdai/wheel-picker";
 
const formSchema = z.object({
  framework: z.string(),
});
 
type FormSchema = z.infer<typeof formSchema>;
 
const options: WheelPickerOption[] = [
  {
    label: "Vite",
    value: "vite",
  },
  {
    label: "Laravel",
    value: "laravel",
  },
  {
    label: "React Router",
    value: "react-router",
  },
  {
    label: "Next.js",
    value: "nextjs",
  },
  {
    label: "Astro",
    value: "astro",
  },
  {
    label: "TanStack Start",
    value: "tanstack-start",
  },
  {
    label: "TanStack Router",
    value: "tanstack-router",
  },
  {
    label: "Gatsby",
    value: "gatsby",
  },
];
 
export function WheelPickerFormDemo() {
  const form = useForm<FormSchema>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      framework: "nextjs",
    },
  });
 
  const onSubmit: SubmitHandler<FormSchema> = (values) => {
    toast("You submitted the following values:", {
      description: (
        <pre className="mt-2 w-80 rounded-lg bg-zinc-950 p-4">
          <code className="text-white">{JSON.stringify(values, null, 2)}</code>
        </pre>
      ),
    });
  };
 
  return (
    <Form {...form}>
      <form
        onSubmit={form.handleSubmit(onSubmit)}
        className="w-56 max-w-full space-y-4"
      >
        <FormField
          control={form.control}
          name="framework"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Framework</FormLabel>
 
              <FormControl>
                <WheelPickerWrapper>
                  <WheelPicker
                    options={options}
                    value={field.value}
                    onValueChange={field.onChange}
                  />
                </WheelPickerWrapper>
              </FormControl>
 
              <FormMessage />
            </FormItem>
          )}
        />
 
        <div className="flex justify-center">
          <Button type="submit">Submit</Button>
        </div>
      </form>
    </Form>
  );
}