Components / Gravity Menu
Gravity Menu
Menu items hold stable orbits until the cursor arrives as a mass and bends their paths past it. F = G·m·r/(r² + ε²)^(3/2) — the inverse square with Plummer softening — and the ε is not a fudge — an unsoftened inverse square diverges as an item crosses the pointer, which in a discrete integrator means one frame that throws it off screen. Softening caps the force at close range, which is what N-body codes do for exactly this reason. Rest the pointer on an item and the ring holds still for it; arrow keys walk focus round the ring.
Usage
import GravityMenu from '@/components/gravity-menu'
<GravityMenu items={items} orbit={130} value={current} onSelect={setCurrent} />Installation
npx shadcn@latest add https://morphiq.prathampatel.design/r/gravity-menu.jsonModel
F = G·m·r / (r² + ε²)^(3/2)the inverse square with Plummer softening ε · k the spring to the slot
Props
| Property | Type | Default | Description |
|---|---|---|---|
items | GravityMenuItem[] | [] | The items, in ring order. An item without an id is known by its index. |
orbit | number | 130 | Orbital radius, px. A change is ridden out on each item's spring. Default 130. |
spin | number | 0.16 | Angular rate, radians per second. The ring holds still under a hovered, pressed or keyboard-focused item. Default 0.16. |
mass | number | 2.6e6 | G·m of the pointer, px³/s². At spring · softening³ an item close to the pointer rests halfway to it. Default 2.6e6. |
softening | number | 46 | Softening ε, px. Caps the force at close range so nothing is flung away. Default 46. |
spring | number | 26 | Pull back to the orbital slot, 1/s². 0 makes it a free two-body system. Default 26. |
damping | number | 7.6 | Damping on an item's motion about its slot, 1/s. 2 · √spring is critical. Default 7.6. |
repel | boolean | false | Push instead of pull. The push lets go of the item the pointer is on, so it can still be clicked. Default false. |
value | string | number | — | Id of the current item, or its index if it has no id. It is marked aria-current and filled. |
onSelect | (id: string | number, item: GravityMenuItem, index: number) => void | — | Called with the item's id (or index), the item and its index when it is clicked or activated from the keyboard. |