{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"model-picker","type":"registry:component","title":"Model picker","description":"The model pill in your composer grows into the menu: what each model is for, speed, smarts and thinking effort.","author":"Hairline UI","dependencies":["lucide-react"],"registryDependencies":["https://hairlineui.com/r/text-morph.json"],"files":[{"path":"registry/model-picker.tsx","type":"registry:component","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { Check, ChevronDown } from \"lucide-react\";\nimport { TextMorph } from \"./text-morph\";\n\n/* ─────────────────────────────────────────────────────────\n * MODEL PICKER: the pill grows into the menu\n *\n *   closed   a quiet pill in the composer toolbar: model and\n *            thinking effort; both morph when they change, and\n *            the model's icon trades places through a blur\n *   open     a copy of the pill lifts clear of the composer and\n *            unfolds into the list: each model with a line on\n *            what it's for, speed and smarts\n *   effort   models that reason get a Thinking control; the\n *            panel's height follows\n *   locked   paid models show \"Pro\" and call onLockedSelect\n *\n * One surface: opening grows the pill's size and radius into the\n * panel while the label cross-fades into the list, and closing\n * folds it back into the pill. It never covers the composer:\n * pass anchorRef and it opens above it, lined up with its edge.\n * ───────────────────────────────────────────────────────── */\n\nexport interface ModelOption {\n  id: string;\n  name: string;\n  /** What it's for, e.g. \"Fast, everyday questions\". */\n  description?: string;\n  icon?: React.ReactNode;\n  /** 1 to 3. */\n  speed?: number;\n  /** 1 to 3. */\n  intelligence?: number;\n  /** Supports a thinking-effort setting. */\n  reasoning?: boolean;\n  /** Needs an upgrade; shows \"Pro\". */\n  locked?: boolean;\n}\n\nexport interface ModelPickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n  models: ModelOption[];\n  value: string;\n  onValueChange: (id: string) => void;\n  /** Thinking effort for reasoning models; shown when onEffortChange is set. */\n  effort?: string;\n  onEffortChange?: (effort: string) => void;\n  efforts?: string[];\n  /** Called instead of selecting a locked model, e.g. to open pricing. */\n  onLockedSelect?: (model: ModelOption) => void;\n  /** Grow upward (composers at the bottom) or downward. */\n  side?: \"top\" | \"bottom\";\n  /** Grow from the pill's left edge or its right edge. */\n  align?: \"start\" | \"end\";\n  /** Space to keep from the viewport edges on narrow screens. */\n  collisionPadding?: number;\n  /** Open clear of this element (e.g. your composer) and line up with its edge, instead of over it. */\n  anchorRef?: React.RefObject<HTMLElement | null>;\n  disabled?: boolean;\n  /** What the choice is, for screen readers: \"Model\" by default, e.g. \"Permission\" for a permissions picker. */\n  label?: string;\n}\n\ntype Phase = \"closed\" | \"opening\" | \"open\" | \"closing\";\n\nconst EFFORTS = [\"Low\", \"Medium\", \"High\"];\nconst EASE = \"cubic-bezier(0.23,1,0.32,1)\";\nconst DURATION = 380;\nconst PANEL_WIDTH = 340;\nconst GAP = 8;\nconst FOCUS = \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\";\n\nconst reducedQuery = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduced = (onChange: () => void) => {\n  const query = window.matchMedia(reducedQuery);\n  query.addEventListener(\"change\", onChange);\n  return () => query.removeEventListener(\"change\", onChange);\n};\nconst useReducedMotion = () =>\n  React.useSyncExternalStore(subscribeReduced, () => window.matchMedia(reducedQuery).matches, () => false);\n\nfunction Meter({ value = 0, label }: { value?: number; label: string }) {\n  return (\n    <span className=\"flex w-[38px] shrink-0 items-center justify-center gap-[3px]\" title={`${label}: ${value} of 3`}>\n      {[1, 2, 3].map((n) => (\n        <span key={n} aria-hidden=\"true\" className={`h-[5px] w-[7px] rounded-full ${n <= value ? \"bg-foreground/70\" : \"bg-border\"}`} />\n      ))}\n      <span className=\"sr-only\">\n        {label} {value} of 3\n      </span>\n    </span>\n  );\n}\n\nconst MORPH = \"cubic-bezier(0.16,1,0.3,1)\";\n\n/** The model's icon: a new one rises out of a blur while the old one shrinks away. */\nfunction ModelIcon({ id, icon, reduced }: { id: string; icon: React.ReactNode; reduced: boolean }) {\n  const [items, setItems] = React.useState([{ id, icon }]);\n  if (items[items.length - 1].id !== id) setItems([...items.slice(-1), { id, icon }]);\n  const first = React.useRef(true);\n  React.useEffect(() => {\n    first.current = false;\n  }, []);\n  return (\n    <span aria-hidden=\"true\" className=\"relative flex size-4 items-center justify-center text-muted-foreground [&_svg]:size-4 [&_svg]:stroke-[1.8]\">\n      {items.map((item) => (\n        <IconLayer key={item.id} on={item.id === id} enter={!first.current && !reduced} reduced={reduced}>\n          {item.icon}\n        </IconLayer>\n      ))}\n    </span>\n  );\n}\n\nfunction IconLayer({ on, enter, reduced, children }: { on: boolean; enter: boolean; reduced: boolean; children: React.ReactNode }) {\n  const ref = React.useRef<HTMLSpanElement>(null);\n  const [entering] = React.useState(enter);\n  React.useLayoutEffect(() => {\n    if (!entering) return;\n    ref.current?.animate(\n      [\n        { opacity: 0, transform: \"scale(0.6)\", filter: \"blur(3px)\" },\n        { opacity: 1, transform: \"none\", filter: \"blur(0px)\" },\n      ],\n      { duration: 380, easing: MORPH, fill: \"backwards\" }\n    );\n  }, [entering]);\n  return (\n    <span\n      ref={ref}\n      className=\"absolute inset-0 flex items-center justify-center\"\n      style={{\n        opacity: on ? 1 : 0,\n        transform: on ? \"none\" : \"scale(0.6)\",\n        filter: on ? \"none\" : \"blur(3px)\",\n        transition: reduced ? \"none\" : `opacity 200ms ${MORPH}, transform 380ms ${MORPH}, filter 200ms ${MORPH}`,\n      }}\n    >\n      {children}\n    </span>\n  );\n}\n\nfunction PillLabel({ model, effort, reduced }: { model?: ModelOption; effort?: string; reduced: boolean }) {\n  // The effort opens beside the name for models that reason, and folds away (keeping its word) for those that don't.\n  const showEffort = Boolean(model?.reasoning && effort);\n  const [lastEffort, setLastEffort] = React.useState(effort);\n  if (effort && effort !== lastEffort) setLastEffort(effort);\n  return (\n    <>\n      {model?.icon && <ModelIcon id={model.id} icon={model.icon} reduced={reduced} />}\n      <span className=\"whitespace-nowrap text-foreground\">\n        <TextMorph>{model?.name ?? \"Choose a model\"}</TextMorph>\n      </span>\n      <span\n        className=\"-ml-1.5 grid\"\n        style={{\n          gridTemplateColumns: showEffort ? \"1fr\" : \"0fr\",\n          opacity: showEffort ? 1 : 0,\n          transition: reduced ? \"none\" : `grid-template-columns 380ms ${MORPH}, opacity ${showEffort ? \"280ms\" : \"140ms\"} ${MORPH}`,\n        }}\n      >\n        <span className=\"min-w-0 overflow-hidden whitespace-nowrap pl-1.5 text-muted-foreground\">\n          <TextMorph>{effort ?? lastEffort ?? \"\"}</TextMorph>\n        </span>\n      </span>\n    </>\n  );\n}\n\nexport function ModelPicker({\n  models,\n  value,\n  onValueChange,\n  effort,\n  onEffortChange,\n  efforts = EFFORTS,\n  onLockedSelect,\n  side = \"top\",\n  align = \"start\",\n  collisionPadding = 16,\n  anchorRef,\n  disabled = false,\n  label = \"Model\",\n  className = \"\",\n  ...props\n}: ModelPickerProps) {\n  const id = React.useId();\n  const reduced = useReducedMotion();\n  const selected = models.find((m) => m.id === value);\n  const [phase, setPhase] = React.useState<Phase>(\"closed\");\n  const [active, setActive] = React.useState(0);\n  const [pill, setPill] = React.useState({ width: 0, height: 32 });\n  const [panelHeight, setPanelHeight] = React.useState(0);\n  // The panel's width and sideways shift, so it always fits the screen.\n  const [fit, setFit] = React.useState({ width: PANEL_WIDTH, shift: 0, lift: 0 });\n  const [highlight, setHighlight] = React.useState({ top: 0, ready: false });\n  const [segment, setSegment] = React.useState({ left: 0, width: 0, ready: false });\n  // After a mouse choice, focus returns to the pill without a keyboard focus ring.\n  const [quietFocus, setQuietFocus] = React.useState(false);\n\n  const wrapRef = React.useRef<HTMLDivElement>(null);\n  const triggerRef = React.useRef<HTMLButtonElement>(null);\n  const panelRef = React.useRef<HTMLDivElement>(null);\n  const listRef = React.useRef<HTMLDivElement>(null);\n  const rowRefs = React.useRef<(HTMLDivElement | null)[]>([]);\n  const segmentRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n  const timer = React.useRef(0);\n\n  const expanded = phase === \"open\";\n  const mounted = phase !== \"closed\";\n  const showEffort = Boolean(onEffortChange && selected?.reasoning);\n\n  /* Where the open panel sits: lifted clear of the anchor (the pill itself by default), lined up\n   * with the anchor's edge, and narrowed or shifted so it never leaves the screen. */\n  const measureFit = React.useCallback(() => {\n    const wrap = wrapRef.current;\n    if (!wrap) return;\n    const rect = wrap.getBoundingClientRect();\n    const edge = (anchorRef?.current ?? wrap).getBoundingClientRect();\n    const viewport = document.documentElement.clientWidth;\n    const width = Math.min(PANEL_WIDTH, viewport - collisionPadding * 2);\n    const lift = side === \"top\" ? rect.bottom - edge.top + GAP : edge.bottom - rect.top + GAP;\n    let shift: number;\n    if (align === \"start\") {\n      const left = Math.min(Math.max(edge.left, collisionPadding), viewport - collisionPadding - width);\n      shift = left - rect.left;\n    } else {\n      const right = Math.max(Math.min(edge.right, viewport - collisionPadding), collisionPadding + width);\n      shift = right - rect.right;\n    }\n    setFit((f) => (f.width === width && f.shift === shift && f.lift === lift ? f : { width, shift, lift }));\n  }, [align, side, collisionPadding, anchorRef]);\n\n  const open = () => {\n    if (disabled || phase === \"open\" || phase === \"opening\") return;\n    window.clearTimeout(timer.current);\n    measureFit();\n    setActive(Math.max(0, models.findIndex((m) => m.id === value)));\n    setPhase(reduced ? \"open\" : \"opening\");\n  };\n\n  const close = React.useCallback(\n    (refocus = true) => {\n      window.clearTimeout(timer.current);\n      if (refocus) triggerRef.current?.focus({ preventScroll: true });\n      if (reduced) {\n        setPhase(\"closed\");\n        return;\n      }\n      setPhase(\"closing\");\n      timer.current = window.setTimeout(() => setPhase(\"closed\"), DURATION);\n    },\n    [reduced]\n  );\n\n  // Opening: paint the surface at the pill's size first, then let it grow.\n  React.useEffect(() => {\n    if (phase !== \"opening\") return;\n    let inner = 0;\n    const outer = requestAnimationFrame(() => {\n      inner = requestAnimationFrame(() => setPhase(\"open\"));\n    });\n    return () => {\n      cancelAnimationFrame(outer);\n      cancelAnimationFrame(inner);\n    };\n  }, [phase]);\n\n  React.useEffect(() => () => window.clearTimeout(timer.current), []);\n\n  React.useEffect(() => {\n    if (!mounted) return;\n    window.addEventListener(\"resize\", measureFit);\n    return () => window.removeEventListener(\"resize\", measureFit);\n  }, [mounted, measureFit]);\n\n  React.useEffect(() => {\n    if (expanded) listRef.current?.focus({ preventScroll: true });\n  }, [expanded]);\n\n  // Measure the pill and the panel; both can change with the model, effort or font loading.\n  React.useLayoutEffect(() => {\n    const trigger = triggerRef.current;\n    if (!trigger) return;\n    const measure = () => {\n      const width = trigger.offsetWidth;\n      const height = trigger.offsetHeight;\n      setPill((p) => (p.width === width && p.height === height ? p : { width, height }));\n    };\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(trigger);\n    return () => observer.disconnect();\n  }, []);\n\n  React.useLayoutEffect(() => {\n    const panel = panelRef.current;\n    if (!panel) return;\n    const measure = () => setPanelHeight(panel.offsetHeight);\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(panel);\n    return () => observer.disconnect();\n  }, [mounted]);\n\n  // The row highlight and the effort pill slide to where they belong.\n  React.useLayoutEffect(() => {\n    const row = rowRefs.current[active];\n    if (!row) return;\n    const top = row.offsetTop;\n    setHighlight((h) => (h.top === top && h.ready === (h.ready || expanded) ? h : { top, ready: h.ready || expanded }));\n  }, [active, expanded, mounted]);\n\n  React.useLayoutEffect(() => {\n    const i = efforts.indexOf(effort ?? \"\");\n    const button = segmentRefs.current[i];\n    if (!button) return;\n    const left = button.offsetLeft;\n    const width = button.offsetWidth;\n    setSegment((s) => (s.left === left && s.width === width && s.ready === (s.ready || expanded) ? s : { left, width, ready: s.ready || expanded }));\n  }, [effort, efforts, expanded, mounted, showEffort]);\n\n  React.useEffect(() => {\n    if (!mounted) setHighlight({ top: 0, ready: false });\n  }, [mounted]);\n\n  // Close on a click outside, or when focus leaves.\n  React.useEffect(() => {\n    if (!expanded) return;\n    const onDown = (e: PointerEvent) => {\n      if (!wrapRef.current?.contains(e.target as Node)) close(false);\n    };\n    document.addEventListener(\"pointerdown\", onDown);\n    return () => document.removeEventListener(\"pointerdown\", onDown);\n  }, [expanded, close]);\n\n  const choose = (model: ModelOption) => {\n    if (model.locked) {\n      onLockedSelect?.(model);\n      if (onLockedSelect) close();\n      return;\n    }\n    onValueChange(model.id);\n    close();\n  };\n\n  const onListKey = (e: React.KeyboardEvent) => {\n    const last = models.length - 1;\n    const move = (to: number) => {\n      e.preventDefault();\n      setActive(Math.max(0, Math.min(last, to)));\n    };\n    if (e.key === \"ArrowDown\") move(active === last ? 0 : active + 1);\n    else if (e.key === \"ArrowUp\") move(active === 0 ? last : active - 1);\n    else if (e.key === \"Home\") move(0);\n    else if (e.key === \"End\") move(last);\n    else if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      if (models[active]) choose(models[active]);\n    }\n  };\n\n  const onEffortKey = (e: React.KeyboardEvent) => {\n    if (!onEffortChange || (e.key !== \"ArrowRight\" && e.key !== \"ArrowLeft\")) return;\n    e.preventDefault();\n    const i = efforts.indexOf(effort ?? \"\");\n    const next = (i + (e.key === \"ArrowRight\" ? 1 : -1) + efforts.length) % efforts.length;\n    onEffortChange(efforts[next]);\n    segmentRefs.current[next]?.focus();\n  };\n\n  const width = expanded ? fit.width : pill.width;\n  const height = expanded ? panelHeight : pill.height;\n  const listId = `${id}-list`;\n\n  return (\n    <div\n      ref={wrapRef}\n      className={`relative inline-flex ${className}`}\n      onKeyDown={(e) => {\n        setQuietFocus(false);\n        if (e.key === \"Escape\" && expanded) {\n          e.preventDefault();\n          close();\n        }\n      }}\n      onBlur={(e) => {\n        if (expanded && !wrapRef.current?.contains(e.relatedTarget as Node)) close(false);\n      }}\n      {...props}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        aria-haspopup=\"listbox\"\n        aria-expanded={expanded}\n        aria-controls={mounted ? listId : undefined}\n        aria-label={`${label}: ${selected?.name ?? \"none\"}${selected?.reasoning && effort ? `, ${effort} thinking` : \"\"}`}\n        disabled={disabled}\n        onClick={() => (expanded ? close() : open())}\n        onPointerDown={() => setQuietFocus(true)}\n        onKeyDown={() => setQuietFocus(false)}\n        onBlur={() => setQuietFocus(false)}\n        className={`inline-flex h-8 items-center gap-1.5 rounded-full px-3 text-[12.5px] font-medium transition-colors duration-150 hover:bg-accent disabled:pointer-events-none disabled:opacity-50 ${quietFocus ? \"outline-none\" : FOCUS} ${expanded ? \"bg-accent\" : \"\"}`}\n      >\n        <PillLabel model={selected} effort={effort} reduced={reduced} />\n        {/* Points up while the panel is open; the panel's copy of the pill turns in step. */}\n        <ChevronDown\n          aria-hidden=\"true\"\n          className=\"size-3.5 text-muted-foreground\"\n          style={{ transform: expanded ? \"rotate(180deg)\" : \"none\", transition: reduced ? \"none\" : `transform 320ms ${EASE}` }}\n        />\n      </button>\n\n      {mounted && (\n        <div\n          // Clicks inside keep focus where it is, so the panel only closes when you leave it.\n          onPointerDown={(e) => {\n            e.preventDefault();\n            setQuietFocus(true);\n          }}\n          className=\"absolute z-50 overflow-hidden bg-popover text-popover-foreground\"\n          style={{\n            [side === \"top\" ? \"bottom\" : \"top\"]: expanded ? fit.lift : 0,\n            [align === \"start\" ? \"left\" : \"right\"]: expanded ? (align === \"start\" ? fit.shift : -fit.shift) : 0,\n            width,\n            height,\n            borderRadius: expanded ? 20 : pill.height / 2,\n            boxShadow: expanded ? \"0 0 0 1px var(--border)\" : \"0 0 0 1px transparent\",\n            transition: reduced\n              ? \"none\"\n              : `width ${DURATION}ms ${EASE}, height ${DURATION}ms ${EASE}, left ${DURATION}ms ${EASE}, right ${DURATION}ms ${EASE}, top ${DURATION}ms ${EASE}, bottom ${DURATION}ms ${EASE}, border-radius ${DURATION}ms ${EASE}, box-shadow 240ms ${EASE}`,\n          }}\n        >\n          {/* The pill's label, fading out as the surface grows (and back in as it folds). */}\n          <span\n            aria-hidden=\"true\"\n            className={`pointer-events-none absolute inline-flex h-8 items-center gap-1.5 px-3 text-[12.5px] font-medium ${side === \"top\" ? \"bottom-0\" : \"top-0\"} ${align === \"start\" ? \"left-0\" : \"right-0\"}`}\n            style={{\n              opacity: expanded ? 0 : 1,\n              transition: reduced ? \"none\" : expanded ? \"opacity 120ms ease-out\" : \"opacity 200ms ease-out 140ms\",\n            }}\n          >\n            <PillLabel model={selected} effort={effort} reduced={reduced} />\n            {/* Turns with the surface: up as it opens, back down as it folds, so the hand-off to the pill never flips. */}\n            <ChevronDown\n              className=\"size-3.5 text-muted-foreground\"\n              style={{ transform: expanded ? \"rotate(180deg)\" : \"none\", transition: reduced ? \"none\" : `transform 320ms ${EASE}` }}\n            />\n          </span>\n\n          <div\n            ref={panelRef}\n            inert={!expanded}\n            className={`absolute p-1.5 ${side === \"top\" ? \"bottom-0\" : \"top-0\"} ${align === \"start\" ? \"left-0\" : \"right-0\"}`}\n            style={{\n              width: fit.width,\n              opacity: expanded ? 1 : 0,\n              transform: expanded ? \"none\" : `translateY(${side === \"top\" ? 6 : -6}px)`,\n              transition: reduced\n                ? \"none\"\n                : expanded\n                  ? `opacity 240ms ease-out 110ms, transform ${DURATION}ms ${EASE}`\n                  : \"opacity 100ms ease-out, transform 200ms ease-out\",\n            }}\n          >\n            <div className=\"flex h-7 items-center gap-3 px-2.5 text-[11px] text-muted-foreground\">\n              <span className=\"flex-1\">{label}</span>\n              {models.some((m) => m.speed != null || m.intelligence != null) && (\n                <>\n                  <span className=\"w-[38px] text-center\">Speed</span>\n                  <span className=\"w-[38px] text-center\">Smarts</span>\n                </>\n              )}\n              <span className=\"w-7\" />\n            </div>\n\n            <div\n              ref={listRef}\n              id={listId}\n              role=\"listbox\"\n              tabIndex={0}\n              aria-label={`${label}s`}\n              aria-activedescendant={`${id}-model-${active}`}\n              onKeyDown={onListKey}\n              className=\"relative rounded-[14px] outline-none\"\n            >\n              <span\n                aria-hidden=\"true\"\n                className=\"absolute inset-x-0 top-0 h-12 rounded-[14px] bg-accent\"\n                style={{ transform: `translateY(${highlight.top}px)`, transition: highlight.ready && !reduced ? `transform 200ms ${EASE}` : \"none\" }}\n              />\n              {models.map((model, i) => {\n                const isSelected = model.id === value;\n                return (\n                  <div\n                    key={model.id}\n                    ref={(el) => {\n                      rowRefs.current[i] = el;\n                    }}\n                    id={`${id}-model-${i}`}\n                    role=\"option\"\n                    aria-selected={isSelected}\n                    aria-disabled={model.locked && !onLockedSelect ? true : undefined}\n                    onPointerMove={() => i !== active && setActive(i)}\n                    onClick={() => choose(model)}\n                    className={`relative flex h-12 cursor-pointer items-center gap-3 rounded-[14px] px-2.5 ${model.locked && !onLockedSelect ? \"cursor-default opacity-50\" : \"\"}`}\n                  >\n                    {model.icon && (\n                      <span aria-hidden=\"true\" className=\"flex size-4 shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4 [&_svg]:stroke-[1.8]\">\n                        {model.icon}\n                      </span>\n                    )}\n                    <span className=\"min-w-0 flex-1\">\n                      <span className=\"block truncate text-[13px] font-medium text-foreground\">{model.name}</span>\n                      {model.description && <span className=\"block truncate text-[11.5px] text-muted-foreground\">{model.description}</span>}\n                    </span>\n                    {/* Speed and smarts only for options that have them (a permissions list doesn't). */}\n                    {(model.speed != null || model.intelligence != null) && (\n                      <>\n                        <Meter value={model.speed} label=\"Speed\" />\n                        <Meter value={model.intelligence} label=\"Smarts\" />\n                      </>\n                    )}\n                    <span className=\"flex w-7 shrink-0 justify-end\">\n                      {model.locked ? (\n                        <span className=\"rounded-full bg-muted px-1.5 py-px text-[10.5px] font-medium text-muted-foreground\">Pro</span>\n                      ) : isSelected ? (\n                        <Check aria-hidden=\"true\" className=\"size-3.5 text-foreground\" strokeWidth={2.2} />\n                      ) : null}\n                    </span>\n                  </div>\n                );\n              })}\n            </div>\n\n            {showEffort && (\n              <div className=\"mt-1.5 flex items-center justify-between gap-3 border-t border-border/70 px-2.5 pb-1 pt-2.5 animate-[ui-fade-in_200ms_ease-out_both]\">\n                <span id={`${id}-effort`} className=\"text-[12px] text-muted-foreground\">\n                  Thinking\n                </span>\n                <div role=\"radiogroup\" aria-labelledby={`${id}-effort`} onKeyDown={onEffortKey} className=\"relative flex rounded-full bg-muted p-0.5\">\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"absolute inset-y-0.5 left-0 rounded-full bg-background shadow-[0_0_0_1px_var(--border)]\"\n                    style={{\n                      width: segment.width,\n                      transform: `translateX(${segment.left}px)`,\n                      transition: segment.ready && !reduced ? `transform 240ms ${EASE}, width 240ms ${EASE}` : \"none\",\n                    }}\n                  />\n                  {efforts.map((level, i) => (\n                    <button\n                      key={level}\n                      ref={(el) => {\n                        segmentRefs.current[i] = el;\n                      }}\n                      type=\"button\"\n                      role=\"radio\"\n                      aria-checked={level === effort}\n                      tabIndex={level === effort ? 0 : -1}\n                      onClick={() => onEffortChange?.(level)}\n                      className={`relative h-6 rounded-full px-2.5 text-[11.5px] font-medium transition-colors ${FOCUS} ${level === effort ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\"}`}\n                    >\n                      {level}\n                    </button>\n                  ))}\n                </div>\n              </div>\n            )}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n"}],"css":{"@keyframes ui-fade-in":{"from":{"opacity":"0"},"to":{"opacity":"1"}}},"docs":"Docs and live preview: https://hairlineui.com/ui/model-picker"}