# Wheel Picker

> People choose a date or a time by scrolling columns, the usual pattern on phones for a time or a nearby date. In Arabic the day sits on the right and ص / م on the left, while hour and minute keep clock order; days and months carry their Arabic names.

Source: https://ritla.app/ui/wheel-picker

## Installation

Installing from the registry is coming soon. Until then, these are the commands you will run.

```bash
npx ritla add wheel-picker
```

With the shadcn CLI instead:

```bash
npx shadcn@latest add @ritla/wheel-picker
```

## Usage

```tsx
import { WheelDateTimePicker } from "@/components/ui/wheel-picker";

const [value, setValue] = useState(() => nextFiveMinutes());

<WheelDateTimePicker value={value} onValueChange={setValue} minuteStep={5} />
```

## Examples

### Date

`WheelDatePicker`: day, month and year, the day first in reading order. Months are short in English and written out in Arabic; years run five either side of the value by default (`minYear`, `maxYear`). For a birthday, type the date in a field rather than scroll through years.

Live preview: https://ritla.app/_kanz/preview/ltr/wheel-picker-date

### In a bottom sheet, from a field

The field shows the value and opens a bottom sheet (`Drawer`) titled with the field's label. Done keeps the new value; Cancel, the close button or a swipe down leave it as it was. Don't leave a wheel open inline on a page.

Live preview: https://ritla.app/_kanz/preview/ltr/wheel-picker-sheet

## API reference

### `WheelDateTimePicker`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / onValueChange` | `Date / (value: Date) => void` | None | The value, always a full date and time. Open on a sensible one: today, the next full five minutes. |
| `min / max` | `Date` | `today / 60 days later` | The first and last day offered. Limit them to what is valid, such as no past days for a delivery. |
| `minuteStep` | `number` | `5` | Minutes in steps of this. |
| `hourCycle` | `"h12" \| "h23"` | `"h12"` | 12 hours with the AM / PM column, or 24 hours without it. |
| `labels` | `{ group, day, hour, minute, period }` | None | Names for the group and columns, for screen readers. Default: in the page language; repeat the field's label in `group`. |
| `locale / numerals` | `string / "latn" \| "arab"` | None | Default: from `KanzProvider`. Names come from Intl in the page language, and every column uses one digit system. |

### `WheelDatePicker`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / onValueChange` | `Date / (value: Date) => void` | None | A day past the end of the new month moves to its last day (31 May, then June: 30 June). |
| `minYear / maxYear` | `number` | `value ± 5` | The years offered. |
| `labels` | `{ group, day, month, year }` | None | Names for the group and columns. |

### `WheelPicker / WheelPickerColumn`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `{ value, label, valueText?, disabled? }[]` | None | The column's rows. `valueText` is what a screen reader says; `disabled` rows stay visible and the column skips them (a booked slot). |
| `value / onValueChange` | `string / (value: string) => void` | None | The value of the row in the band. |
| `label` | `string` | None | The column's name ("Hour"). |
| `aria-label (WheelPicker)` | `string` | None | The whole picker's name; repeat the field's label. |

## Accessibility

Keyboard (keys as in left-to-right; arrows swap in right-to-left):

- `Tab`: From one column to the next: in the date and time picker, day, hour, minute, then AM / PM.
- `ArrowUp` + `ArrowDown`: One row up or down in the column.
- `PageUp` + `PageDown`: Five rows.
- `Home` + `End`: The first and last row.

ARIA:

- Each column is a `spinbutton` with its name and its value in words in `aria-valuetext` ("Wednesday 30 December"), never the band's color alone.
- The columns are a group named after the field. The rows are the picture only, hidden from screen readers.
- Scrolling uses the platform's own momentum and settles by CSS scroll snapping; with reduced motion the column jumps instead of gliding.
- Scrolling is hard for some people: also let them type the value in the field.

## Arabic and RTL notes

Mirrors:

- The column order: the day on the right, where the Arabic reader starts, then the time, with ص / م on the left.

Does not mirror:

- Scrolling is vertical: up and down don't change and the rows inside a column don't flip.
- Hour, then minute, left to right in every language, as on a clock; only AM / PM moves to the other side. Tab follows the pair: hour, then minute.

Numerals: Every column uses one digit system from `numerals`: "6 25" or "٦ ٢٥", never both in one picker.

Arabic typography: Rows are `body-24` Semibold, 48px tall; long Arabic names ("الأربعاء 30 ديسمبر") widen the column rather than wrap.

Mixed-direction text: Arabic month names come from Intl (يناير to ديسمبر), the same set as the date pickers and the rest of the product.
