DateCarousel
A horizontal picker. Tap a date, drag it, flick it, or use the arrow keys. The highlight resists being moved and stretches wide on the way.
Usage
<DateCarousel
defaultIndex={4}
itemWidth={96}
height={120}
momentum={0.18}
stickiness={0.7}
spring="smooth"
items={days.map((d) => ({
key: String(d),
primary: d,
suffix: "日",
}))}
/>How the motion works
Two springs drive it. The scroll spring holds the row position in pixels and reaches the selected slot first; a much looser second spring chases it and is always behind. The highlight spans between them — leading edge at the fast spring, trailing edge at the slow one — so moving left pulls it wider to the right and it draws back into a circle as the lag closes. Only the span is animated: the height and the fully-round ends are fixed, which is what keeps it reading as one solid pill. The gap between the springs is the deformation: nothing keyframes the stretch, it falls out of one spring outrunning the other. On release the flick velocity is projected forward to choose the landing index, and past the ends the offset is rubber-banded so overscroll resists instead of stopping dead.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | CarouselItem[] | — | Each has key, primary and optional suffix. |
| index / defaultIndex | number | — | Controlled or initial selected index. |
| onChange | (index: number) => void | — | Fires when the selection settles. |
| itemWidth | number | 96 | Slot width and highlight diameter, in px. |
| momentum | number | 0.18 | How far a flick carries past the release point. |
| stickiness | number | 0.7 | 0–1. How far the trailing edge lags behind, and so how far the pill stretches. |
