{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command",
  "title": "Command",
  "description": "What you type is set as large as a headline, in italic. Matches are marked; the chosen row steps forward. Or every row set on one axis at its match, as a mesostic; or the groups run on, as a book's index.",
  "dependencies": [
    "@radix-ui/react-dialog",
    "cmdk"
  ],
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json",
    "https://thedirectors.agency/ui/r/scrollbar.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/command.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Command as CommandPrimitive } from \"cmdk\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\n\nimport { useComposedRefs } from \"@/lib/0nlytype/refs\"\nimport { cn } from \"@/lib/utils\"\nimport { Scrollbar } from \"@/components/ui/scrollbar\"\n\nconst SearchContext = React.createContext(\"\")\nconst SearchSetter = React.createContext<(search: string) => void>(() => {})\n\n// A row matches when the words you typed sit inside its value or keywords.\n// ponytail: substring, not cmdk's fuzzy score, so the highlighter can mark exactly what matched.\nconst contains = (value: string, search: string, keywords?: string[]) =>\n  [value, ...(keywords ?? [])].join(\" \").toLowerCase().includes(search.trim().toLowerCase()) ? 1 : 0\n\n// cmdk brings the chosen row (and its group's heading) into view whenever the choice changes, including as it mounts,\n// which scrolls the page to a palette that opens with a row chosen. It now does so only while you're in the palette.\nfunction scrollOnlyWhileIn(node: HTMLElement | null) {\n  if (!node) return\n  node.scrollIntoView = (arg?: boolean | ScrollIntoViewOptions) => {\n    if (node.closest(\"[cmdk-root]\")?.contains(document.activeElement)) Element.prototype.scrollIntoView.call(node, arg)\n  }\n}\nconst headingOnlyWhileIn = (group: HTMLDivElement | null) => scrollOnlyWhileIn(group?.querySelector<HTMLElement>(\":scope > [cmdk-group-heading]\") ?? null)\n\n/** The highlighter: wraps each place the query appears in <mark>. */\nfunction marks(text: string, q: string): React.ReactNode {\n  if (!q) return text\n  const lower = text.toLowerCase()\n  const out: React.ReactNode[] = []\n  let at = 0\n  for (let i = lower.indexOf(q); i >= 0; i = lower.indexOf(q, at)) {\n    if (i > at) out.push(text.slice(at, i))\n    out.push(<mark key={i}>{text.slice(i, i + q.length)}</mark>)\n    at = i + q.length\n  }\n  out.push(text.slice(at))\n  return out\n}\n\n/** The name split at its first match: what comes before, then the key (the match onward). The mesostic sets the key on its axis; elsewhere the two run on. */\nfunction marked(text: string, query: string): React.ReactNode {\n  const q = query.trim().toLowerCase()\n  const at = Math.max(q ? text.toLowerCase().indexOf(q) : 0, 0)\n  return (\n    <>\n      <span className=\"ot-command-before\">{text.slice(0, at)}</span>\n      <span className=\"ot-command-key\">{marks(text.slice(at), q)}</span>\n    </>\n  )\n}\n\ntype CommandProps = React.ComponentProps<typeof CommandPrimitive> & {\n  /** headline: what you type set large, rows below. mesostic: every row set on one axis at its match. index: each group run on as a book's index. */\n  variant?: \"headline\" | \"mesostic\" | \"index\"\n  /** What the input holds when it opens, for a palette that starts from a query. */\n  defaultSearch?: string\n}\n\n/** A command palette. What you type is set large and italic; matches are marked; the chosen row steps forward. */\nfunction Command({ className, filter = contains, variant = \"headline\", defaultSearch = \"\", ...props }: CommandProps) {\n  const [search, setSearch] = React.useState(defaultSearch)\n  return (\n    <SearchContext.Provider value={search}>\n      <SearchSetter.Provider value={setSearch}>\n        <CommandPrimitive data-slot=\"command\" data-variant={variant} filter={filter} className={cn(\"ot-command\", className)} {...props} />\n      </SearchSetter.Provider>\n    </SearchContext.Provider>\n  )\n}\n/** What you type is yours, so it is italic and as large as a headline. Give it a placeholder that names what to type. */\nfunction CommandInput({ className, onValueChange, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  const search = React.useContext(SearchContext)\n  const setSearch = React.useContext(SearchSetter)\n  return (\n    <CommandPrimitive.Input\n      data-slot=\"command-input\"\n      aria-label={props[\"aria-label\"] ?? props.placeholder}\n      value={search}\n      onValueChange={(next) => {\n        setSearch(next)\n        onValueChange?.(next)\n      }}\n      className={cn(\"ot-command-input\", className)}\n      {...props}\n    />\n  )\n}\n\n/** The rows, on the scrollbar's rail when there are more than fit. */\nfunction CommandList({ className, children, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List data-slot=\"command-list\" className={cn(\"ot-command-list\", className)} {...props}>\n      {children}\n      <Scrollbar />\n    </CommandPrimitive.List>\n  )\n}\n\n/** Empty copy names something to try. */\nfunction CommandEmpty({ className, children, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  const search = React.useContext(SearchContext)\n  return (\n    <CommandPrimitive.Empty data-slot=\"command-empty\" className={cn(\"ot-command-empty\", className)} {...props}>\n      {children ?? <>Nothing matches “{search}”.</>}\n    </CommandPrimitive.Empty>\n  )\n}\n\nfunction CommandGroup({ className, ref, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  const heading = useComposedRefs(headingOnlyWhileIn, ref)\n  return <CommandPrimitive.Group ref={heading} data-slot=\"command-group\" className={cn(\"ot-command-group\", className)} {...props} />\n}\n\nfunction CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return <CommandPrimitive.Separator data-slot=\"command-separator\" className={cn(\"ot-command-separator\", className)} {...props} />\n}\n\n/** A row. Words in it are set as the name and marked where they match; other parts (CommandHint, CommandShortcut) sit at the end. */\nfunction CommandItem({ className, children, ref, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  const search = React.useContext(SearchContext)\n  const row = useComposedRefs(scrollOnlyWhileIn, ref)\n  return (\n    <CommandPrimitive.Item ref={row} data-slot=\"command-item\" className={cn(\"ot-command-option\", className)} {...props}>\n      {React.Children.map(children, (child) =>\n        typeof child === \"string\" ? <span className=\"ot-command-name\">{marked(child, search)}</span> : child,\n      )}\n    </CommandPrimitive.Item>\n  )\n}\n\n/** A key that runs the row, drawn as a cap (base ot-kbd). */\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"kbd\">) {\n  return <kbd data-slot=\"command-shortcut\" className={cn(\"ot-kbd\", className)} {...props} />\n}\n\n/** A quiet word at the end of a row, when it isn't a key: a year, a kind. */\nfunction CommandHint({ className, ...props }: React.ComponentProps<\"span\">) {\n  return <span data-slot=\"command-hint\" className={cn(\"ot-command-hint\", className)} {...props} />\n}\n\ntype CommandDialogProps = CommandProps & {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  /** Read out by screen readers; not shown. */\n  title?: string\n  /** Where focus goes when the palette closes. By default it returns to what had focus when it opened, which is the page itself if a key opened it; preventDefault and focus your trigger instead. */\n  onCloseAutoFocus?: (event: Event) => void\n}\n\n/** The ⌘K palette: a modal over the page. Register the key yourself; this only opens where you tell it. */\nfunction CommandDialog({ open, onOpenChange, title = \"Search\", onCloseAutoFocus, children, ...props }: CommandDialogProps) {\n  return (\n    <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n      <DialogPrimitive.Portal>\n        <DialogPrimitive.Overlay data-slot=\"command-overlay\" className=\"ot-command-overlay\" />\n        <DialogPrimitive.Content data-slot=\"command-dialog\" aria-describedby={undefined} onCloseAutoFocus={onCloseAutoFocus} className=\"ot-command-dialog ot-corners\">\n          <DialogPrimitive.Title className=\"ot-sr\">{title}</DialogPrimitive.Title>\n          <Command {...props}>{children}</Command>\n        </DialogPrimitive.Content>\n      </DialogPrimitive.Portal>\n    </DialogPrimitive.Root>\n  )\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandSeparator,\n  CommandShortcut,\n  CommandHint,\n  type CommandProps,\n  type CommandDialogProps,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/command.css",
      "content": "@layer components {\n  /* ── ot-command ── what you type is set as large as a headline.\n     cmdk marks the chosen row aria-selected; rows it filters out are removed, so a\n     row that comes back arrives again. The dialog is Radix's: an overlay like the\n     dialog's backdrop, and a frame that opens out from inside. */\n  .ot-command { display: grid; gap: var(--ot-space-4); }\n  .ot-command-input {\n    appearance: none; width: 100%; margin: 0; border: 0; border-radius: 0; outline: 0; padding: 0 0 0.12em;\n    background: linear-gradient(var(--ot-rule-strong), var(--ot-rule-strong)) no-repeat 0 100% / 100% var(--ot-hairline);\n    color: var(--ot-ink); font-family: var(--ot-expression); font-style: italic; font-weight: 400; text-overflow: ellipsis;\n    font-size: calc(var(--ot-f) * var(--ot-expression-scale)); line-height: 1.15;\n  }\n  .ot-command-input::placeholder { font-family: var(--ot-voice); font-style: normal; font-weight: 200; font-size: var(--ot-f); color: var(--ot-pencil); opacity: 1; }\n  .ot-command-input:focus-visible { background-image: linear-gradient(var(--ot-accent), var(--ot-accent)); background-size: 100% var(--ot-stroke); }\n  .ot-command-list { max-height: 22rem; overflow: auto; padding-inline-end: var(--ot-space-4); } /* the rows stop short of the scrollbar's rail */\n  .ot-command-group [cmdk-group-heading] { margin-block: var(--ot-space-4) var(--ot-space-1); margin-inline: 0; font-size: var(--ot-pp); line-height: var(--ot-pp-lh); color: var(--ot-pencil); }\n  .ot-command-separator { height: var(--ot-hairline); margin-block: var(--ot-space-3); background: var(--ot-rule); }\n  .ot-command-option {\n    display: grid; grid-template-columns: minmax(0, 1fr); grid-auto-flow: column; grid-auto-columns: auto; align-items: baseline; column-gap: var(--ot-space-4);\n    --step: 8px; padding-block: 0.4em; border-block-end: var(--ot-hairline) solid var(--ot-rule); cursor: pointer; color: var(--ot-graphite);\n    animation: ot-arrive var(--ot-moderato) var(--ot-exhale) backwards; animation-delay: calc(min(sibling-index(), 9) * var(--ot-arpeggio));\n  }\n  .ot-command-option::after { content: \"→\" / \"\"; inline-size: 1.2em; justify-self: end; color: var(--ot-ink); opacity: 0; translate: calc(-1 * var(--step)) 0; transition: translate var(--ot-moderato) var(--ot-exhale), opacity var(--ot-allegro); }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *) .ot-command-option { --step: -8px; }\n  :is([dir=\"rtl\"], [dir=\"rtl\"] *) .ot-command-option::after { content: \"←\" / \"\"; }\n  .ot-command-option > * { transition: translate var(--ot-moderato) var(--ot-exhale), opacity var(--ot-allegro), color var(--ot-allegro); }\n  .ot-command-name { font-size: var(--ot-mp); line-height: var(--ot-mp-lh); }\n  .ot-command-hint { font-size: var(--ot-pp); color: var(--ot-pencil); }\n  .ot-command-option:hover, .ot-command-option[aria-selected=\"true\"] { color: var(--ot-ink); }\n  .ot-command-option[aria-selected=\"true\"] .ot-command-name { translate: var(--step) 0; }\n  .ot-command-option[aria-selected=\"true\"]::after { opacity: 1; translate: 0 0; }\n  .ot-command-option[aria-disabled=\"true\"] { cursor: not-allowed; opacity: 0.5; }\n  .ot-command-option mark { padding: 0; margin: 0; } /* part of a word: no overshoot, or it would crowd the letters either side */\n  .ot-command-empty { padding-block: var(--ot-space-4); font-size: var(--ot-mp); color: var(--ot-graphite); }\n\n  /* ── mesostic ── John Cage's mesostics, the printed concordance: every row is set on one vertical axis at the place\n     your words appear. The found letters stand on the axis in roman capitals, the rest of the name falls away either side,\n     and what you type sits on the same axis above them, so the query reads straight down through the results.\n     The axis is the middle of the rows less the arrow's column; the input's text starts there too. */\n  .ot-command[data-variant=\"mesostic\"] { --axis-tail: var(--ot-space-6); }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-input { padding-inline-start: calc(50% - (var(--ot-space-4) + var(--axis-tail)) / 2); }\n  .ot-command[data-variant=\"mesostic\"] :is([cmdk-group-heading], .ot-command-empty) { padding-inline-start: calc(50% - var(--axis-tail) / 2); }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-option { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) var(--axis-tail); grid-auto-flow: row; column-gap: 0; }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-name { display: contents; }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-before { grid-area: 1 / 1; justify-self: end; min-width: 0; max-width: 100%; overflow: hidden; white-space: pre; font-size: var(--ot-mp); line-height: var(--ot-mp-lh); }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-key { grid-area: 1 / 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: pre; font-size: var(--ot-mp); line-height: var(--ot-mp-lh); }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-option > :not(.ot-command-name) { grid-area: 1 / 1; justify-self: start; }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-option::after { grid-area: 1 / 3; }\n  .ot-command[data-variant=\"mesostic\"] mark { background: none; color: inherit; margin: 0; }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-key > mark:first-child { color: var(--ot-ink); font-weight: 600; text-transform: uppercase; }\n  .ot-command[data-variant=\"mesostic\"] .ot-command-option:is(:hover, [aria-selected=\"true\"]) :is(.ot-command-before, .ot-command-key) { color: var(--ot-ink); }\n  @media (max-width: 40rem) {\n    .ot-command[data-variant=\"mesostic\"] { --axis-tail: var(--ot-space-5); }\n    .ot-command[data-variant=\"mesostic\"] .ot-command-option > :not(.ot-command-name) { grid-area: 2 / 2; }\n  }\n\n  /* ── index ── a book's index, run in: each group's name hangs in the margin as the headword, and its rows run on after it\n     as one paragraph, sub-entries parted by semicolons and closed with a full stop. A match is marked; the chosen entry is\n     underlined in ink. Rows that stop matching leave the sentence, which closes up. */\n  .ot-command[data-variant=\"index\"] .ot-command-group { display: grid; grid-template-columns: minmax(0, 7rem) minmax(0, 1fr); column-gap: var(--ot-space-5); align-items: baseline; margin-block-start: var(--ot-space-4); }\n  .ot-command[data-variant=\"index\"] .ot-command-group [cmdk-group-heading] { margin: 0; }\n  .ot-command[data-variant=\"index\"] .ot-command-group [cmdk-group-items] { font-size: var(--ot-mp); line-height: var(--ot-mp-lh); }\n  .ot-command[data-variant=\"index\"] .ot-command-option { display: inline; padding: 0; border: 0; animation-name: ot-command-fade; }\n  .ot-command[data-variant=\"index\"] .ot-command-option > * { margin-inline-start: 0.3em; }\n  .ot-command[data-variant=\"index\"] .ot-command-option > .ot-command-name { margin: 0; text-decoration: underline var(--ot-hairline) transparent; text-underline-offset: 0.2em; transition: text-decoration-color var(--ot-allegro), color var(--ot-allegro); }\n  .ot-command[data-variant=\"index\"] .ot-command-option::after { content: \";\\00a0\" / \"\"; inline-size: auto; opacity: 1; translate: none; color: var(--ot-pencil); }\n  .ot-command[data-variant=\"index\"] .ot-command-option:last-child::after { content: \".\" / \"\"; }\n  .ot-command[data-variant=\"index\"] .ot-command-option:is(:hover, [aria-selected=\"true\"]) > .ot-command-name { translate: none; text-decoration-color: currentColor; }\n  .ot-command[data-variant=\"index\"] .ot-command-separator { display: none; }\n  @media (max-width: 40rem) {\n    .ot-command[data-variant=\"index\"] .ot-command-group { grid-template-columns: minmax(0, 1fr); row-gap: var(--ot-space-1); }\n  }\n\n  /* The ⌘K dialog. */\n  .ot-command-overlay {\n    position: fixed; inset: 0; z-index: 50;\n    background: color-mix(in srgb, var(--ot-paper) 86%, transparent);\n    -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);\n    animation: ot-command-fade var(--ot-andante);\n  }\n  .ot-command-overlay[data-state=\"closed\"] { animation: ot-command-fade var(--ot-andante) reverse forwards; }\n  .ot-command-dialog {\n    position: fixed; z-index: 51; inset-inline: 0; inset-block-start: 12vh; margin-inline: auto;\n    width: min(44rem, calc(100vw - 2 * var(--ot-margin))); padding: var(--ot-space-7);\n    background: var(--ot-paper); color: var(--ot-graphite);\n    --ot-corner: 14px;\n    animation: ot-command-in var(--ot-andante) var(--ot-exhale);\n  }\n  .ot-command-dialog[data-state=\"closed\"] { animation: ot-command-out var(--ot-allegro) var(--ot-breath) forwards; }\n  .ot-command-dialog[data-state=\"open\"]::after { animation: ot-command-frame var(--ot-andante) var(--ot-exhale) 80ms backwards; }\n  .ot-command-dialog .ot-command-list { max-height: min(28rem, 50vh); }\n  @media (max-width: 40rem) {\n    .ot-command-dialog { padding: var(--ot-space-6) var(--ot-space-5); }\n    .ot-command-input { font-size: calc(var(--ot-mf) * var(--ot-expression-scale)); }\n    .ot-command-input::placeholder { font-size: var(--ot-mf); }\n  }\n  @keyframes ot-command-fade { from { opacity: 0; } }\n  @keyframes ot-command-in { from { opacity: 0; translate: 0 12px; } }\n  @keyframes ot-command-out { to { opacity: 0; } }\n  @keyframes ot-command-frame { from { opacity: 0; scale: 0.95; } }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/command.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/command.css\"": {}
  },
  "meta": {
    "movement": "VIII",
    "underneath": "cmdk",
    "contract": "ot-command"
  },
  "type": "registry:ui"
}
