KQ-1148 · STRAIGHTEN/DIAL
Straighten Dial
A photo editor's straighten control: a degree ruler under the picture scrolls 1:1 under the finger and turns the picture about its centre, while a thirds grid fades in for as long as it turns and the crop scales, frame by frame from the same angle, just enough to keep the corners covered. Zero is magnetic: the ruler holds at exactly 0 inside the magnet and catches up at double rate beyond it, the ends rubber-band, and a flick coasts to its projected landing on a critically damped spring while a slow release stays put; rubber-banded releases and keys move on the glide spring. Arrows turn a degree (Shift a tenth), PageUp and PageDown five, Home and End to the ends, with the same ticks and the same snap at zero.
+2.0° · 1.8° off level
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/straighten-dial
Props
- value / defaultValue
- number
- default 0
- The rotation in degrees, clockwise positive, in tenths. Controlled or uncontrolled; a value the host refuses is turned back to once the finger lets go.
- onValueChange
- (degrees: number) => void
- default —
- Fires from the drag, key, throw or reset that turned the picture, with the new rotation.
- children
- React.ReactNode
- default —
- The picture to straighten. It fills the frame and turns inside it.
- aspect
- number
- default 2
- The frame's width over its height. The crop scale is worked out from it.
- label
- string
- default "Straighten"
- The ruler's accessible name, shown left of the reading.
- range
- 15 | 45 | "15" | "45"
- default "45"
- How far it turns either way. The 15° ruler is also the finer one: 12 px a degree against 6, labelled every 5° rather than every 15°.
- grid
- boolean
- default true
- Fade a thirds grid over the picture while it turns, and away again once it stops.
- magnet
- number
- default 1.5
- How wide the sticky zone around zero is, 0 to 3 degrees, drawn on the ruler. Inside it the ruler holds at exactly 0; 0 makes zero an ordinary degree.
- crop
- boolean
- default true
- Scale the picture by the least amount that keeps the frame covered as it turns. Off, it turns at its own size and the empty corners show a checkerboard.
- sound
- boolean
- default false
- A fine tick as each degree passes the needle, pitched up the further it is from level, and a snap as zero does. Nothing plays, and no audio context exists, unless this is on.
- disabled
- boolean
- default false
- Dims the control, takes the ruler out of the tab order and ignores drags, keys and the reset.
- className
- string
- default —
- Merged onto the root's classes.
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue | number | 0 | The rotation in degrees, clockwise positive, in tenths. Controlled or uncontrolled; a value the host refuses is turned back to once the finger lets go. |
| onValueChange | (degrees: number) => void | — | Fires from the drag, key, throw or reset that turned the picture, with the new rotation. |
| children | React.ReactNode | — | The picture to straighten. It fills the frame and turns inside it. |
| aspect | number | 2 | The frame's width over its height. The crop scale is worked out from it. |
| label | string | "Straighten" | The ruler's accessible name, shown left of the reading. |
| range | 15 | 45 | "15" | "45" | "45" | How far it turns either way. The 15° ruler is also the finer one: 12 px a degree against 6, labelled every 5° rather than every 15°. |
| grid | boolean | true | Fade a thirds grid over the picture while it turns, and away again once it stops. |
| magnet | number | 1.5 | How wide the sticky zone around zero is, 0 to 3 degrees, drawn on the ruler. Inside it the ruler holds at exactly 0; 0 makes zero an ordinary degree. |
| crop | boolean | true | Scale the picture by the least amount that keeps the frame covered as it turns. Off, it turns at its own size and the empty corners show a checkerboard. |
| sound | boolean | false | A fine tick as each degree passes the needle, pitched up the further it is from level, and a snap as zero does. Nothing plays, and no audio context exists, unless this is on. |
| disabled | boolean | false | Dims the control, takes the ruler out of the tab order and ignores drags, keys and the reset. |
| className | string | — | Merged onto the root's classes. |