{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-line",
  "title": "Command line",
  "description": "One line to type, on a baseline like a field already filled in. The address recedes, the part that's yours is italic, and copying draws the line in the accent.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json",
    "https://thedirectors.agency/ui/r/radio-group.json",
    "https://thedirectors.agency/ui/r/source.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/command-line.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\"\nimport { CopyButton } from \"@/components/ui/source\"\n\n/** What runs a package without installing it, per package manager. */\nconst RUNNERS = { npm: \"npx\", pnpm: \"pnpm dlx\", yarn: \"yarn\", bun: \"bunx --bun\" } as const\ntype Runner = keyof typeof RUNNERS\nconst STORE = \"0db-runner\"\n\n// Every command line on the page shares one choice, and the next visit remembers it.\nlet chosen: Runner | undefined\nconst heard = new Set<() => void>()\nconst isRunner = (v: unknown): v is Runner => typeof v === \"string\" && v in RUNNERS\nfunction current(): Runner {\n  if (chosen) return chosen\n  try {\n    const stored = localStorage.getItem(STORE)\n    if (isRunner(stored)) return stored\n  } catch {} // private mode: the choice lasts the visit\n  return \"npm\"\n}\nfunction subscribe(notify: () => void) {\n  heard.add(notify)\n  addEventListener(\"storage\", notify)\n  return () => {\n    heard.delete(notify)\n    removeEventListener(\"storage\", notify)\n  }\n}\nfunction choose(next: Runner) {\n  chosen = next\n  try {\n    localStorage.setItem(STORE, next)\n  } catch {}\n  heard.forEach((notify) => notify())\n}\n\ntype CommandLineProps = Omit<React.ComponentProps<\"figure\">, \"children\"> & {\n  /** The command, without a runner when `runner` is set: \"shadcn@latest add https://…/button.json\". */\n  command: string\n  /** Run it through a package manager the reader picks (npx, pnpm dlx, yarn, bunx). The pick is shared and remembered. */\n  runner?: boolean\n  /** The part that's the reader's, set in italic: an item's name inside a URL. */\n  emphasis?: string\n  /**\n   * line (the default): one line on a baseline. parsed: each word glossed beneath in small type, on a short\n   * rule under the word. synopsis: the emphasis is a blank the reader fills in, and Copy copies what they wrote.\n   */\n  variant?: \"line\" | \"parsed\" | \"synopsis\"\n  /** parsed: a gloss for each word of the command, in order; leave one empty to skip that word. */\n  glosses?: string[]\n  /** synopsis: names the blank for assistive tech, e.g. \"Item name\". */\n  blank?: string\n}\n\n/**\n * One line to type, set on a baseline like a field already filled in. The runner and the address\n * recede to pencil; the words that matter are ink, and the part that's yours is italic.\n * Copying draws the baseline in the accent.\n */\nfunction CommandLine({ command, runner = false, emphasis, variant = \"line\", glosses, blank = \"Your part\", className, ...props }: CommandLineProps) {\n  const pick = React.useSyncExternalStore(subscribe, current, () => \"npm\" as Runner)\n  const prefix = runner ? `${RUNNERS[pick]} ` : \"\"\n  const [copied, setCopied] = React.useState(0)\n  const [filled, setFilled] = React.useState(emphasis ?? \"\")\n  const synopsis = variant === \"synopsis\" && !!emphasis\n  const typed = synopsis ? command.replace(emphasis!, filled.trim() || emphasis!) : command\n  const yours = (part: string) =>\n    synopsis ? (\n      <input\n        className=\"ot-command-line-blank\"\n        aria-label={blank}\n        value={filled}\n        placeholder={part}\n        size={Math.max(filled.length, part.length, 1)}\n        spellCheck={false}\n        autoCapitalize=\"off\"\n        autoComplete=\"off\"\n        onChange={(e) => setFilled(e.target.value.replace(/\\s/g, \"\"))}\n      />\n    ) : (\n      <em className=\"ot-command-line-yours\">{part}</em>\n    )\n  const parsed = variant === \"parsed\" && glosses?.length\n  return (\n    <figure data-slot=\"command-line\" data-variant={variant} className={cn(\"ot-command-line\", className)} {...props}>\n      {runner ? (\n        <RadioGroup aria-label=\"Package manager\" className=\"ot-command-line-runners\" value={pick} onValueChange={(v) => isRunner(v) && choose(v)}>\n          {(Object.keys(RUNNERS) as Runner[]).map((r) => (\n            <RadioGroupItem key={r} value={r}>\n              {r}\n            </RadioGroupItem>\n          ))}\n        </RadioGroup>\n      ) : null}\n      <div data-copied={copied ? (copied % 2 ? \"a\" : \"b\") : undefined} className=\"ot-command-line-row\">\n        <code data-slot=\"command-line-text\" className=\"ot-command-line-text\" dir=\"ltr\">\n          {prefix ? <span className=\"ot-command-line-quiet\">{prefix}</span> : null}\n          {words(command, emphasis, yours, parsed ? glosses : undefined)}\n        </code>\n        {parsed ? (\n          <span className=\"ot-sr\">\n            {command\n              .split(/\\s+/)\n              .map((w, i) => (glosses[i] ? `${w}: ${glosses[i]}` : \"\"))\n              .filter(Boolean)\n              .join(\". \")}\n          </span>\n        ) : null}\n        <CopyButton text={prefix + typed} onCopied={() => setCopied((c) => c + 1)} />\n      </div>\n    </figure>\n  )\n}\n\n/** Addresses recede, all but the emphasis; a long one breaks after a slash. */\nfunction words(command: string, emphasis: string | undefined, yours: (part: string) => React.ReactNode, glosses?: string[]) {\n  let n = -1\n  return command.split(/(\\s+)/).map((word, i) => {\n    if (/^\\s+$/.test(word)) return <React.Fragment key={i}>{word}</React.Fragment>\n    n++\n    const set = address(word, emphasis, yours)\n    // parsed: the word stands over a short rule, its gloss hung beneath (aria-hidden; the list is read after).\n    if (glosses?.[n])\n      return (\n        <span key={i} className=\"ot-command-line-word\">\n          <span className=\"ot-command-line-said\">{set}</span>\n          <span className=\"ot-command-line-gloss\" aria-hidden=\"true\">\n            {glosses[n]}\n          </span>\n        </span>\n      )\n    return <React.Fragment key={i}>{set}</React.Fragment>\n  })\n}\n\nfunction address(word: string, emphasis: string | undefined, yours: (part: string) => React.ReactNode) {\n  const at = emphasis ? word.lastIndexOf(emphasis) : -1\n  if (!/^[a-z]+:\\/\\//.test(word)) {\n    if (at < 0) return word\n    return (\n      <>\n        {word.slice(0, at)}\n        {yours(emphasis!)}\n        {word.slice(at + emphasis!.length)}\n      </>\n    )\n  }\n  const before = at < 0 ? word : word.slice(0, at)\n  return (\n    <span className=\"ot-command-line-quiet\">\n      {breakable(before)}\n      {at < 0 ? null : (\n        // The name and what follows it (\".json\") hold together: no line starts with \".json\" or \"card\".\n        <span className=\"ot-command-line-whole\">\n          {yours(emphasis!)}\n          {word.slice(at + emphasis!.length)}\n        </span>\n      )}\n    </span>\n  )\n}\n\n// A break may follow a slash, but never falls inside \"//\".\nconst breakable = (s: string) =>\n  s.split(/(?<=\\/)(?!\\/)/).map((part, i) => (\n    <React.Fragment key={i}>\n      {i ? <wbr /> : null}\n      {part}\n    </React.Fragment>\n  ))\n\nexport { CommandLine, type CommandLineProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/command-line.css",
      "content": "@layer components {\n  /* ── ot-command-line ── one line to type, on a baseline like a field already filled in.\n     The runner and the address recede to pencil; the part that's yours is italic.\n     Copying draws the baseline in the accent, left to right. */\n  .ot-command-line { margin: 0; min-width: 0; }\n  .ot-command-line-runners.ot-choice {\n    font-size: var(--ot-pp); line-height: var(--ot-pp-lh); column-gap: var(--ot-space-4);\n    padding-block-end: var(--ot-space-3); margin-block-end: var(--ot-space-3);\n  }\n  .ot-command-line-row {\n    position: relative; display: flex; align-items: baseline; gap: var(--ot-space-5);\n    padding-block-end: var(--ot-space-3);\n    background: linear-gradient(var(--ot-rule-strong), var(--ot-rule-strong)) no-repeat 0 100% / 100% var(--ot-hairline);\n  }\n  .ot-command-line-row::after {\n    content: \"\"; position: absolute; inset-inline: 0; inset-block-end: 0; block-size: var(--ot-stroke);\n    background: var(--ot-accent); transform-origin: 0 0; scale: 0 1; opacity: 0; pointer-events: none;\n  }\n  .ot-command-line-row:is([dir=\"rtl\"], [dir=\"rtl\"] *)::after { transform-origin: 100% 0; }\n  /* The row is never remounted (that would drop Copy's own \"Copied\" and its focus), so each copy flips between two identical\n     keyframes to start the line again. */\n  .ot-command-line-row[data-copied=\"a\"]::after { animation: ot-command-line-draw var(--ot-adagio) var(--ot-exhale) both; }\n  .ot-command-line-row[data-copied=\"b\"]::after { animation: ot-command-line-draw-again var(--ot-adagio) var(--ot-exhale) both; }\n  @keyframes ot-command-line-draw {\n    0%   { opacity: 1; scale: 0 1; }\n    45%  { opacity: 1; scale: 1 1; }\n    100% { opacity: 0; scale: 1 1; }\n  }\n  @keyframes ot-command-line-draw-again {\n    0%   { opacity: 1; scale: 0 1; }\n    45%  { opacity: 1; scale: 1 1; }\n    100% { opacity: 0; scale: 1 1; }\n  }\n  .ot-command-line-text {\n    flex: 1; min-width: 0; padding-inline-start: 1.5em; text-indent: -1.5em; overflow-wrap: break-word;\n    font-family: var(--ot-voice); font-size: var(--ot-mp); line-height: var(--ot-mp-lh); letter-spacing: var(--ot-mp-tr);\n    font-stretch: 94%; color: var(--ot-ink);\n  }\n  .ot-command-line-quiet { color: var(--ot-pencil); }\n  /* The name and its \".json\" move to a new line whole; a name wider than the line itself breaks after a hyphen. */\n  .ot-command-line-whole { display: inline-block; max-inline-size: 100%; text-indent: 0; }\n  .ot-command-line-yours {\n    font-family: var(--ot-expression); font-style: italic; font-weight: 400; font-stretch: 100%;\n    font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1; color: var(--ot-ink);\n  }\n  .ot-command-line-row > .ot-source-copy { flex: none; margin: 0; font-size: var(--ot-pp); }\n\n  /* parsed: the interlinear gloss (a linguist's word-by-word gloss; the dictionary entry of \"the uncreative\",\n     the part of speech set small under the word). Each glossed word stands over a short rule, its gloss hung\n     beneath in small type; the words keep one baseline and the line teaches itself. Pointing at a word inks\n     its rule and its gloss. */\n  .ot-command-line[data-variant=\"parsed\"] .ot-command-line-text { line-height: calc(var(--ot-mp-lh) + 0.2); }\n  .ot-command-line-word { display: inline-flex; flex-direction: column; max-inline-size: 100%; vertical-align: baseline; text-indent: 0; }\n  .ot-command-line-said { align-self: flex-start; padding-block-end: 0.1em; border-block-end: var(--ot-hairline) solid var(--ot-rule-strong); transition: border-color var(--ot-allegro); }\n  .ot-command-line-gloss {\n    padding-block: var(--ot-space-1) var(--ot-space-2); padding-inline-end: var(--ot-space-4); font-family: var(--ot-voice); font-stretch: 100%;\n    font-size: var(--ot-pp); line-height: var(--ot-pp-lh); letter-spacing: var(--ot-pp-tr); color: var(--ot-pencil);\n    white-space: nowrap; transition: color var(--ot-allegro);\n  }\n  .ot-command-line-word:hover > .ot-command-line-said, .ot-command-line[data-force~=\"hover\"] .ot-command-line-word:first-child > .ot-command-line-said { border-block-end-color: var(--ot-ink); }\n  .ot-command-line-word:hover > .ot-command-line-gloss, .ot-command-line[data-force~=\"hover\"] .ot-command-line-word:first-child > .ot-command-line-gloss { color: var(--ot-ink); }\n\n  /* synopsis: a manual page's synopsis, where the words you replace are set in italic. Here that part is a\n     blank you write in, in your italic; it grows with what you write, and Copy copies your line. Focus draws\n     the accent under your part only. */\n  .ot-command-line-blank {\n    appearance: none; margin: 0; padding: 0 0.05em; border: 0; border-radius: 0; background: none; outline: 0;\n    field-sizing: content; min-inline-size: 2ch;\n    font-family: var(--ot-expression); font-style: italic; font-weight: 400; font-stretch: 100%;\n    font-size: calc(var(--ot-expression-scale) * 1em); line-height: 1; letter-spacing: 0; color: var(--ot-ink);\n    box-shadow: inset 0 calc(-1 * var(--ot-hairline)) 0 var(--ot-ink);\n    transition: box-shadow var(--ot-allegro);\n  }\n  .ot-command-line[data-force~=\"focus\"] { outline: none; }\n  .ot-command-line-blank::placeholder { color: var(--ot-pencil); opacity: 1; }\n  .ot-command-line-blank:focus-visible, .ot-command-line[data-force~=\"focus\"] .ot-command-line-blank { box-shadow: inset 0 calc(-1 * var(--ot-stroke)) 0 var(--ot-accent); }\n\n  @media (forced-colors: active) {\n    .ot-command-line-blank:focus-visible { outline: 2px solid Highlight; }\n    .ot-command-line-said { border-block-end-color: CanvasText; }\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/command-line.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/command-line.css\"": {}
  },
  "meta": {
    "movement": "X",
    "underneath": "hook",
    "contract": "ot-command-line"
  },
  "type": "registry:ui"
}
