{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"downline-tree","type":"registry:ui","title":"DownlineTree","description":"Controlled network tree \u2014 collapsible nodes, per-member tier\/volume, selection + onChange. Renders unilevel, binary, and matrix shapes from a flat member list.","package":"fancy-mlm-ui","dependencies":["@particle-academy\/react-fancy"],"registryDependencies":[],"files":[{"path":"components\/fancy\/downline-tree\/DownlineTree.tsx","content":"import { useState, type ComponentProps, type ReactNode } from \"react\";\nimport { cn, Badge } from \"@particle-academy\/react-fancy\";\nimport type { DownlineEdge, DownlineMember } from \".\/types\";\nimport { buildDownline, type DownlineNode } from \".\/tree\";\n\ntype BadgeColor = ComponentProps<typeof Badge>[\"color\"];\n\nexport interface DownlineTreeProps<T extends DownlineMember = DownlineMember> {\n  \/** Flat member list (controlled). Linked into a tree by `edge`. *\/\n  value: T[];\n  \/**\n   * Which pointer links the tree \u2014 `\"sponsor\"` (unilevel, default) or\n   * `\"placement\"` (binary \/ matrix). The same `value` renders every tree shape.\n   *\/\n  edge?: DownlineEdge;\n  \/** Render only this member's subtree. *\/\n  rootId?: string | null;\n  \/** Highlight a member (controlled selection). *\/\n  selectedId?: string | null;\n  onSelect?: (id: string, member: T) => void;\n  \/** Map a tier to a react-fancy Badge color. *\/\n  tierColor?: (tier: string | undefined) => BadgeColor;\n  \/** Replace the default member row content. *\/\n  renderMember?: (member: T, node: DownlineNode<T>) => ReactNode;\n  className?: string;\n}\n\n\/**\n * Genealogy \/ downline tree. Controlled `value`, stable `data-mlm-node` handles\n * on every member, controlled selection, and JSON-friendly inputs \u2014 an agent can\n * read the tree and drive selection without scraping the DOM.\n *\/\nexport function DownlineTree<T extends DownlineMember = DownlineMember>({\n  value,\n  edge = \"sponsor\",\n  rootId,\n  selectedId,\n  onSelect,\n  tierColor,\n  renderMember,\n  className,\n}: DownlineTreeProps<T>) {\n  const forest = buildDownline(value, rootId, edge);\n\n  return (\n    <div data-fancy-mlm=\"downline-tree\" data-mlm-edge={edge} className={cn(\"fancy-mlm-tree\", className)}>\n      <ul className=\"fancy-mlm-tree__list\" role=\"tree\">\n        {forest.map((node) => (\n          <DownlineNodeView\n            key={node.member.id}\n            node={node}\n            selectedId={selectedId}\n            onSelect={onSelect}\n            tierColor={tierColor}\n            renderMember={renderMember}\n          \/>\n        ))}\n      <\/ul>\n    <\/div>\n  );\n}\n\nfunction DownlineNodeView<T extends DownlineMember>({\n  node,\n  selectedId,\n  onSelect,\n  tierColor,\n  renderMember,\n}: {\n  node: DownlineNode<T>;\n  selectedId?: string | null;\n  onSelect?: (id: string, member: T) => void;\n  tierColor?: (tier: string | undefined) => BadgeColor;\n  renderMember?: (member: T, node: DownlineNode<T>) => ReactNode;\n}) {\n  const [expanded, setExpanded] = useState(true);\n  const { member, children } = node;\n  const hasChildren = children.length > 0;\n  const active = member.active ?? true;\n\n  return (\n    <li className=\"fancy-mlm-tree__item\" data-mlm-node={member.id} role=\"treeitem\" aria-expanded={hasChildren ? expanded : undefined}>\n      <div\n        className={cn(\n          \"fancy-mlm-tree__row\",\n          selectedId === member.id && \"is-selected\",\n          !active && \"is-inactive\",\n        )}\n      >\n        {hasChildren ? (\n          <button\n            type=\"button\"\n            className=\"fancy-mlm-tree__toggle\"\n            aria-label={expanded ? \"Collapse\" : \"Expand\"}\n            aria-expanded={expanded}\n            onClick={() => setExpanded((value) => !value)}\n          >\n            {expanded ? \"\u25be\" : \"\u25b8\"}\n          <\/button>\n        ) : (\n          <span className=\"fancy-mlm-tree__toggle fancy-mlm-tree__toggle--leaf\" aria-hidden=\"true\" \/>\n        )}\n\n        <button\n          type=\"button\"\n          className=\"fancy-mlm-tree__label\"\n          data-mlm-member={member.id}\n          onClick={() => onSelect?.(member.id, member)}\n        >\n          {renderMember ? (\n            renderMember(member, node)\n          ) : (\n            <>\n              <span className=\"fancy-mlm-tree__name\">{member.label ?? member.id}<\/span>\n              {member.tier ? (\n                <Badge color={tierColor?.(member.tier)} variant=\"soft\">\n                  {member.tier}\n                <\/Badge>\n              ) : null}\n              {!active ? <span className=\"fancy-mlm-tree__muted\">inactive<\/span> : null}\n            <\/>\n          )}\n        <\/button>\n      <\/div>\n\n      {hasChildren && expanded ? (\n        <ul className=\"fancy-mlm-tree__list\" role=\"group\">\n          {children.map((child) => (\n            <DownlineNodeView\n              key={child.member.id}\n              node={child}\n              selectedId={selectedId}\n              onSelect={onSelect}\n              tierColor={tierColor}\n              renderMember={renderMember}\n            \/>\n          ))}\n        <\/ul>\n      ) : null}\n    <\/li>\n  );\n}\n","type":"registry:ui","target":"components\/fancy\/downline-tree\/DownlineTree.tsx"}]}