{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"description-list","type":"registry:ui","title":"Description List","description":"Terms and their values: a record's details, a summary, an invoice's totals.","dependencies":[],"registryDependencies":["https://dashral.com/r/utils.json"],"files":[{"path":"registry/dashral/description-list.tsx","type":"registry:file","target":"components/dashral/description-list.tsx","content":"import * as React from \"react\"\n\nimport { cn } from \"./lib/utils\"\n\ntype DescriptionItem = {\n  /** What the value is: \"Email\", \"Plan\", \"Due\". */\n  term: React.ReactNode\n  /** The value. Empty (undefined, null or \"\") shows the list's `empty` mark. */\n  description?: React.ReactNode\n  /** With layout=\"stacked\" and columns: the item takes the whole row, e.g. an address or notes. */\n  span?: \"full\"\n  /** A stable key; the index is used without one. */\n  key?: string\n}\n\ntype DescriptionListLayout = \"inline\" | \"justified\" | \"stacked\"\n\ntype DescriptionListProps = Omit<React.ComponentProps<\"dl\">, \"children\"> & {\n  items: DescriptionItem[]\n  /**\n   * inline (default): the term in a column of its own, the value beside it, as on a record's details. justified: the\n   * term at the start and the value at the far end, as in a summary or totals. stacked: the term above the value,\n   * in columns with `columns`.\n   */\n  layout?: DescriptionListLayout\n  /** With layout=\"stacked\": how many side by side from the sm breakpoint up (one on phones). */\n  columns?: 1 | 2 | 3 | 4\n  /** Lines between the items. */\n  divided?: boolean\n  /** With layout=\"inline\": the term column's width, any CSS length. */\n  termWidth?: string\n  /** sm for a dense panel or a popover: smaller text and gaps. */\n  size?: \"sm\" | \"default\"\n  /** In place of an empty value. A muted dash by default. */\n  empty?: React.ReactNode\n  classNames?: { item?: string; term?: string; description?: string }\n}\n\nconst columnClasses: Record<NonNullable<DescriptionListProps[\"columns\"]>, string> = {\n  1: \"\",\n  2: \"sm:grid-cols-2\",\n  3: \"sm:grid-cols-2 lg:grid-cols-3\",\n  4: \"sm:grid-cols-2 lg:grid-cols-4\",\n}\n\nconst isEmpty = (value: React.ReactNode) => value === undefined || value === null || value === \"\"\n\n/**\n * Terms and their values, in one tag: `<DescriptionList items={[{ term: \"Plan\", description: \"Pro\" }]} />`. A real\n * <dl>, so screen readers hear each value with its term. Values wrap rather than being cut off.\n */\nfunction DescriptionList({\n  items,\n  layout = \"inline\",\n  columns = 1,\n  divided = false,\n  termWidth = \"8rem\",\n  size = \"default\",\n  empty,\n  className,\n  classNames,\n  style,\n  ...props\n}: DescriptionListProps) {\n  const sm = size === \"sm\"\n  const emptyMark = empty ?? (\n    <span aria-hidden className=\"text-muted-foreground\">\n      —\n    </span>\n  )\n  const stackedColumns = layout === \"stacked\" && columns > 1\n\n  return (\n    <dl\n      data-slot=\"description-list\"\n      data-layout={layout}\n      style={layout === \"inline\" ? ({ \"--term-width\": termWidth, ...style } as React.CSSProperties) : style}\n      className={cn(\n        \"grid min-w-0\",\n        sm ? \"text-xs\" : \"text-sm\",\n        divided ? (stackedColumns ? (sm ? \"gap-x-4\" : \"gap-x-6\") : \"divide-y\") : sm ? \"gap-2\" : \"gap-3\",\n        stackedColumns && [columnClasses[columns], !divided && (sm ? \"gap-x-4\" : \"gap-x-6\")],\n        className\n      )}\n      {...props}\n    >\n      {items.map((item, i) => (\n        <div\n          key={item.key ?? i}\n          data-slot=\"description-item\"\n          className={cn(\n            \"min-w-0\",\n            layout === \"inline\" && \"grid grid-cols-[minmax(0,var(--term-width))_minmax(0,1fr)] gap-x-4 gap-y-1\",\n            layout === \"justified\" && \"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1\",\n            layout === \"stacked\" && \"grid content-start gap-1\",\n            item.span === \"full\" && \"sm:col-span-full\",\n            // Divided rows get their breathing room from padding, so the lines sit halfway between them; in columns,\n            // each item has a line on top instead, so the line runs under every column.\n            divided && (stackedColumns ? [\"border-t\", sm ? \"pt-2 pb-2\" : \"pt-3 pb-3\"] : sm ? \"py-2 first:pt-0 last:pb-0\" : \"py-3 first:pt-0 last:pb-0\"),\n            classNames?.item\n          )}\n        >\n          <dt className={cn(\"min-w-0 text-muted-foreground\", classNames?.term)}>{item.term}</dt>\n          <dd\n            className={cn(\n              \"min-w-0 break-words\",\n              layout === \"justified\" && \"text-end\",\n              layout === \"stacked\" && \"font-medium\",\n              classNames?.description\n            )}\n          >\n            {isEmpty(item.description) ? emptyMark : item.description}\n          </dd>\n        </div>\n      ))}\n    </dl>\n  )\n}\n\nexport { DescriptionList, type DescriptionListProps, type DescriptionItem, type DescriptionListLayout }\n"}]}