Rotary table
A rotary indexing table in plan view. Fixtures and workpieces rotate with the platter while the base, motor, and index pointer stay fixed.
variant
loaded
angle30°
stations6
Install
pnpm dlx shadcn@latest add https://robocn.dev/r/rotary-table.jsonNotes
- The platter angle updates immediately without an internal timer. Drive it from your simulation or index it by setting angle to stationIndex * 360 / stations when stations is greater than zero.
- Zero degrees aligns the first fixture to the fixed pointer at the top. The remaining fixtures are equally spaced clockwise.
Usage
import { RotaryTable } from "@/components/ui/rotary-table"
<RotaryTable angle={60} stations={6} loaded variant="blueprint" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| angle | number | 0 | Controlled clockwise platter angle in degrees. Wraps after each turn; non-finite values use zero. |
| stations | number | 6 | Equally spaced fixtures, rounded and clamped to 0–12. Non-finite values use six; zero gives a bare platter. |
| loaded | boolean | true | Show a workpiece in every fixture. |
| showTicks | boolean | true | Draw the graduated angle marks on the rotating platter. |
| label | string | — | Caption below the stationary angle readout. |
| variant | "solid" | "outline" | "blueprint" | "wire" | "solid" | How the machine is painted. Geometry never changes between variants. |
| size | "xs" | "sm" | "md" | "lg" | "xl" | number | "md" | Rendered width in pixels, or a step on the scale. |
| color | string | var(--robot-shell) | Body panels — the colour the machine reads as. |
| accent | string | var(--robot-accent) | Status colour: tip light, live tool, readouts. |
| metal | string | var(--robot-metal) | Bare machined parts: collars, bolts, tool bodies. |
| dark | string | var(--robot-dark) | Cast joints, base, shadow side. |
| palette | Partial<RobotPalette> | — | Override any subset of roles at once, including glow and grid. |
Source
src/components/ui/rotary-table.tsx
"use client"
import type * as React from "react"
import {
px,
resolveRobotPalette,
resolveRobotSize,
robotSurface,
type RobotPaletteProps,
type RobotSize,
type RobotVariant,
} from "@/lib/robocn/style"
import { cn } from "@/lib/utils"
export interface RotaryTableProps
extends Omit<React.ComponentProps<"svg">, "color">, RobotPaletteProps {
/** Controlled clockwise platter angle in degrees. Zero aligns the first fixture to the top. */
angle?: number
/** Equally spaced fixtures, rounded and clamped to 0–12. */
stations?: number
/** Draw a workpiece in each fixture. */
loaded?: boolean
showTicks?: boolean
label?: string
size?: RobotSize | number
variant?: RobotVariant
}
function RotaryTable({
angle = 0, stations = 6, loaded = true, showTicks = true, label,
size = "md", variant = "solid", color, accent, metal, dark, glow, grid,
palette: paletteOverride, className, style, ...props
}: RotaryTableProps) {
const palette = resolveRobotPalette({ color, accent, metal, dark, glow, grid, palette: paletteOverride })
const width = resolveRobotSize(size)
const rotation = Number.isFinite(angle) ? ((angle % 360) + 360) % 360 : 0
const count = Number.isFinite(stations) ? Math.max(0, Math.min(12, Math.round(stations))) : 6
const shell = robotSurface("shell", variant, palette)
const machined = robotSurface("metal", variant, palette)
const cast = robotSurface("dark", variant, palette)
return (
<svg role="img" aria-label={`Rotary table, ${count} stations, angle ${Math.round(rotation)} degrees`}
viewBox="0 0 180 180" width={width} height={width}
className={cn("max-w-full select-none", className)} style={{ color: palette.foreground, ...style }} {...props}>
<g transform="translate(90 86)">
<g data-base>
<rect x={-57} y={-57} width={114} height={114} rx={10} {...cast} />
{[-49, 49].flatMap(x => [-49, 49].map(y => <g key={`${x}-${y}`}>
<circle cx={x} cy={y} r={4} {...machined} />
<path d={`M ${x - 2} ${y} h 4`} stroke={palette.dark} strokeWidth={1} />
</g>))}
<rect x={47} y={-17} width={31} height={34} rx={5} {...shell} />
<rect x={68} y={-12} width={9} height={24} rx={2} {...cast} />
<path d="M 70 -7 h 5 M 70 -2 h 5 M 70 3 h 5 M 70 8 h 5" stroke={palette.metal} strokeWidth={1} />
<circle r={64} {...cast} />
</g>
<g data-platter transform={`rotate(${px(rotation)})`}>
<circle r={59} {...machined} />
<circle r={52} fill="none" stroke={palette.dark} strokeWidth={0.5} />
{showTicks && Array.from({ length: 36 }, (_, i) => <path key={i} d={`M 0 -${i % 3 === 0 ? 53 : 55} V -58`}
transform={`rotate(${i * 10})`} stroke={palette.dark} strokeWidth={0.7} />)}
{Array.from({ length: count }, (_, i) => <g key={i} data-fixture={i}
transform={`rotate(${px(i * 360 / count)}) translate(0 -38)`}>
<rect x={-10} y={-10} width={20} height={20} rx={3} {...cast} />
{loaded && <rect x={-6} y={-7} width={12} height={14} rx={2} {...shell} />}
{[-8, 8].map(x => <rect key={x} x={x - 1.5} y={-4} width={3} height={8} rx={0.5} fill={palette.metal} />)}
</g>)}
<circle r={20} {...shell} />
<circle r={11} {...cast} />
<circle r={5} {...machined} />
<path d="M -3 -13 L 0 -17 L 3 -13" fill="none" stroke={palette.accent} strokeWidth={1.5} />
{[0, 90, 180, 270].map(degrees => <circle key={degrees} cy={15} r={1.5} transform={`rotate(${degrees})`} fill={palette.dark} />)}
</g>
<path d="M -4 -72 L 0 -65 L 4 -72 Z" fill={palette.accent} />
</g>
<g fontFamily="ui-monospace, monospace" textAnchor="middle" fill={palette.foreground}>
<text x={90} y={163} fontSize={5}>{`${px(rotation)}° / ${count} STATIONS`}</text>
{label && <text x={90} y={175} fontSize={4.5}>{label}</text>}
</g>
</svg>
)
}
export { RotaryTable }