{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dns-records-table",
  "title": "DNS Records Table",
  "description": "A compact, copy-ready DNS record table for customer-domain setup.",
  "dependencies": ["@hugeicons/core-free-icons", "@hugeicons/react", "@opencoredev/domain-sdk"],
  "registryDependencies": ["badge", "button", "native-select", "table"],
  "files": [
    {
      "path": "registry/domain/dns-records-table.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport type { DnsRecord, DomainIssue } from \"@opencoredev/domain-sdk\";\nimport {\n  Alert02Icon,\n  CheckmarkCircle02Icon,\n  Copy01Icon,\n  HelpCircleIcon,\n  TimeQuarter02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { NativeSelect, NativeSelectOption } from \"@/components/ui/native-select\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport {\n  filterDnsRecords,\n  issuesForRecord,\n  type DnsRecordFilter,\n} from \"@/components/domain/domain-registry-utils\";\n\nexport type DnsRecordCopyField = \"name\" | \"value\";\n\nexport interface DnsRecordsTableProps {\n  records: readonly DnsRecord[];\n  issues?: readonly DomainIssue[];\n  defaultFilter?: DnsRecordFilter;\n  onCopy?(content: string, record: DnsRecord, field: DnsRecordCopyField): void | Promise<void>;\n}\n\nconst statusMeta = {\n  valid: {\n    label: \"Valid\",\n    icon: CheckmarkCircle02Icon,\n    variant: \"outline\",\n    className:\n      \"border-transparent bg-primary/10 text-primary bg-[var(--dns-success)] text-[var(--dns-success-foreground)]\",\n  },\n  pending: {\n    label: \"Pending\",\n    icon: TimeQuarter02Icon,\n    variant: \"outline\",\n    className:\n      \"border-transparent bg-secondary text-secondary-foreground bg-[var(--dns-warning)] text-[var(--dns-warning-foreground)]\",\n  },\n  invalid: {\n    label: \"Invalid\",\n    icon: Alert02Icon,\n    variant: \"destructive\",\n    className: \"\",\n  },\n  unknown: {\n    label: \"Unknown\",\n    icon: HelpCircleIcon,\n    variant: \"outline\",\n    className: \"\",\n  },\n} as const;\n\nconst dnsStatusStyle = {\n  \"--dns-success\": \"light-dark(oklch(0.94 0.06 150), oklch(0.28 0.07 150))\",\n  \"--dns-success-foreground\": \"light-dark(oklch(0.34 0.12 150), oklch(0.84 0.13 150))\",\n  \"--dns-warning\": \"light-dark(oklch(0.95 0.07 85), oklch(0.29 0.07 75))\",\n  \"--dns-warning-foreground\": \"light-dark(oklch(0.4 0.11 70), oklch(0.86 0.13 85))\",\n} as React.CSSProperties;\n\nconst copyableFieldClassName = {\n  desktop:\n    \"h-auto min-h-12 w-full touch-manipulation justify-between rounded-none px-4 py-3 text-left whitespace-normal\",\n  mobile:\n    \"h-auto min-h-10 w-full touch-manipulation justify-between rounded-md px-2 py-2 text-left whitespace-normal\",\n} as const;\n\nconst copyFeedbackClassName = {\n  copied:\n    \"inline-flex shrink-0 items-center gap-1 text-xs font-medium text-foreground opacity-100 transition-opacity duration-150 motion-reduce:transition-none\",\n  idle: \"inline-flex shrink-0 items-center gap-1 text-xs font-medium text-foreground opacity-60 transition-opacity duration-150 motion-reduce:transition-none md:opacity-0 md:group-hover/button:opacity-100 md:group-focus-visible/button:opacity-100\",\n} as const;\n\nfunction RecordStatus({ status }: { status: DnsRecord[\"status\"] }) {\n  const meta = statusMeta[status];\n\n  return (\n    <Badge variant={meta.variant} className={meta.className}>\n      <HugeiconsIcon icon={meta.icon} data-icon=\"inline-start\" aria-hidden=\"true\" />\n      {meta.label}\n    </Badge>\n  );\n}\n\nfunction CopyableRecordField({\n  record,\n  field,\n  copied,\n  mobile = false,\n  onCopy,\n}: {\n  record: DnsRecord;\n  field: DnsRecordCopyField;\n  copied: boolean;\n  mobile?: boolean;\n  onCopy(record: DnsRecord, field: DnsRecordCopyField): void | Promise<void>;\n}) {\n  return (\n    <Button\n      type=\"button\"\n      variant=\"ghost\"\n      className={copyableFieldClassName[mobile ? \"mobile\" : \"desktop\"]}\n      onClick={() => void onCopy(record, field)}\n      aria-label={`Copy ${field} for ${record.name}`}\n      title={`Copy ${field === \"name\" ? \"Name\" : \"Value\"}`}\n    >\n      <code className=\"block min-w-0 break-all text-xs\" translate=\"no\">\n        {record[field]}\n      </code>\n      <span className={copyFeedbackClassName[copied ? \"copied\" : \"idle\"]} aria-hidden=\"true\">\n        <HugeiconsIcon\n          icon={copied ? CheckmarkCircle02Icon : Copy01Icon}\n          data-icon=\"inline-end\"\n          aria-hidden=\"true\"\n        />\n        {copied ? \"Copied\" : \"Copy\"}\n      </span>\n    </Button>\n  );\n}\n\nexport function DnsRecordsTable({\n  records,\n  issues = [],\n  defaultFilter = \"required\",\n  onCopy,\n}: DnsRecordsTableProps) {\n  const headingId = React.useId();\n  const [filter, setFilter] = React.useState<DnsRecordFilter>(defaultFilter);\n  const [copiedField, setCopiedField] = React.useState<string>();\n  const [announcement, setAnnouncement] = React.useState(\"\");\n  const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);\n  const visibleRecords = filterDnsRecords(records, filter);\n  const hasOptionalRecords = records.some((record) => !record.required);\n\n  React.useEffect(() => () => clearTimeout(timerRef.current), []);\n\n  function showCopied(key: string) {\n    setCopiedField(key);\n    clearTimeout(timerRef.current);\n    timerRef.current = setTimeout(() => setCopiedField(undefined), 1800);\n  }\n\n  function fieldKey(record: DnsRecord, field: DnsRecordCopyField) {\n    return `${field}:${record.type}:${record.name}:${record.value}`;\n  }\n\n  async function copyField(record: DnsRecord, field: DnsRecordCopyField) {\n    const content = record[field];\n\n    try {\n      if (onCopy) await onCopy(content, record, field);\n      else await navigator.clipboard.writeText(content);\n    } catch {\n      setAnnouncement(\n        `Could not copy the ${field} for ${record.name}. Check clipboard permissions, then try again.`,\n      );\n      return;\n    }\n\n    showCopied(fieldKey(record, field));\n    setAnnouncement(`Copied the ${field} for ${record.name}.`);\n  }\n\n  async function copyAll() {\n    const combined = visibleRecords\n      .map((record) => `${record.type}\\t${record.name}\\t${record.value}`)\n      .join(\"\\n\");\n\n    try {\n      await navigator.clipboard.writeText(combined);\n      showCopied(\"all\");\n      setAnnouncement(`Copied ${visibleRecords.length} DNS records.`);\n    } catch {\n      setAnnouncement(\n        \"Could not copy the DNS records. Check clipboard permissions, then try again.\",\n      );\n    }\n  }\n\n  return (\n    <section aria-labelledby={headingId} className=\"min-w-0 text-foreground\" style={dnsStatusStyle}>\n      <header className=\"mb-4 flex flex-wrap items-end justify-between gap-3\">\n        <div className=\"flex min-w-0 flex-col gap-1\">\n          <h2 id={headingId} className=\"text-sm font-semibold\">\n            DNS records\n          </h2>\n          <p className=\"text-sm text-muted-foreground\">\n            Click any name or value to copy it, then add the records at your DNS provider.\n          </p>\n        </div>\n        <div className=\"flex items-center gap-2\">\n          {hasOptionalRecords ? (\n            <NativeSelect\n              value={filter}\n              onChange={(event) => setFilter(event.target.value as DnsRecordFilter)}\n              aria-label=\"Filter DNS records\"\n              size=\"sm\"\n            >\n              <NativeSelectOption value=\"required\">Required</NativeSelectOption>\n              <NativeSelectOption value=\"all\">All records</NativeSelectOption>\n            </NativeSelect>\n          ) : null}\n          <Button\n            type=\"button\"\n            variant=\"outline\"\n            size=\"sm\"\n            disabled={visibleRecords.length === 0}\n            onClick={() => void copyAll()}\n          >\n            <HugeiconsIcon\n              icon={copiedField === \"all\" ? CheckmarkCircle02Icon : Copy01Icon}\n              data-icon=\"inline-start\"\n              aria-hidden=\"true\"\n            />\n            {copiedField === \"all\" ? \"Copied\" : \"Copy all\"}\n          </Button>\n        </div>\n      </header>\n\n      <div className=\"overflow-hidden rounded-lg border bg-background\">\n        <div className=\"hidden md:block\">\n          <Table>\n            <TableHeader className=\"bg-muted/40\">\n              <TableRow>\n                <TableHead className=\"h-9 w-24 px-4 text-xs text-muted-foreground\">Type</TableHead>\n                <TableHead className=\"h-9 w-[30%] px-4 text-xs text-muted-foreground\">\n                  Name\n                </TableHead>\n                <TableHead className=\"h-9 px-4 text-xs text-muted-foreground\">Value</TableHead>\n                <TableHead className=\"h-9 w-24 px-4 text-xs text-muted-foreground\">\n                  Status\n                </TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              {visibleRecords.length === 0 ? (\n                <TableRow>\n                  <TableCell\n                    colSpan={4}\n                    className=\"h-20 px-4 text-center text-sm text-muted-foreground\"\n                  >\n                    No DNS records in this view.\n                  </TableCell>\n                </TableRow>\n              ) : null}\n              {visibleRecords.map((record, index) => {\n                const key = `${record.type}:${record.name}:${record.value}`;\n                const recordIssues = issuesForRecord(issues, record);\n\n                return (\n                  <React.Fragment key={`${key}:${index}`}>\n                    <TableRow>\n                      <TableCell className=\"px-4 py-3\">\n                        <span className=\"text-xs font-semibold\">{record.type}</span>\n                      </TableCell>\n                      <TableCell className=\"p-0\">\n                        <CopyableRecordField\n                          record={record}\n                          field=\"name\"\n                          copied={copiedField === fieldKey(record, \"name\")}\n                          onCopy={copyField}\n                        />\n                      </TableCell>\n                      <TableCell className=\"p-0\">\n                        <CopyableRecordField\n                          record={record}\n                          field=\"value\"\n                          copied={copiedField === fieldKey(record, \"value\")}\n                          onCopy={copyField}\n                        />\n                      </TableCell>\n                      <TableCell className=\"px-4 py-3\">\n                        <RecordStatus status={record.status} />\n                      </TableCell>\n                    </TableRow>\n                    {recordIssues.map((issue) => (\n                      <TableRow\n                        key={`${key}:${issue.code}`}\n                        className=\"bg-destructive/5 hover:bg-destructive/5\"\n                      >\n                        <TableCell\n                          colSpan={4}\n                          className=\"whitespace-normal px-4 py-2.5 text-xs text-destructive\"\n                        >\n                          {issue.message} Update this record, then refresh the domain.\n                        </TableCell>\n                      </TableRow>\n                    ))}\n                  </React.Fragment>\n                );\n              })}\n            </TableBody>\n          </Table>\n        </div>\n\n        <div className=\"divide-y md:hidden\">\n          {visibleRecords.length === 0 ? (\n            <p className=\"px-4 py-8 text-center text-sm text-muted-foreground\">\n              No DNS records in this view.\n            </p>\n          ) : null}\n          {visibleRecords.map((record) => {\n            const key = `${record.type}:${record.name}:${record.value}`;\n            const recordIssues = issuesForRecord(issues, record);\n\n            return (\n              <article key={key} className=\"flex flex-col gap-3 px-4 py-4\">\n                <header className=\"flex items-center justify-between gap-3\">\n                  <span className=\"text-xs font-semibold\">{record.type}</span>\n                  <RecordStatus status={record.status} />\n                </header>\n                <dl className=\"grid gap-2\">\n                  <div className=\"grid gap-0.5\">\n                    <dt className=\"px-2 text-xs text-muted-foreground\">Name</dt>\n                    <dd>\n                      <CopyableRecordField\n                        record={record}\n                        field=\"name\"\n                        copied={copiedField === fieldKey(record, \"name\")}\n                        mobile\n                        onCopy={copyField}\n                      />\n                    </dd>\n                  </div>\n                  <div className=\"grid gap-0.5\">\n                    <dt className=\"px-2 text-xs text-muted-foreground\">Value</dt>\n                    <dd>\n                      <CopyableRecordField\n                        record={record}\n                        field=\"value\"\n                        copied={copiedField === fieldKey(record, \"value\")}\n                        mobile\n                        onCopy={copyField}\n                      />\n                    </dd>\n                  </div>\n                </dl>\n                {recordIssues.map((issue) => (\n                  <p key={issue.code} className=\"text-xs text-destructive\">\n                    {issue.message} Update this record, then refresh the domain.\n                  </p>\n                ))}\n              </article>\n            );\n          })}\n        </div>\n\n        <p className=\"border-t bg-muted/20 px-4 py-3 text-xs text-muted-foreground\">\n          DNS changes can take time to propagate. Keep the names and values exactly as shown.\n        </p>\n      </div>\n\n      <span className=\"sr-only\" aria-live=\"polite\">\n        {announcement}\n      </span>\n    </section>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/domain/dns-records-table.tsx"
    },
    {
      "path": "registry/domain/domain-registry-utils.ts",
      "content": "import type { DnsRecord, DomainIssue } from \"@opencoredev/domain-sdk\";\n\nexport type DnsRecordFilter = \"required\" | \"all\";\n\nexport function filterDnsRecords(\n  records: readonly DnsRecord[],\n  filter: DnsRecordFilter,\n): DnsRecord[] {\n  return filter === \"required\" ? records.filter((record) => record.required) : [...records];\n}\n\nexport function issuesForRecord(issues: readonly DomainIssue[], record: DnsRecord): DomainIssue[] {\n  return issues.filter(\n    (issue) =>\n      issue.record?.type === record.type &&\n      issue.record.name === record.name &&\n      issue.record.value === record.value &&\n      issue.record.purpose === record.purpose,\n  );\n}\n",
      "type": "registry:lib",
      "target": "components/domain/domain-registry-utils.ts"
    }
  ],
  "type": "registry:ui"
}
