Why Use It
- Best for time pickers, numeric selectors, unit choosers, and any slot-machine style control.
- Exposes the container shell and the wheel column separately, so you can compose one column or several.
- Preserves the glossy selected row and shell framing while leaving option data and column layout in your control.
- Supports both infinite looping and finite lists with disabled options.
Installation
Install the registry block, then compose the shell and one or more columns in your own picker layout.
$ bunx --bun shadcn@latest add https://sabraman.ru/r/legacy-wheel-picker.jsonQuick Start
Start with one centered column. It is the simplest pattern and the right base for most finite selectors.
"use client";
import { useState } from "react";
import {
LegacyPickerColumn,
LegacyPickerContainer,
} from "@/components/legacy-wheel-picker";
const frameworkOptions = [
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular", disabled: true },
{ label: "Svelte", value: "svelte" },
];
export function LegacyBasicPickerDemo() {
const [value, setValue] = useState("react");
return (
<div className="flex w-full max-w-[320px] flex-col items-center gap-6">
<LegacyPickerContainer className="w-full">
<LegacyPickerColumn
align="center"
fontSize={22}
onValueChange={setValue}
options={frameworkOptions}
value={value}
/>
</LegacyPickerContainer>
</div>
);
}Examples
Single Column
A single-column framework picker for the default copy-first setup.
- Svelte
- React
- Vue
- Angular
- Svelte
- React
- Vue
- Angular
- Svelte
- React
"use client";
import { useState } from "react";
import {
LegacyPickerColumn,
LegacyPickerContainer,
} from "@/components/legacy-wheel-picker";
const frameworkOptions = [
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular", disabled: true },
{ label: "Svelte", value: "svelte" },
];
export function LegacyBasicPickerDemo() {
const [value, setValue] = useState("react");
return (
<div className="flex w-full max-w-[320px] flex-col items-center gap-6">
<LegacyPickerContainer className="w-full">
<LegacyPickerColumn
align="center"
fontSize={22}
onValueChange={setValue}
options={frameworkOptions}
value={value}
/>
</LegacyPickerContainer>
</div>
);
}Three-Column Time Picker
A three-column time picker with mixed widths and a finite meridiem column.
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 59
- 00
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 00
- AM
- PM
"use client";
import { useState } from "react";
import {
LegacyPickerColumn,
LegacyPickerContainer,
} from "@/components/legacy-wheel-picker";
const hours = Array.from({ length: 12 }, (_, index) => ({
label: String(index + 1),
value: index + 1,
}));
const minutes = Array.from({ length: 60 }, (_, index) => ({
label: String(index).padStart(2, "0"),
value: index,
}));
const meridiem = [
{ label: "AM", value: "AM" },
{ label: "PM", value: "PM" },
];
export function LegacyTimePickerExample() {
const [hour, setHour] = useState(10);
const [minute, setMinute] = useState(41);
const [ampm, setAmpm] = useState("AM");
return (
<LegacyPickerContainer
className="relative w-full shrink-0 overflow-hidden"
frameWidth="100%"
width="100%"
>
<div className="relative z-[3] flex flex-[1.2] shrink-0 border-[#000]/20 border-r shadow-[1px_0_0_0_rgba(255,255,255,0.3)]">
<LegacyPickerColumn
align="center"
fontSize={25}
onValueChange={setHour}
options={hours}
padX={15}
value={hour}
/>
</div>
<div className="relative z-[2] flex flex-[1] shrink-0 border-[#000]/20 border-r shadow-[1px_0_0_0_rgba(255,255,255,0.3)]">
<LegacyPickerColumn
align="center"
fontSize={25}
onValueChange={setMinute}
options={minutes}
padX={10}
value={minute}
/>
</div>
<div className="relative z-[1] flex flex-[1.2] shrink-0">
<LegacyPickerColumn
align="center"
fontSize={20}
infinite={false}
onValueChange={setAmpm}
options={meridiem}
padX={15}
value={ampm}
/>
</div>
</LegacyPickerContainer>
);
}Finite Options
A finite list with a disabled option for cases where the picker should stop at the ends.
- Slow
- Normal
- Fast
- Ludicrous
Selected pace: normal
"use client";
import { useState } from "react";
import {
LegacyPickerColumn,
LegacyPickerContainer,
} from "@/components/legacy-wheel-picker";
const paceOptions = [
{ label: "Slow", value: "slow" },
{ label: "Normal", value: "normal" },
{ label: "Fast", value: "fast" },
{ label: "Ludicrous", value: "ludicrous", disabled: true },
];
export function LegacyWheelPickerFiniteExample() {
const [pace, setPace] =
useState<(typeof paceOptions)[number]["value"]>("normal");
return (
<div className="flex w-full max-w-[320px] flex-col items-center gap-4">
<LegacyPickerContainer className="w-full">
<LegacyPickerColumn
align="center"
fontSize={22}
infinite={false}
onValueChange={setPace}
options={paceOptions}
value={pace}
/>
</LegacyPickerContainer>
<p className="font-medium text-[#8b9bb4] text-sm">
Selected pace: <span className="text-white capitalize">{pace}</span>
</p>
</div>
);
}React Hook Form
A wheel picker wired into React Hook Form so the selected value submits with the rest of the form.
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import type { SubmitHandler } from "react-hook-form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { LegacyBarButton } from "@/components/legacy-bar-button";
import { showLegacyNotification } from "@/components/legacy-notification";
import {
LegacyPickerColumn,
LegacyPickerContainer,
} from "@/components/legacy-wheel-picker";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
const formSchema = z.object({
framework: z.string(),
});
type FormSchema = z.infer<typeof formSchema>;
const formOptions = [
{ label: "Vite", value: "vite" },
{ label: "Laravel", value: "laravel", disabled: true },
{ label: "Next.js", value: "nextjs" },
{ label: "Astro", value: "astro" },
];
export function LegacyHookFormDemo() {
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema),
defaultValues: { framework: "nextjs" },
});
const onSubmit: SubmitHandler<FormSchema> = (values) => {
const selectedFramework = formOptions.find(
(option) => option.value === values.framework,
)?.label;
showLegacyNotification({
body: "Selection synced with the form state.",
showIcon: false,
subtitle: selectedFramework ?? "Framework",
time: "now",
title: "Settings Saved",
});
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full max-w-[320px] flex-col gap-6"
>
<FormField
control={form.control}
name="framework"
render={({ field }) => (
<FormItem className="flex w-full flex-col items-center">
<FormLabel className="sr-only">Framework</FormLabel>
<FormControl>
<LegacyPickerContainer className="w-full">
<LegacyPickerColumn
align="center"
fontSize={22}
onValueChange={field.onChange}
options={formOptions}
value={field.value}
/>
</LegacyPickerContainer>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<LegacyBarButton
className="w-full justify-center"
label="Submit"
type="submit"
variant="accent"
/>
</form>
</Form>
);
}API Reference
LegacyPickerContainerProps controls the shell width and frame. LegacyPickerColumnProps controls the option list, alignment, and whether the wheel loops infinitely.
LegacyPickerContainerProps
| Prop | Type | Required |
|---|---|---|
children | React.ReactNode | Yes |
className | string | No |
frameWidth | number | string | No |
width | number | string | No |
LegacyPickerColumnProps
| Prop | Type | Required |
|---|---|---|
align | "left" | "center" | "right" | No |
className | string | No |
fontSize | number | No |
infinite | boolean | No |
onValueChange | (value: T) => void | Yes |
options | WheelPickerOption<T>[] | Yes |
padX | number | No |
value | T | Yes |
width | string | number | No |
zIndex | number | No |
Notes
- Use
infinite={false}when the list should stop at the ends or include disabled options that should remain visible. padX,fontSize, andalignare the main controls for matching the wheel to your content density.- Multi-column layouts are just flex children inside
LegacyPickerContainer, so separators and custom widths live in your own markup.