{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"x-files-manager","type":"registry:ui","title":"XFilesManager","description":"The compound surface \u2014 a tab per file kind wiring each editor next to its live preview over one aggregate model.","package":"fancy-x-files-ui","dependencies":["@particle-academy\/react-fancy"],"registryDependencies":["model.js","validate.js"],"files":[{"path":"components\/fancy\/x-files-manager\/XFilesManager.tsx","content":"import { useState } from \"react\";\r\nimport { Badge, Tabs } from \"@particle-academy\/react-fancy\";\r\nimport {\r\n  X_FILE_META,\r\n  emptyAgents,\r\n  emptyHumansTxt,\r\n  emptyLlmsTxt,\r\n  emptyRobots,\r\n  emptySecurityTxt,\r\n  emptySitemap,\r\n  type XFileKind,\r\n  type XFilesModel,\r\n} from \"..\/model.js\";\r\nimport { validateModel } from \"..\/validate.js\";\r\nimport { AgentsEditor } from \".\/AgentsEditor.js\";\r\nimport { HumansTxtEditor } from \".\/HumansTxtEditor.js\";\r\nimport { LlmsTxtEditor } from \".\/LlmsTxtEditor.js\";\r\nimport { RobotsEditor } from \".\/RobotsEditor.js\";\r\nimport { SecurityTxtEditor } from \".\/SecurityTxtEditor.js\";\r\nimport { SitemapEditor } from \".\/SitemapEditor.js\";\r\nimport { XFilePreview } from \".\/XFilePreview.js\";\r\n\r\nexport interface XFilesManagerProps {\r\n  \/** The aggregate model \u2014 each key optional; absent keys offer an \"Add\" CTA. *\/\r\n  value: XFilesModel;\r\n  \/** Fires with the next aggregate model on every edit. *\/\r\n  onChange: (next: XFilesModel) => void;\r\n  \/** Restrict + order the tabs. Default: all six kinds. *\/\r\n  kinds?: XFileKind[];\r\n  \/** Controlled active tab (a file kind). *\/\r\n  activeKind?: XFileKind;\r\n  \/** Default active tab when uncontrolled. *\/\r\n  defaultKind?: XFileKind;\r\n  onActiveKindChange?: (kind: XFileKind) => void;\r\n  className?: string;\r\n}\r\n\r\nconst ALL_KINDS: XFileKind[] = [\r\n  \"robots\",\r\n  \"securityTxt\",\r\n  \"llmsTxt\",\r\n  \"sitemap\",\r\n  \"humansTxt\",\r\n  \"agents\",\r\n];\r\n\r\nconst EMPTY: Record<XFileKind, () => XFilesModel[XFileKind]> = {\r\n  robots: emptyRobots,\r\n  securityTxt: emptySecurityTxt,\r\n  llmsTxt: emptyLlmsTxt,\r\n  humansTxt: emptyHumansTxt,\r\n  sitemap: emptySitemap,\r\n  agents: emptyAgents,\r\n};\r\n\r\n\/**\r\n * The headline compound component. Tabs (one per file kind) hold the aggregate\r\n * `value` + `onChange`, wiring each editor next to its live preview. Fully\r\n * controlled and JSON-friendly so an embedded agent can read the whole set of\r\n * well-known files and propose edits through the same channel (Human+ UX).\r\n *\/\r\nexport function XFilesManager({\r\n  value,\r\n  onChange,\r\n  kinds = ALL_KINDS,\r\n  activeKind,\r\n  defaultKind,\r\n  onActiveKindChange,\r\n  className,\r\n}: XFilesManagerProps) {\r\n  const [internalActive, setInternalActive] = useState<XFileKind>(\r\n    defaultKind ?? kinds[0],\r\n  );\r\n  const active = activeKind ?? internalActive;\r\n\r\n  const setActive = (kind: string) => {\r\n    const k = kind as XFileKind;\r\n    if (activeKind == null) setInternalActive(k);\r\n    onActiveKindChange?.(k);\r\n  };\r\n\r\n  const patch = (kind: XFileKind, model: XFilesModel[XFileKind]) =>\r\n    onChange({ ...value, [kind]: model });\r\n\r\n  const ensure = (kind: XFileKind) => {\r\n    if (value[kind] == null) patch(kind, EMPTY[kind]());\r\n  };\r\n\r\n  const issueCount = (kind: XFileKind): number =>\r\n    value[kind] != null ? validateModel(kind, value[kind]).length : 0;\r\n\r\n  const renderEditor = (kind: XFileKind) => {\r\n    const model = value[kind];\r\n    if (model == null) {\r\n      return (\r\n        <div\r\n          data-x-empty={kind}\r\n          style={{\r\n            padding: \"1.5rem\",\r\n            textAlign: \"center\",\r\n            display: \"flex\",\r\n            flexDirection: \"column\",\r\n            gap: \"0.6rem\",\r\n            alignItems: \"center\",\r\n          }}\r\n        >\r\n          <p style={{ margin: 0, opacity: 0.7 }}>\r\n            No {X_FILE_META[kind].label} yet.\r\n          <\/p>\r\n          <button\r\n            type=\"button\"\r\n            onClick={() => ensure(kind)}\r\n            data-x-create={kind}\r\n            style={{\r\n              padding: \"0.4rem 0.8rem\",\r\n              borderRadius: \"0.4rem\",\r\n              border: \"1px solid var(--rf-border, #e5e7eb)\",\r\n              cursor: \"pointer\",\r\n              background: \"transparent\",\r\n            }}\r\n          >\r\n            Add {X_FILE_META[kind].label}\r\n          <\/button>\r\n        <\/div>\r\n      );\r\n    }\r\n\r\n    switch (kind) {\r\n      case \"robots\":\r\n        return (\r\n          <RobotsEditor\r\n            value={model as never}\r\n            onChange={(m) => patch(\"robots\", m)}\r\n          \/>\r\n        );\r\n      case \"securityTxt\":\r\n        return (\r\n          <SecurityTxtEditor\r\n            value={model as never}\r\n            onChange={(m) => patch(\"securityTxt\", m)}\r\n          \/>\r\n        );\r\n      case \"llmsTxt\":\r\n        return (\r\n          <LlmsTxtEditor\r\n            value={model as never}\r\n            onChange={(m) => patch(\"llmsTxt\", m)}\r\n          \/>\r\n        );\r\n      case \"sitemap\":\r\n        return (\r\n          <SitemapEditor\r\n            value={model as never}\r\n            onChange={(m) => patch(\"sitemap\", m)}\r\n          \/>\r\n        );\r\n      case \"humansTxt\":\r\n        return (\r\n          <HumansTxtEditor\r\n            value={model as never}\r\n            onChange={(m) => patch(\"humansTxt\", m)}\r\n          \/>\r\n        );\r\n      case \"agents\":\r\n        return (\r\n          <AgentsEditor\r\n            value={model as never}\r\n            onChange={(m) => patch(\"agents\", m)}\r\n          \/>\r\n        );\r\n    }\r\n  };\r\n\r\n  return (\r\n    <div className={className} data-x-manager>\r\n      <Tabs activeTab={active} onTabChange={setActive}>\r\n        <Tabs.List>\r\n          {kinds.map((kind) => {\r\n            const count = issueCount(kind);\r\n            return (\r\n              <Tabs.Tab key={kind} value={kind} data-x-tab={kind}>\r\n                {X_FILE_META[kind].label}\r\n                {count > 0 ? (\r\n                  <Badge\r\n                    color=\"amber\"\r\n                    variant=\"soft\"\r\n                    size=\"sm\"\r\n                    style={{ marginLeft: \"0.4rem\" }}\r\n                  >\r\n                    {count}\r\n                  <\/Badge>\r\n                ) : null}\r\n              <\/Tabs.Tab>\r\n            );\r\n          })}\r\n        <\/Tabs.List>\r\n        <Tabs.Panels>\r\n          {kinds.map((kind) => (\r\n            <Tabs.Panel key={kind} value={kind}>\r\n              <div\r\n                data-x-pane={kind}\r\n                style={{\r\n                  display: \"grid\",\r\n                  gridTemplateColumns: \"minmax(0, 1fr) minmax(0, 1fr)\",\r\n                  gap: \"1rem\",\r\n                  alignItems: \"start\",\r\n                }}\r\n              >\r\n                <div>{renderEditor(kind)}<\/div>\r\n                <div style={{ position: \"sticky\", top: \"1rem\" }}>\r\n                  <XFilePreview kind={kind} model={value[kind]} \/>\r\n                <\/div>\r\n              <\/div>\r\n            <\/Tabs.Panel>\r\n          ))}\r\n        <\/Tabs.Panels>\r\n      <\/Tabs>\r\n    <\/div>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/x-files-manager\/XFilesManager.tsx"}]}