{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "umm-materials",
  "title": "Umm Materials",
  "description": "Data-driven UMM material search and hierarchy browsing, class-only or designation selection, specification previews and explicit-basis density estimates. Consumers supply canonical versioned data; no built-in taxonomy or compliance inference.",
  "registryDependencies": [
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/domain/umm-materials/umm-density-calculator.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport type { UmmReferenceDensity } from \"./umm-types\"\n\nexport interface UmmDensityCalculatorProps {\n  density?: UmmReferenceDensity\n  className?: string\n}\n\n/** An estimate on the supplied density basis, never a dry-to-wet or compaction conversion. */\nexport function UmmDensityCalculator({ density, className }: UmmDensityCalculatorProps) {\n  const id = React.useId()\n  const [volume, setVolume] = React.useState(\"10\")\n  const numericVolume = volume.trim() ? Number(volume) : Number.NaN\n  const validVolume = Number.isFinite(numericVolume) && numericVolume >= 0\n  const validDensity = density?.unit === \"t/m3\" && Number.isFinite(density.mid) && density.mid > 0\n  const tonnes = validVolume && validDensity && density ? numericVolume * density.mid : undefined\n  const validRange = density && Number.isFinite(density.min) && Number.isFinite(density.max) && density.min! > 0 && density.min! <= density.mid && density.max! >= density.mid\n  const format = (value: number) => value.toLocaleString(\"en-GB\", { maximumFractionDigits: 2 })\n  return (\n    <section aria-label=\"Density estimate\" className={cn(\"space-y-4 rounded-xl border border-border bg-card p-5 text-sm text-card-foreground\", className)}>\n      <div><h3 className=\"font-semibold\">Volume to mass estimate</h3><p className=\"mt-1 text-xs text-muted-foreground\">Uses the supplied reference density and physical basis.</p></div>\n      {validDensity && density ? <>\n        <div className=\"space-y-2\"><label htmlFor={id} className=\"block font-medium\">Volume (m³)</label><input id={id} type=\"number\" min=\"0\" step=\"any\" value={volume} onChange={(event) => setVolume(event.target.value)} aria-invalid={!validVolume} aria-describedby={`${id}-result`} className=\"h-10 w-full rounded-lg border border-input bg-background px-3 shadow-xs focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring/25\" /></div>\n        <div id={`${id}-result`} aria-live=\"polite\">{tonnes !== undefined && Number.isFinite(tonnes) ? <><p className=\"text-2xl font-semibold tabular-nums\">{format(tonnes)} <span className=\"text-sm font-normal text-muted-foreground\">tonnes estimated</span></p>{validRange && <p className=\"mt-1 text-xs text-muted-foreground\">Reference range: {format(numericVolume * density.min!)}–{format(numericVolume * density.max!)} tonnes</p>}</> : <p className=\"text-muted-foreground\">Enter a finite, non-negative volume.</p>}</div>\n        <dl className=\"space-y-2 border-t border-border pt-4 text-xs\"><div><dt className=\"font-medium\">Density</dt><dd className=\"text-muted-foreground\">{density.mid} t/m³ · {density.basis}</dd></div><div><dt className=\"font-medium\">Source</dt><dd className=\"text-muted-foreground\">{density.source}</dd></div></dl>\n        <p className=\"text-xs text-muted-foreground\">Reference estimate, not a measured load weight. Moisture, compaction and material variation affect actual mass.</p>\n      </> : <p className=\"text-muted-foreground\">No valid reference density supplied for this material. No conversion is assumed.</p>}\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/umm-materials/umm-density-calculator.tsx"
    },
    {
      "path": "components/blocks/domain/umm-materials/umm-material-detail.tsx",
      "content": "import { cn } from \"@/lib/utils\"\nimport type { UmmClass, UmmSpecificationPreview, UmmTerm } from \"./umm-types\"\n\nexport interface UmmMaterialDetailProps {\n  materialClass?: UmmClass\n  term?: UmmTerm\n  specification?: UmmSpecificationPreview\n  version?: string\n  className?: string\n}\n\nexport function UmmMaterialDetail({ materialClass, term, specification, version, className }: UmmMaterialDetailProps) {\n  const validTerm = term?.parent === materialClass?.key ? term : undefined\n  const validSpecification = specification?.key === validTerm?.specTemplateKey ? specification : undefined\n  return (\n    <section aria-label=\"Material details\" className={cn(\"space-y-4 rounded-xl border border-border bg-card p-5 text-sm text-card-foreground\", className)}>\n      <div><p className=\"text-xs font-medium text-muted-foreground\">Material reference{version ? ` · ${version}` : \"\"}</p><h3 className=\"mt-1 text-lg font-semibold\">{validTerm?.label ?? materialClass?.name ?? \"Choose a material\"}</h3></div>\n      {materialClass ? <>\n        <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2\"><dt className=\"text-muted-foreground\">Class</dt><dd className=\"break-all font-mono text-xs\">{materialClass.key}</dd>{validTerm && <><dt className=\"text-muted-foreground\">Designation</dt><dd className=\"break-all font-mono text-xs\">{validTerm.code}</dd></>}{validTerm?.specTemplateKey && <><dt className=\"text-muted-foreground\">Specification</dt><dd className=\"break-all font-mono text-xs\">{validTerm.specTemplateKey}</dd></>}</dl>\n        {validTerm?.notes && <p className=\"text-muted-foreground\">{validTerm.notes}</p>}\n        {validSpecification && <div className=\"space-y-3 border-t border-border pt-4\"><h4 className=\"font-semibold\">{validSpecification.name}</h4>{validSpecification.description && <p className=\"text-muted-foreground\">{validSpecification.description}</p>}{validSpecification.requirements?.length ? <dl className=\"space-y-2\">{validSpecification.requirements.map((requirement, index) => <div key={`${requirement.label}-${index}`} className=\"flex flex-wrap justify-between gap-x-3\"><dt className=\"text-muted-foreground\">{requirement.label}</dt><dd>{requirement.value}</dd></div>)}</dl> : null}</div>}\n        <p className=\"border-t border-border pt-4 text-xs text-muted-foreground\">This describes a material or requested designation. Selection does not establish compliance, waste status or suitability; those require separate evidence.</p>\n      </> : <p className=\"text-muted-foreground\">Class identity and linked specifications will appear here.</p>}\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/umm-materials/umm-material-detail.tsx"
    },
    {
      "path": "components/blocks/domain/umm-materials/umm-material-selector.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport type { UmmClass, UmmSelection, UmmTerm } from \"./umm-types\"\n\nexport interface UmmMaterialSelectorProps {\n  classes: readonly UmmClass[]\n  terms: readonly UmmTerm[]\n  value: UmmSelection | null\n  onValueChange: (value: UmmSelection | null) => void\n  label?: string\n  disabled?: boolean\n  className?: string\n}\n\nconst normalise = (value: string) => value.toLocaleLowerCase(\"en-GB\").replace(/[^\\p{L}\\p{N}]/gu, \"\")\nconst control = \"rounded-lg border border-border px-3 py-2 text-sm font-medium shadow-xs transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring/25 disabled:cursor-default disabled:opacity-50\"\n\n/** Search and browse externally supplied UMM data; selection makes no suitability claim. */\nexport function UmmMaterialSelector({ classes, terms, value, onValueChange, label = \"Find a material\", disabled = false, className }: UmmMaterialSelectorProps) {\n  const id = React.useId()\n  const [query, setQuery] = React.useState(\"\")\n  const [parentKey, setParentKey] = React.useState<string | null>(null)\n  const byKey = React.useMemo(() => new Map(classes.map((item) => [item.key, item])), [classes])\n  const current = parentKey ? byKey.get(parentKey) : undefined\n  const selectedClass = value ? byKey.get(value.classKey) : undefined\n  const selectedTerm = value?.termCode ? terms.find((term) => term.code === value.termCode && term.parent === value.classKey) : undefined\n  const missingSelection = value && (!selectedClass || (value.termCode && !selectedTerm))\n  const search = normalise(query)\n  const lineage = (item: UmmClass) => (item.facetPath ?? [item.key]).map((key) => byKey.get(key)?.name ?? key).join(\" / \")\n  const visibleClasses = classes.filter((item) => search\n    ? normalise([item.key, item.name, ...(item.synonyms ?? []), lineage(item)].join(\" \")).includes(search)\n    : item.parentKey === (current?.key ?? null))\n    .sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0) || a.name.localeCompare(b.name, \"en-GB\"))\n  const visibleTerms = terms.filter((term) => byKey.has(term.parent) && (search\n    ? normalise([term.code, term.label, ...(term.synonyms ?? []), term.specTemplateKey ?? \"\", lineage(byKey.get(term.parent)!)].join(\" \")).includes(search)\n    : term.parent === current?.key))\n  const choose = (selection: UmmSelection) => onValueChange(selection)\n  const selected = (classKey: string, termCode?: string) => value?.classKey === classKey && value?.termCode === termCode\n\n  return (\n    <section aria-label={label} className={cn(\"space-y-4 rounded-xl border border-border bg-card p-5 text-card-foreground\", className)}>\n      <div className=\"space-y-2\">\n        <label htmlFor={id} className=\"block text-sm font-semibold\">{label}</label>\n        <input id={id} type=\"search\" value={query} disabled={disabled} onChange={(event) => setQuery(event.target.value)} placeholder=\"Search names, aliases or specifications…\" className={cn(control, \"w-full bg-background font-normal\")} aria-describedby={`${id}-help`} />\n        <p id={`${id}-help`} className=\"text-xs text-muted-foreground\">Choose a material class or a named designation. You can describe a material without choosing a specification.</p>\n      </div>\n      {!search && (\n        <nav aria-label=\"Material hierarchy\" className=\"flex flex-wrap items-center gap-2 text-sm\">\n          <button type=\"button\" disabled={disabled || !current} className={control} onClick={() => setParentKey(null)}>All materials</button>\n          {current && <><span aria-hidden=\"true\">/</span><span className=\"font-medium\">{lineage(current)}</span><button type=\"button\" disabled={disabled} className={cn(control, \"ml-auto\")} onClick={() => setParentKey(current.parentKey)}>Back</button></>}\n        </nav>\n      )}\n      {!search && current?.isLeaf && (\n        <button type=\"button\" disabled={disabled} aria-pressed={selected(current.key)} onClick={() => choose({ classKey: current.key })} className={cn(control, \"w-full text-left\", selected(current.key) && \"border-primary bg-primary/5\")}>\n          Choose {current.name} <span className=\"font-normal text-muted-foreground\">— no designation</span>\n        </button>\n      )}\n      <p role=\"status\" className=\"text-xs text-muted-foreground\">{search ? `${visibleClasses.length + visibleTerms.length} search results` : `${visibleClasses.length} categories · ${visibleTerms.length} designations`}</p>\n      <ul aria-label=\"Available materials\" className=\"max-h-80 space-y-2 overflow-y-auto p-1\">\n        {visibleClasses.map((item) => (\n          <li key={item.key} className=\"flex gap-2\">\n            <button type=\"button\" disabled={disabled} className={cn(control, \"min-w-0 flex-1 text-left\")} onClick={() => { setParentKey(item.key); setQuery(\"\") }}>\n              <span className=\"block\">{item.name}</span>\n              <span className=\"block truncate text-xs font-normal text-muted-foreground\">{search ? lineage(item) : \"Browse materials and designations\"}</span>\n            </button>\n            {item.isLeaf && <button type=\"button\" disabled={disabled} aria-label={`Choose ${item.name} without designation`} aria-pressed={selected(item.key)} onClick={() => choose({ classKey: item.key })} className={cn(control, selected(item.key) && \"border-primary bg-primary/5\")}>Choose</button>}\n          </li>\n        ))}\n        {visibleTerms.map((term) => (\n          <li key={term.code}>\n            <button type=\"button\" disabled={disabled} aria-pressed={selected(term.parent, term.code)} className={cn(control, \"w-full text-left\", selected(term.parent, term.code) && \"border-primary bg-primary/5\")} onClick={() => choose({ classKey: term.parent, termCode: term.code })}>\n              <span className=\"block\">{term.label}</span>\n              <span className=\"block text-xs font-normal text-muted-foreground\">{byKey.get(term.parent)?.name}{term.specTemplateKey ? \" · specification linked\" : \" · designation\"}</span>\n            </button>\n          </li>\n        ))}\n      </ul>\n      {!visibleClasses.length && !visibleTerms.length && <p className=\"text-sm text-muted-foreground\">{search ? \"No matching materials. Try a broader name.\" : \"No further entries in this dataset.\"}</p>}\n      {value && <div className=\"flex items-start justify-between gap-3 border-t border-border pt-4 text-sm\"><div aria-live=\"polite\"><p className=\"font-semibold\">{missingSelection ? \"Selection unavailable in this dataset\" : selectedTerm?.label ?? selectedClass?.name}</p><p className=\"break-all font-mono text-xs text-muted-foreground\">{value.classKey}{value.termCode ? ` / ${value.termCode}` : \"\"}</p></div><button type=\"button\" className={control} disabled={disabled} onClick={() => onValueChange(null)}>Clear</button></div>}\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/umm-materials/umm-material-selector.tsx"
    },
    {
      "path": "components/blocks/domain/umm-materials/umm-types.ts",
      "content": "/** Minimal UMM wire contracts. Consumers own fetching, version pinning and curation. */\nexport interface UmmClass {\n  key: string\n  parentKey: string | null\n  name: string\n  synonyms?: readonly string[]\n  isLeaf: boolean\n  facetPath?: readonly string[]\n  sort?: number\n}\n\nexport interface UmmTerm {\n  code: string\n  label: string\n  parent: string\n  synonyms?: readonly string[]\n  specTemplateKey?: string\n  termKind?: string\n  notes?: string\n}\n\nexport interface UmmSelection {\n  classKey: string\n  termCode?: string\n}\n\n/** A presentation adapter over a specification template, not an assessment. */\nexport interface UmmSpecificationPreview {\n  key: string\n  name: string\n  description?: string\n  requirements?: readonly { label: string; value: string }[]\n}\n\n/** Explicit unit and basis prevent accidental dry/loose/bulk density conversions. */\nexport interface UmmReferenceDensity {\n  unit: \"t/m3\"\n  mid: number\n  min?: number\n  max?: number\n  basis: string\n  source: string\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/umm-materials/umm-types.ts"
    }
  ],
  "type": "registry:block"
}