"use client" /** * Button-based segmented control — mode / theme pickers on doc pages and compact toolbars. * Shares chrome with hub view pills via `viewSegmentedToolbarClass` / `viewSegmentedButtonClass`. * * For hub view switching with counts and settings chevron, use `ViewSegmentedControl` or * `ListPageTemplate` views toolbar — do not duplicate that chrome here. */ import * as React from "react" import { cn } from "../../lib/utils" import { Tip } from "./tip" import { viewSegmentedButtonClass, viewSegmentedToolbarClass, } from "./view-segmented-control" export interface ButtonSegmentOption { value: T label: string icon?: string } export interface ButtonSegmentedControlProps { value: T onValueChange: (value: T) => void options: readonly ButtonSegmentOption[] "aria-label": string "aria-describedby"?: string iconOnly?: boolean /** * Whole group unavailable — the choice still exists and still reads, it just * cannot be changed yet (a parent switch is off). Dimming is the caller's: * the group is usually one row of a settings block that fades together, and * two stacked opacities read as a bug. */ disabled?: boolean className?: string showTooltips?: boolean tooltipSide?: "top" | "bottom" | "left" | "right" } export function ButtonSegmentedControl({ value, onValueChange, options, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, iconOnly = false, disabled = false, className, showTooltips, tooltipSide = "top", }: ButtonSegmentedControlProps) { const tips = showTooltips ?? iconOnly const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([]) React.useLayoutEffect(() => { itemRefs.current = itemRefs.current.slice(0, options.length) }, [options.length]) const focusIndex = React.useCallback( (index: number) => { const len = options.length if (len === 0) return const i = ((index % len) + len) % len onValueChange(options[i].value) requestAnimationFrame(() => { itemRefs.current[i]?.focus() }) }, [onValueChange, options], ) const onRadioKeyDown = React.useCallback( (e: React.KeyboardEvent, index: number) => { const len = options.length if (len === 0) return if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault() focusIndex(index + 1) } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault() focusIndex(index - 1) } else if (e.key === "Home") { e.preventDefault() focusIndex(0) } else if (e.key === "End") { e.preventDefault() focusIndex(len - 1) } }, [focusIndex, options.length], ) return (
{options.map((opt, index) => { const isActive = opt.value === value const tabIndex = isActive && !disabled ? 0 : -1 const button = (