Command Palette

Search for a command to run...

1.5k

Command Palette

Search for a command to run...

Components
PreviousNext

React Wheel Picker

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

Loading…
import type { WheelPickerOption } from "@/components/wheel-picker"
import {
  WheelPicker,
  WheelPickerWrapper,
} from "@/components/wheel-picker"
 
export default 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>
  )
}
 
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" },
]

Features

Built on top of React Wheel Picker.

  • Natural touch scrolling with smooth inertia, mouse drag and scroll for desktop.
  • Infinite loop scrolling.
  • Unstyled core for complete style customization.
  • Full keyboard navigation and type-ahead search.

Installation

$ pnpm dlx shadcn@latest add @ncdai/wheel-picker

Usage

import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@/components/wheel-picker"
const options: WheelPickerOption[] = [
  {
    label: "React",
    value: "react",
  },
  {
    label: "Vue",
    value: "vue",
  },
  {
    label: "Angular",
    value: "angular",
    disabled: true,
  },
  {
    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/wheel-picker"
import {
  WheelPicker,
  WheelPickerWrapper,
} from "@/components/wheel-picker"
 
export default 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>
  )
}
 
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" },
]

React Hook Form

Loading…
"use client"
 
import { zodResolver } from "@hookform/resolvers/zod"
import type { SubmitHandler } from "react-hook-form"
import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
 
import { Button } from "@/components/ui/button"
import {
  Field,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import type { WheelPickerOption } from "@/components/wheel-picker"
import {
  WheelPicker,
  WheelPickerWrapper,
} from "@/components/wheel-picker"
 
const formSchema = z.object({
  framework: z.string(),
})
 
type FormSchema = z.infer<typeof formSchema>
 
export default function WheelPickerFormDemo() {
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = 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-full rounded-md border p-4">
          <code>{JSON.stringify(values, null, 2)}</code>
        </pre>
      ),
      classNames: {
        content: "flex-1",
      },
    })
  }
 
  return (
    <form onSubmit={handleSubmit(onSubmit)} className="w-56 max-w-full">
      <FieldGroup>
        <Controller
          control={control}
          name="framework"
          render={({ field }) => (
            <Field data-invalid={!!errors.framework}>
              <FieldLabel>Framework</FieldLabel>
 
              <WheelPickerWrapper>
                <WheelPicker
                  options={options}
                  value={field.value}
                  onValueChange={field.onChange}
                />
              </WheelPickerWrapper>
 
              {errors.framework && (
                <FieldError>{errors.framework.message}</FieldError>
              )}
            </Field>
          )}
        />
        <Field>
          <Button type="submit">Submit</Button>
        </Field>
      </FieldGroup>
    </form>
  )
}
 
const options: WheelPickerOption[] = [
  {
    label: "Vite",
    value: "vite",
  },
  {
    label: "Laravel",
    value: "laravel",
    disabled: true,
  },
  {
    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",
  },
]