{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"description-list","type":"registry:ui","title":"Description list","description":"A record's details as label and value pairs, side by side when there is room, with copy and edit where the value sits.","dependencies":["@radix-ui/react-slot","clsx","lucide-react","tailwind-merge"],"registryDependencies":["https://caliperui.site/r/styles.json"],"files":[{"path":"ui/button.tsx","type":"registry:ui","target":"components/ui/button.tsx","content":"\"use client\";\n\nimport { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport { LoaderCircle } from \"lucide-react\";\nimport {\n  useLayoutEffect,\n  useRef,\n  useState,\n  type ComponentProps,\n  type ReactNode,\n} from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/loading-indicator.css\";\nimport \"./button.css\";\n\n/* Colour, material and press live in button.css keyed on data-variant, so a\n   consumer's utility classes still override them. Raised to press: the\n   button sinks 1px into an inner shade instead of scaling. */\nconst variants = {\n  default: \"\",\n  secondary: \"\",\n  ghost: \"\",\n  destructive: \"\",\n};\n\nconst sizes = {\n  sm: \"h-8 gap-1.5 rounded-full px-3 text-[13px] max-md:min-h-11 [@media(pointer:coarse)]:min-h-11\",\n  md: \"h-[var(--control-height)] gap-2 rounded-full px-3.5 text-sm\",\n  lg: \"h-11 gap-2 rounded-full px-[18px] text-[15px]\",\n  icon: \"size-[var(--control-height)] rounded-full p-0\",\n};\n\nexport type ButtonProps = ComponentProps<\"button\"> & {\n  variant?: keyof typeof variants;\n  size?: keyof typeof sizes;\n  asChild?: boolean;\n  /** Shows a spinner over the label and swallows clicks while keeping focus and width. */\n  loading?: boolean;\n  /** Animate a meaningful label/icon change, such as saving → saved. */\n  feedbackKey?: string;\n};\n\nexport function Button({\n  className,\n  variant = \"default\",\n  size = \"md\",\n  asChild = false,\n  loading = false,\n  feedbackKey,\n  disabled,\n  \"aria-disabled\": ariaDisabled,\n  children,\n  onClick,\n  onClickCapture,\n  tabIndex,\n  type = \"button\",\n  ...props\n}: ButtonProps) {\n  const Component = asChild ? Slot : \"button\";\n  const nativeDisabled = Boolean(disabled);\n  const blocked =\n    nativeDisabled ||\n    loading ||\n    ariaDisabled === true ||\n    ariaDisabled === \"true\";\n\n  return (\n    <Component\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      type={asChild ? undefined : type}\n      className={cn(\n        \"relative inline-flex shrink-0 items-center justify-center whitespace-nowrap border border-transparent font-medium tracking-[-0.006em] outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none aria-disabled:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n        variants[variant],\n        sizes[size],\n        className,\n      )}\n      disabled={asChild ? undefined : nativeDisabled}\n      aria-disabled={nativeDisabled || ariaDisabled ? true : undefined}\n      aria-busy={loading || undefined}\n      tabIndex={asChild && nativeDisabled ? -1 : tabIndex}\n      {...props}\n      onKeyDown={(event) => {\n        if (blocked && (event.key === \"Enter\" || event.key === \" \")) {\n          event.preventDefault();\n        }\n        props.onKeyDown?.(event);\n      }}\n      onClickCapture={(event) => {\n        if (blocked) {\n          event.preventDefault();\n          event.stopPropagation();\n          return;\n        }\n        onClickCapture?.(event);\n      }}\n      onClick={onClick}\n    >\n      {loading && asChild && (\n        <LoaderCircle data-slot=\"loading-indicator\" aria-hidden=\"true\" />\n      )}\n      {loading && !asChild && feedbackKey === undefined && (\n        <span data-slot=\"button-busy\" aria-hidden=\"true\">\n          <LoaderCircle data-slot=\"loading-indicator\" />\n        </span>\n      )}\n      <Slottable>\n        {loading && !asChild && feedbackKey === undefined ? (\n          <span data-slot=\"button-label\" data-hidden=\"true\">\n            {children}\n          </span>\n        ) : feedbackKey !== undefined && !asChild ? (\n          <ButtonFeedback stateKey={feedbackKey}>\n            {loading && (\n              <LoaderCircle data-slot=\"loading-indicator\" aria-hidden=\"true\" />\n            )}\n            {children}\n          </ButtonFeedback>\n        ) : (\n          children\n        )}\n      </Slottable>\n    </Component>\n  );\n}\n\nfunction ButtonFeedback({\n  stateKey,\n  children,\n}: {\n  stateKey: string;\n  children: ReactNode;\n}) {\n  const content = useRef<HTMLSpanElement>(null);\n  const [initialKey] = useState(stateKey);\n  const [width, setWidth] = useState<number>();\n  useLayoutEffect(() => {\n    const element = content.current;\n    if (!element) return;\n    const measure = () => setWidth(element.offsetWidth);\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(element);\n    return () => observer.disconnect();\n  }, [stateKey]);\n  return (\n    <span data-slot=\"button-feedback\" style={{ width }}>\n      <span\n        key={stateKey}\n        ref={content}\n        data-slot=\"button-feedback-label\"\n        data-enter={stateKey !== initialKey}\n      >\n        {children}\n      </span>\n    </span>\n  );\n}\n"},{"path":"ui/copy-button.tsx","type":"registry:ui","target":"components/ui/copy-button.tsx","content":"\"use client\";\n\nimport { Check, Copy } from \"lucide-react\";\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { Button, type ButtonProps } from \"./button\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/action-label.css\";\nimport \"./copy-button.css\";\n\n/** \"rv_live_8Kq2mZt7XcP1vW9yL3nB4f2a\" → \"rv_live_8K…4f2a\". */\nexport function shortenValue(value: string, head = 10, tail = 4) {\n  return value.length <= head + tail + 1 ? value : `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nasync function writeClipboard(value: string) {\n  try {\n    await navigator.clipboard.writeText(value);\n    return true;\n  } catch {\n    // Older or locked-down browsers: fall back to a selected textarea.\n    const area = document.createElement(\"textarea\");\n    area.value = value;\n    area.setAttribute(\"readonly\", \"\");\n    area.style.position = \"fixed\";\n    area.style.opacity = \"0\";\n    document.body.appendChild(area);\n    area.select();\n    let ok = false;\n    try {\n      ok = document.execCommand(\"copy\");\n    } catch {\n      ok = false;\n    }\n    area.remove();\n    return ok;\n  }\n}\n\n/**\n * Copies a value and confirms what was copied, shortened in mono, on a small\n * ticket above the button. If the browser blocks the clipboard, the ticket\n * offers the value selected so it can be copied by hand.\n */\nexport function CopyButton({\n  value,\n  label,\n  display,\n  iconOnly = false,\n  variant = \"secondary\",\n  size = \"sm\",\n  resetAfterMs = 1800,\n  onCopy,\n  className,\n  children,\n}: {\n  /** The text written to the clipboard. */\n  value: string;\n  /** Accessible name, e.g. \"Copy live secret key\". */\n  label: string;\n  /** What the ticket shows; defaults to the value shortened in the middle. */\n  display?: string;\n  iconOnly?: boolean;\n  variant?: ButtonProps[\"variant\"];\n  size?: Exclude<ButtonProps[\"size\"], \"icon\">;\n  resetAfterMs?: number;\n  onCopy?: (ok: boolean) => void;\n  className?: string;\n  /** Visible label; defaults to “Copy”. */\n  children?: ReactNode;\n}) {\n  const [state, setState] = useState<\"idle\" | \"copied\" | \"failed\">(\"idle\");\n  const fallback = useRef<HTMLInputElement>(null);\n\n  useEffect(() => {\n    if (state !== \"copied\") return;\n    const timer = setTimeout(() => setState(\"idle\"), resetAfterMs);\n    return () => clearTimeout(timer);\n  }, [state, resetAfterMs]);\n\n  useEffect(() => {\n    if (state === \"failed\") fallback.current?.select();\n  }, [state]);\n\n  async function copy() {\n    const ok = await writeClipboard(value);\n    setState(ok ? \"copied\" : \"failed\");\n    onCopy?.(ok);\n  }\n\n  const copied = state === \"copied\";\n  const iconClass = { sm: \"size-8 rounded-control max-md:size-11\", md: undefined, lg: \"size-11 rounded-touch\" }[size];\n\n  return (\n    <span\n      data-slot=\"copy-button\"\n      className={cn(\"relative inline-flex\", className)}\n      onKeyDown={(event) => {\n        if (event.key === \"Escape\" && state !== \"idle\") setState(\"idle\");\n      }}\n    >\n      <span data-slot=\"copy-ticket\" data-state={state} role={state === \"failed\" ? \"alert\" : \"status\"} aria-live={state === \"failed\" ? \"assertive\" : \"polite\"}>\n        {copied && (\n          <span data-slot=\"copy-ticket-body\">\n            <Check aria-hidden=\"true\" />\n            Copied <span className=\"font-mono text-[11px] opacity-75\">{display ?? shortenValue(value)}</span>\n          </span>\n        )}\n        {state === \"failed\" && (\n          <span data-slot=\"copy-ticket-body\" data-tone=\"error\">\n            <span>Couldn’t copy. The browser blocked the clipboard; press ⌘C or Ctrl+C.</span>\n            <input\n              ref={fallback}\n              readOnly\n              value={value}\n              aria-label={`${label}: value to copy by hand`}\n              onBlur={() => setState(\"idle\")}\n              className=\"w-full rounded-[6px] bg-background px-2 py-1 font-mono text-xs text-foreground outline-none\"\n            />\n          </span>\n        )}\n      </span>\n      <Button\n        variant={variant}\n        size={iconOnly ? \"icon\" : size}\n        aria-label={label}\n        data-copied={copied}\n        className={iconOnly ? iconClass : undefined}\n        onClick={copy}\n      >\n        <span data-slot=\"copy-icon\" data-copied={copied} aria-hidden=\"true\">\n          <Copy />\n          <Check />\n        </span>\n        {!iconOnly && (\n          <span data-slot=\"action-labels\">\n            <span data-slot=\"action-label\" data-active={!copied}>{children ?? \"Copy\"}</span>\n            <span data-slot=\"action-label\" data-active={copied}>Copied</span>\n          </span>\n        )}\n      </Button>\n    </span>\n  );\n}\n"},{"path":"ui/description-list.tsx","type":"registry:ui","target":"components/ui/description-list.tsx","content":"\"use client\";\n\nimport { Pencil } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { Button } from \"./button\";\nimport { CopyButton } from \"./copy-button\";\nimport { Input } from \"./input\";\nimport { cn } from \"./lib/cn\";\nimport \"./description-list.css\";\n\nexport type DescriptionItem = {\n  /** The label, e.g. \"Region\". */\n  term: string;\n  /** What is shown. Leave empty for \"Not set\". */\n  value?: ReactNode;\n  /** Set in mono with tabular figures: IDs, keys, amounts. */\n  mono?: boolean;\n  /** Adds a copy button for this text, e.g. an ID. */\n  copy?: string;\n  /** A short note under the value, e.g. \"Changed 2 days ago\". */\n  hint?: ReactNode;\n  /** Makes the value editable in place. `onSave` may return a promise; a thrown error keeps the draft and shows the message. */\n  edit?: { value: string; onSave: (value: string) => void | Promise<void>; type?: \"text\" | \"email\" | \"url\" | \"number\"; placeholder?: string };\n};\n\n/**\n * A record's details as label and value pairs: side by side when there is\n * room, stacked on a phone. Values can be copied or edited where they sit,\n * empty ones say so, and a loading record keeps its shape.\n */\nexport function DescriptionList({\n  items,\n  columns = 1,\n  loading = false,\n  title,\n  className,\n}: {\n  items: readonly DescriptionItem[];\n  /** Two pairs per row on wide containers. */\n  columns?: 1 | 2;\n  loading?: boolean;\n  /** A small heading over the list, e.g. \"Billing details\". */\n  title?: string;\n  className?: string;\n}) {\n  const titleId = useId();\n  return (\n    <section data-slot=\"description-list\" data-columns={columns} aria-labelledby={title ? titleId : undefined} aria-busy={loading || undefined} className={cn(\"@container min-w-0\", className)}>\n      {title && <h3 id={titleId} className=\"mb-2 text-sm font-semibold\">{title}</h3>}\n      <dl>\n        {items.map((item) => (\n          <div key={item.term} data-slot=\"description-row\">\n            <dt>{item.term}</dt>\n            <dd>{loading ? <span data-slot=\"description-skeleton\" aria-hidden=\"true\" /> : <Value item={item} />}</dd>\n          </div>\n        ))}\n      </dl>\n      {loading && <span className=\"sr-only\">Loading details</span>}\n    </section>\n  );\n}\n\nfunction Value({ item }: { item: DescriptionItem }) {\n  const [editing, setEditing] = useState(false);\n  const [draft, setDraft] = useState(item.edit?.value ?? \"\");\n  const [error, setError] = useState<string>();\n  const [saving, setSaving] = useState(false);\n  const field = useRef<HTMLInputElement>(null);\n  const editButton = useRef<HTMLButtonElement>(null);\n  const restore = useRef(false);\n  const errorId = useId();\n\n  useEffect(() => {\n    if (editing) field.current?.select();\n    else if (restore.current) {\n      restore.current = false;\n      editButton.current?.focus();\n    }\n  }, [editing]);\n\n  async function save() {\n    if (!item.edit) return;\n    if (draft === item.edit.value) return close();\n    setSaving(true);\n    setError(undefined);\n    try {\n      await item.edit.onSave(draft);\n      close();\n    } catch (reason) {\n      setError(reason instanceof Error ? reason.message : \"That could not be saved. Try again.\");\n      requestAnimationFrame(() => field.current?.focus());\n    } finally {\n      setSaving(false);\n    }\n  }\n\n  function close() {\n    restore.current = true;\n    setError(undefined);\n    setEditing(false);\n  }\n\n  if (editing && item.edit) {\n    return (\n      <form\n        data-slot=\"description-edit\"\n        onSubmit={(event) => {\n          event.preventDefault();\n          void save();\n        }}\n      >\n        <Input\n          ref={field}\n          aria-label={item.term}\n          type={item.edit.type ?? \"text\"}\n          value={draft}\n          placeholder={item.edit.placeholder}\n          disabled={saving}\n          aria-invalid={error ? true : undefined}\n          aria-describedby={error ? errorId : undefined}\n          onChange={(event) => setDraft(event.target.value)}\n          onKeyDown={(event) => {\n            if (event.key === \"Escape\") {\n              event.preventDefault();\n              close();\n            }\n          }}\n          className=\"h-8\"\n        />\n        <div className=\"flex gap-1.5\">\n          <Button type=\"submit\" size=\"sm\" loading={saving}>Save</Button>\n          <Button size=\"sm\" variant=\"ghost\" onClick={close} disabled={saving}>Cancel</Button>\n        </div>\n        {error && <p id={errorId} role=\"alert\" className=\"basis-full text-xs text-destructive\">{error}</p>}\n      </form>\n    );\n  }\n\n  const empty = item.value === undefined || item.value === null || item.value === \"\";\n  return (\n    <div data-slot=\"description-value\">\n      <div className=\"grid min-w-0 gap-0.5\">\n        <span data-mono={item.mono || undefined} data-empty={empty || undefined} className=\"min-w-0 [overflow-wrap:anywhere]\">\n          {empty ? (\n            <>\n              <span aria-hidden=\"true\">—</span>\n              <span className=\"sr-only\">Not set</span>\n            </>\n          ) : (\n            item.value\n          )}\n        </span>\n        {item.hint && <span className=\"text-xs text-muted-foreground\">{item.hint}</span>}\n      </div>\n      {(item.copy || item.edit) && (\n        <span data-slot=\"description-actions\">\n          {item.copy && <CopyButton value={item.copy} label={`Copy ${item.term.toLowerCase()}`} iconOnly variant=\"ghost\" />}\n          {item.edit && (\n            <Button\n              ref={editButton}\n              variant=\"ghost\"\n              size=\"icon\"\n              aria-label={`Edit ${item.term.toLowerCase()}`}\n              className=\"size-8\"\n              onClick={() => {\n                setDraft(item.edit!.value);\n                setEditing(true);\n              }}\n            >\n              <Pencil aria-hidden=\"true\" />\n            </Button>\n          )}\n        </span>\n      )}\n    </div>\n  );\n}\n"},{"path":"ui/input.tsx","type":"registry:ui","target":"components/ui/input.tsx","content":"import type { ComponentProps, ReactNode } from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/input.css\";\nimport \"./shared/input-2.css\";\nimport \"./shared/input-group.css\";\nimport \"./input.css\";\n\nconst fieldText =\n  \"w-full min-w-0 text-base text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm\";\n\nexport type InputProps = ComponentProps<\"input\"> & {\n  /** Fixed text or an icon before the value, e.g. \"caliper.app/\". */\n  leading?: ReactNode;\n  /** Text, an icon or a status after the value, e.g. a spinner or a count. */\n  trailing?: ReactNode;\n};\n\n/**\n * A recessed text field. Fields sit slightly below the paper (an inner shade\n * and a 3:1 ring); buttons stand on it, so what you fill and what you press\n * never look alike.\n */\nexport function Input({ className, leading, trailing, ...props }: InputProps) {\n  if (leading === undefined && trailing === undefined) {\n    return (\n      <input\n        data-slot=\"input\"\n        className={cn(\"flex h-[var(--control-height)] rounded-button px-3\", fieldText, className)}\n        {...props}\n      />\n    );\n  }\n  return (\n    <span\n      data-slot=\"input-group\"\n      data-disabled={props.disabled || undefined}\n      data-invalid={props[\"aria-invalid\"] === true || props[\"aria-invalid\"] === \"true\" || undefined}\n      data-readonly={props.readOnly || undefined}\n      className={cn(\"flex h-[var(--control-height)] w-full min-w-0 items-center gap-2 rounded-button px-3\", className)}\n    >\n      {leading !== undefined && <span data-slot=\"input-affix\">{leading}</span>}\n      <input data-slot=\"input-bare\" className={cn(\"h-full flex-1 bg-transparent\", fieldText)} {...props} />\n      {trailing !== undefined && <span data-slot=\"input-trailing\">{trailing}</span>}\n    </span>\n  );\n}\n"},{"path":"ui/lib/cn.ts","type":"registry:ui","target":"components/ui/lib/cn.ts","content":"import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n  extend: { theme: { radius: [\"control\", \"surface\", \"overlay\"], shadow: [\"overlay\"] } },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"},{"path":"ui/shared/loading-indicator.css","type":"registry:file","target":"components/ui/shared/loading-indicator.css","content":"/* Shared by button.tsx, confirm-morph.tsx, expanding-button-group.tsx, field.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"loading-indicator\"] {\n    animation: caliper-spin var(--duration-loading) var(--ease-linear) infinite;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"loading-indicator\"] {\n      animation: none;\n    }\n  }\n}\n"},{"path":"ui/button.css","type":"registry:file","target":"components/ui/button.css","content":"/* button.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"button\"] {\n    transition-property: background-color, color, border-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-out);\n  }\n\n  /* Button material. Raised to press: a lit top edge and a small lift; the\n     press travels 1px down and swaps the lift for an inner shade. Only\n     translate animates (release on the settle curve); shadows swap. */\n  [data-slot=\"button\"] {\n    --b-bg: var(--raised);\n    --b-bg-hover: var(--raised-hover);\n    --b-bg-press: var(--raised-press);\n    --b-fg: var(--foreground);\n    --b-shadow: var(--shadow-raised);\n    --b-shadow-press: var(--shadow-raised-press);\n    background-color: var(--b-bg);\n    color: var(--b-fg);\n    box-shadow: var(--b-shadow);\n    transition-property: background-color, color, border-color, translate, scale;\n    transition-duration:\n      var(--duration-fast), var(--duration-fast), var(--duration-fast),\n      var(--duration-normal), var(--duration-normal);\n    transition-timing-function:\n      var(--ease-out), var(--ease-out), var(--ease-out), var(--ease-settle),\n      var(--ease-settle);\n  }\n  [data-slot=\"button\"][data-variant=\"default\"] {\n    --b-bg: var(--primary);\n    --b-bg-hover: var(--primary-hover);\n    --b-bg-press: var(--primary-press);\n    --b-fg: var(--primary-foreground);\n    --b-shadow: var(--shadow-accent);\n    --b-shadow-press: var(--shadow-accent-press);\n  }\n  [data-slot=\"button\"][data-variant=\"destructive\"] {\n    --b-bg: var(--danger-fill);\n    --b-bg-hover: var(--danger-fill-hover);\n    --b-bg-press: var(--danger-fill-press);\n    --b-fg: var(--danger-fill-foreground);\n    --b-shadow: var(--shadow-danger);\n    --b-shadow-press: var(--shadow-danger-press);\n  }\n  [data-slot=\"button\"][data-variant=\"ghost\"] {\n    --b-bg: transparent;\n    --b-bg-hover: var(--muted);\n    --b-bg-press: var(--muted);\n    --b-fg: var(--muted-foreground);\n    --b-shadow: none;\n    --b-shadow-press: none;\n  }\n  @media (hover: hover) {\n    [data-slot=\"button\"]:hover {\n      background-color: var(--b-bg-hover);\n    }\n    [data-slot=\"button\"][data-variant=\"ghost\"]:hover {\n      color: var(--foreground);\n    }\n  }\n  [data-slot=\"button\"]:active:not(:disabled):not([aria-disabled=\"true\"]):not(\n      [aria-busy=\"true\"]\n    ) {\n    translate: 0 var(--motion-press-travel);\n    scale: var(--motion-press-scale);\n    background-color: var(--b-bg-press);\n    box-shadow: var(--b-shadow-press);\n    transition-duration:\n      var(--duration-fast), var(--duration-fast), var(--duration-fast), 90ms,\n      90ms;\n    transition-timing-function: var(--ease-out);\n  }\n  [data-slot=\"button\"]:is(:disabled, [aria-disabled=\"true\"]):not([aria-busy=\"true\"]) {\n    background-color: var(--sunken);\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"button\"][data-variant=\"ghost\"]:is(:disabled, [aria-disabled=\"true\"]):not(\n      [aria-busy=\"true\"]\n    ) {\n    background-color: transparent;\n    box-shadow: none;\n  }\n  /* Loading keeps the exact width: the label stays in flow but invisible. */\n  [data-slot=\"button-label\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: inherit;\n  }\n  /* Opacity, not visibility, so the label still names the button. */\n  [data-slot=\"button-label\"][data-hidden=\"true\"] {\n    opacity: 0;\n  }\n  [data-slot=\"button-busy\"] {\n    position: absolute;\n    inset: 0;\n    display: grid;\n    place-items: center;\n  }\n  [data-slot=\"button-feedback\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: inherit;\n    overflow: clip;\n    transition: width var(--duration-normal) var(--ease-out);\n  }\n  [data-slot=\"button-feedback-label\"] {\n    display: inline-flex;\n    flex-shrink: 0;\n    align-items: center;\n    gap: inherit;\n    width: max-content;\n  }\n  [data-slot=\"button-feedback-label\"][data-enter=\"true\"] {\n    animation: caliper-feedback-enter var(--duration-normal) var(--ease-out);\n  }\n\n  [data-slot=\"button\"][aria-busy=\"true\"] {\n    pointer-events: none;\n  }\n  [data-slot=\"button\"][data-action-phase=\"error\"] {\n    color: var(--danger);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"button\"],\n    [data-slot=\"button-feedback\"] {\n      transition: none;\n    }\n    [data-slot=\"button\"]:active:not(:disabled):not([aria-disabled=\"true\"]):not(\n        [aria-busy=\"true\"]\n      ) {\n      translate: none;\n    }\n    [data-slot=\"button-feedback-label\"],\n    [data-slot=\"button-feedback-label\"][data-enter=\"true\"] {\n      animation: none;\n    }\n  }\n\n  /* Toggle: pressed is recessed, as a toggled tool in a button group is. */\n  /* Four attributes, so it wins over the group and toolbar pressed rules in any stylesheet order. */\n  [data-slot=\"button\"][data-variant=\"ghost\"][data-toggle][aria-pressed=\"true\"] {\n    --b-bg: var(--raised-press);\n    --b-bg-hover: var(--raised-press);\n    --b-bg-press: var(--raised-press);\n    --b-fg: var(--foreground);\n    --b-shadow: var(--shadow-sunken);\n    --b-shadow-press: var(--shadow-sunken);\n  }\n}\n"},{"path":"ui/shared/action-label.css","type":"registry:file","target":"components/ui/shared/action-label.css","content":"/* Shared by action-button.tsx, copy-button.tsx, hold-to-confirm.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* Action button: every phase's label shares one grid cell, so the button\n     is always as wide as its widest label and never shifts. */\n  [data-slot=\"action-labels\"] {\n    display: inline-grid;\n    justify-items: center;\n  }\n  [data-slot=\"action-label\"] {\n    grid-area: 1 / 1;\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n    visibility: hidden;\n    opacity: 0;\n    transition: opacity var(--duration-fast) var(--ease-out), visibility 0s linear var(--duration-fast);\n  }\n  [data-slot=\"action-label\"][data-active=\"true\"] {\n    visibility: visible;\n    opacity: 1;\n    transition: opacity var(--duration-fast) var(--ease-out), visibility 0s;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"action-label\"],\n    [data-slot=\"action-label\"][data-active=\"true\"] {\n      transition: none !important;\n    }\n  }\n}\n"},{"path":"ui/copy-button.css","type":"registry:file","target":"components/ui/copy-button.css","content":"/* copy-button.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* Copy button: the icon swaps in place; a small inverse ticket says what\n     was copied. A failure keeps the ticket open with the value selected. */\n  [data-slot=\"copy-icon\"] {\n    position: relative;\n    display: inline-grid;\n    width: 16px;\n    height: 16px;\n  }\n  [data-slot=\"copy-icon\"] svg {\n    grid-area: 1 / 1;\n    transition:\n      opacity var(--duration-fast) var(--ease-out),\n      scale var(--duration-normal) var(--ease-settle);\n  }\n  [data-slot=\"copy-icon\"] svg:last-child,\n  [data-slot=\"copy-icon\"][data-copied=\"true\"] svg:first-child {\n    opacity: 0;\n    scale: 0.6;\n  }\n  [data-slot=\"copy-icon\"][data-copied=\"true\"] svg:last-child {\n    opacity: 1;\n    scale: 1;\n  }\n  [data-slot=\"copy-ticket\"] {\n    position: absolute;\n    right: 0;\n    bottom: calc(100% + 8px);\n    z-index: 30;\n    pointer-events: none;\n  }\n  [data-slot=\"copy-ticket-body\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n    padding: 6px 10px;\n    border-radius: 8px;\n    background: var(--primary);\n    color: var(--primary-foreground);\n    font-size: 12px;\n    line-height: 16px;\n    font-weight: 500;\n    white-space: nowrap;\n    box-shadow: var(--shadow-feedback);\n    animation: caliper-ticket-in var(--duration-normal) var(--ease-out);\n  }\n  [data-slot=\"copy-ticket-body\"] svg {\n    width: 14px;\n    height: 14px;\n  }\n  [data-slot=\"copy-ticket-body\"][data-tone=\"error\"] {\n    display: grid;\n    width: min(320px, calc(100vw - 32px));\n    gap: 8px;\n    padding: 10px;\n    white-space: normal;\n    background: var(--elevated);\n    color: var(--danger);\n    box-shadow: var(--shadow-overlay), inset 0 0 0 1px var(--border);\n    pointer-events: auto;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"copy-icon\"] svg {\n      transition: none;\n    }\n    [data-slot=\"copy-ticket-body\"] {\n      animation: none;\n    }\n  }\n  @keyframes caliper-ticket-in {\n    from {\n      opacity: 0;\n      translate: 0 4px;\n    }\n  }\n}\n"},{"path":"ui/description-list.css","type":"registry:file","target":"components/ui/description-list.css","content":"/* description-list.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* Description list: pairs side by side once there is room. */\n  [data-slot=\"description-list\"] dl { display: grid; margin: 0; }\n  [data-slot=\"description-row\"] { display: grid; gap: 2px; padding-block: 10px; border-bottom: 1px solid var(--border); min-width: 0; }\n  [data-slot=\"description-row\"]:last-child { border-bottom: 0; }\n  [data-slot=\"description-row\"] dt { font-size: 13px; color: var(--muted-foreground); }\n  [data-slot=\"description-row\"] dd { margin: 0; min-width: 0; font-size: 14px; line-height: 20px; color: var(--foreground); }\n  @container (min-width: 480px) {\n    [data-slot=\"description-row\"] { grid-template-columns: minmax(120px, 34%) minmax(0, 1fr); gap: 16px; align-items: start; }\n    [data-slot=\"description-row\"] dt { line-height: 20px; padding-top: 6px; }\n    [data-slot=\"description-row\"] dd { display: flex; min-height: 32px; padding-top: 6px; }\n  }\n  @container (min-width: 880px) {\n    [data-slot=\"description-list\"][data-columns=\"2\"] dl { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 32px; }\n    [data-slot=\"description-list\"][data-columns=\"2\"] [data-slot=\"description-row\"]:nth-last-child(2):nth-child(odd) { border-bottom: 0; }\n  }\n  [data-slot=\"description-value\"] { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; width: 100%; min-width: 0; }\n  [data-slot=\"description-row\"] dd > [data-slot=\"description-edit\"] { margin-top: -6px; }\n  [data-slot=\"description-value\"] [data-mono] { font-size: 13px; font-variant-numeric: tabular-nums; }\n  [data-slot=\"description-value\"] [data-empty] { color: var(--muted-foreground); }\n  [data-slot=\"description-actions\"] { display: inline-flex; flex: none; gap: 2px; margin-block: -6px; opacity: 0; transition: opacity var(--duration-fast) var(--ease-out); }\n  [data-slot=\"description-row\"]:is(:hover, :focus-within) [data-slot=\"description-actions\"] { opacity: 1; }\n  @media (hover: none) { [data-slot=\"description-actions\"] { opacity: 1; } }\n  [data-slot=\"description-edit\"] { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; min-width: 0; }\n  [data-slot=\"description-edit\"] > [data-slot=\"input\"], [data-slot=\"description-edit\"] > [data-slot=\"input-group\"] { flex: 1 1 180px; }\n  [data-slot=\"description-skeleton\"] { display: block; width: min(60%, 220px); height: 12px; margin-block: 4px; border-radius: 4px; background: var(--muted); animation: caliper-skeleton-pulse var(--duration-skeleton) var(--ease-in-out) infinite; }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"description-skeleton\"] { animation: none !important; }\n  }\n}\n"},{"path":"ui/shared/input.css","type":"registry:file","target":"components/ui/shared/input.css","content":"/* Shared by combobox.tsx, input.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"input\"] {\n    transition-property: background-color, color, border-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-out);\n  }\n\n  /* Recessed to fill. One ring for every field: 3:1 at rest, darker on\n     hover, a 2px ember ring with a soft halo on focus, danger when invalid.\n     Disabled and read-only drop into the sunken fill and stay legible. */\n  [data-slot=\"input\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"input\"] {\n      transition: none;\n    }\n  }\n}\n"},{"path":"ui/shared/input-2.css","type":"registry:file","target":"components/ui/shared/input-2.css","content":"/* Shared by combobox.tsx, input.tsx, select-menu.tsx, select.tsx, textarea.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  @media (hover: hover) {\n    :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"]):hover:enabled:not(:focus-visible):not([aria-invalid=\"true\"]) {\n      box-shadow: var(--shadow-field-hover);\n    }\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"]):focus-visible {\n    outline: none;\n    box-shadow: var(--shadow-field-focus);\n    transition: none;\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"])[aria-invalid=\"true\"] {\n    box-shadow: var(--shadow-field-invalid);\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"])[aria-invalid=\"true\"]:focus-visible {\n    box-shadow:\n      var(--shadow-field-invalid),\n      0 0 0 4px color-mix(in srgb, var(--danger) 18%, transparent);\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"]):disabled {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n    color: var(--muted-foreground);\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"])[readonly] {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n  }\n}\n"},{"path":"ui/shared/input-group.css","type":"registry:file","target":"components/ui/shared/input-group.css","content":"/* Shared by input.tsx, money-field.tsx, phone-field.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  @media (hover: hover) {\n    :is([data-slot=\"input-group\"], [data-slot=\"money-field\"], [data-slot=\"phone-field-control\"]):not([data-disabled]):not([data-invalid]):hover:not(:focus-within) {\n      box-shadow: var(--shadow-field-hover);\n    }\n  }\n  :is([data-slot=\"input-group\"], [data-slot=\"money-field\"], [data-slot=\"phone-field-control\"]):focus-within {\n    outline: none;\n    box-shadow: var(--shadow-field-focus);\n    transition: none;\n  }\n}\n"},{"path":"ui/input.css","type":"registry:file","target":"components/ui/input.css","content":"/* input.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* Recessed to fill. One ring for every field: 3:1 at rest, darker on\n     hover, a 2px ember ring with a soft halo on focus, danger when invalid.\n     Disabled and read-only drop into the sunken fill and stay legible. */\n  [data-slot=\"input-group\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"input-group\"][data-invalid] {\n    box-shadow: var(--shadow-field-invalid);\n  }\n  [data-slot=\"input-group\"][data-invalid]:focus-within {\n    box-shadow:\n      var(--shadow-field-invalid),\n      0 0 0 4px color-mix(in srgb, var(--danger) 18%, transparent);\n  }\n  [data-slot=\"input-group\"][data-disabled] {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"input-group\"][data-readonly] {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n  }\n  [data-slot=\"input-bare\"] {\n    border: 0;\n    box-shadow: none;\n    outline: none;\n  }\n  [data-slot=\"input-affix\"] {\n    display: inline-flex;\n    align-items: center;\n    height: 20px;\n    padding-inline-end: 10px;\n    margin-inline-end: 2px;\n    color: var(--muted-foreground);\n    box-shadow: 1px 0 0 var(--border);\n    white-space: nowrap;\n  }\n  [data-slot=\"input-affix\"]:dir(rtl) {\n    box-shadow: -1px 0 0 var(--border);\n  }\n  [data-slot=\"input-affix\"]:has(> svg:only-child) {\n    padding-inline-end: 0;\n    box-shadow: none;\n  }\n  [data-slot=\"input-trailing\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: 6px;\n    color: var(--muted-foreground);\n    white-space: nowrap;\n    font-size: 12px;\n  }\n  :is([data-slot=\"input-affix\"], [data-slot=\"input-trailing\"]) svg {\n    width: 16px;\n    height: 16px;\n  }\n  [data-slot=\"docs-toolbar\"] [data-slot=\"input-group\"] { max-width: 24rem; }\n}\n"}],"categories":["data"],"meta":{"tier":"free","license":"MIT","family":"records","docs":"/docs/components/description-list"}}