{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"c-data-grid-30","type":"registry:block","title":"Data grid with tree rows","description":"Data grid with tree rows","dependencies":["@tanstack/react-table"],"registryDependencies":["avatar","@reui/badge","card","@reui/data-grid","@reui/data-grid-column-header","@reui/data-grid-pagination","@reui/data-grid-scroll-area","@reui/data-grid-table"],"files":[{"path":"c-data-grid-30.tsx","type":"registry:block","content":"\"use client\"\n\nimport { useEffect, useMemo, useState } from \"react\"\nimport { Badge } from \"@/components/reui/badge\"\nimport {\n  DataGrid,\n  DataGridContainer,\n  dataGridFeatures,\n  type DataGridFeatures,\n} from \"@/components/reui/data-grid/data-grid\"\nimport { DataGridColumnHeader } from \"@/components/reui/data-grid/data-grid-column-header\"\nimport { DataGridPagination } from \"@/components/reui/data-grid/data-grid-pagination\"\nimport { DataGridScrollArea } from \"@/components/reui/data-grid/data-grid-scroll-area\"\nimport {\n  DataGridTable,\n  DataGridTableRowExpand,\n} from \"@/components/reui/data-grid/data-grid-table\"\nimport {\n  ColumnDef,\n  ExpandedState,\n  PaginationState,\n  SortingState,\n  useTable,\n} from \"@tanstack/react-table\"\n\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/components/ui/avatar\"\nimport { Card } from \"@/components/ui/card\"\n\ninterface IData {\n  id: string\n  name: string\n  type: \"department\" | \"team\" | \"member\"\n  status: \"active\" | \"inactive\"\n  role?: string\n  avatar?: string\n  flag?: string\n  location?: string\n  children?: IData[]\n}\n\nconst demoData: IData[] = [\n  {\n    id: \"eng\",\n    name: \"Engineering\",\n    type: \"department\",\n    status: \"active\",\n    children: [\n      {\n        id: \"eng-platform\",\n        name: \"Platform\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"eng-platform-1\",\n            name: \"Alex Johnson\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Staff Engineer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=96&h=96&dpr=2&q=80\",\n            flag: \"us\",\n            location: \"United States\",\n          },\n          {\n            id: \"eng-platform-2\",\n            name: \"Sarah Chen\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Senior Engineer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80\",\n            flag: \"gb\",\n            location: \"United Kingdom\",\n          },\n          {\n            id: \"eng-platform-3\",\n            name: \"Michael Rodriguez\",\n            type: \"member\",\n            status: \"inactive\",\n            role: \"Frontend Engineer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1584308972272-9e4e7685e80f?w=96&h=96&dpr=2&q=80\",\n            flag: \"ca\",\n            location: \"Canada\",\n          },\n        ],\n      },\n      {\n        id: \"eng-mobile\",\n        name: \"Mobile\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"eng-mobile-1\",\n            name: \"Emma Wilson\",\n            type: \"member\",\n            status: \"active\",\n            role: \"iOS Engineer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1485893086445-ed75865251e0?w=96&h=96&dpr=2&q=80\",\n            flag: \"au\",\n            location: \"Australia\",\n          },\n          {\n            id: \"eng-mobile-2\",\n            name: \"David Kim\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Android Engineer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1607990281513-2c110a25bd8c?w=96&h=96&dpr=2&q=80\",\n            flag: \"de\",\n            location: \"Germany\",\n          },\n        ],\n      },\n    ],\n  },\n  {\n    id: \"design\",\n    name: \"Design\",\n    type: \"department\",\n    status: \"active\",\n    children: [\n      {\n        id: \"design-product\",\n        name: \"Product Design\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"design-product-1\",\n            name: \"Aron Thompson\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Design Lead\",\n            avatar:\n              \"https://images.unsplash.com/photo-1527980965255-d3b416303d12?w=96&h=96&dpr=2&q=80\",\n            flag: \"my\",\n            location: \"Malaysia\",\n          },\n          {\n            id: \"design-product-2\",\n            name: \"Maria Garcia\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Product Designer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1620075225255-8c2051b6c015?w=96&h=96&dpr=2&q=80\",\n            flag: \"jp\",\n            location: \"Japan\",\n          },\n          {\n            id: \"design-product-3\",\n            name: \"James Brown\",\n            type: \"member\",\n            status: \"inactive\",\n            role: \"UX Researcher\",\n            avatar:\n              \"https://images.unsplash.com/photo-1543299750-19d1d6297053?w=96&h=96&dpr=2&q=80\",\n            flag: \"es\",\n            location: \"Spain\",\n          },\n        ],\n      },\n      {\n        id: \"design-brand\",\n        name: \"Brand\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"design-brand-1\",\n            name: \"Nick Johnson\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Brand Designer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1485206412256-701ccc5b93ca?w=96&h=96&dpr=2&q=80\",\n            flag: \"fr\",\n            location: \"France\",\n          },\n          {\n            id: \"design-brand-2\",\n            name: \"Liam Thompson\",\n            type: \"member\",\n            status: \"inactive\",\n            role: \"Motion Designer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1542595913-85d69b0edbaf?w=96&h=96&dpr=2&q=80\",\n            flag: \"it\",\n            location: \"Italy\",\n          },\n        ],\n      },\n    ],\n  },\n  {\n    id: \"marketing\",\n    name: \"Marketing\",\n    type: \"department\",\n    status: \"active\",\n    children: [\n      {\n        id: \"marketing-growth\",\n        name: \"Growth\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"marketing-growth-1\",\n            name: \"Olivia Martin\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Growth Lead\",\n            avatar:\n              \"https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80\",\n            flag: \"us\",\n            location: \"United States\",\n          },\n          {\n            id: \"marketing-growth-2\",\n            name: \"Ethan Clark\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Performance Marketer\",\n            avatar:\n              \"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=96&h=96&dpr=2&q=80\",\n            flag: \"ca\",\n            location: \"Canada\",\n          },\n        ],\n      },\n      {\n        id: \"marketing-content\",\n        name: \"Content\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"marketing-content-1\",\n            name: \"Sofia Rossi\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Content Lead\",\n            avatar:\n              \"https://images.unsplash.com/photo-1485893086445-ed75865251e0?w=96&h=96&dpr=2&q=80\",\n            flag: \"it\",\n            location: \"Italy\",\n          },\n          {\n            id: \"marketing-content-2\",\n            name: \"Lucas Meyer\",\n            type: \"member\",\n            status: \"inactive\",\n            role: \"Copywriter\",\n            avatar:\n              \"https://images.unsplash.com/photo-1584308972272-9e4e7685e80f?w=96&h=96&dpr=2&q=80\",\n            flag: \"de\",\n            location: \"Germany\",\n          },\n        ],\n      },\n    ],\n  },\n  {\n    id: \"operations\",\n    name: \"Operations\",\n    type: \"department\",\n    status: \"active\",\n    children: [\n      {\n        id: \"operations-finance\",\n        name: \"Finance\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"operations-finance-1\",\n            name: \"Grace Lee\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Finance Lead\",\n            avatar:\n              \"https://images.unsplash.com/photo-1620075225255-8c2051b6c015?w=96&h=96&dpr=2&q=80\",\n            flag: \"kr\",\n            location: \"South Korea\",\n          },\n          {\n            id: \"operations-finance-2\",\n            name: \"Daniel Novak\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Accountant\",\n            avatar:\n              \"https://images.unsplash.com/photo-1607990281513-2c110a25bd8c?w=96&h=96&dpr=2&q=80\",\n            flag: \"cz\",\n            location: \"Czechia\",\n          },\n        ],\n      },\n      {\n        id: \"operations-people\",\n        name: \"People\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"operations-people-1\",\n            name: \"Chloe Dubois\",\n            type: \"member\",\n            status: \"active\",\n            role: \"People Lead\",\n            avatar:\n              \"https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80\",\n            flag: \"fr\",\n            location: \"France\",\n          },\n          {\n            id: \"operations-people-2\",\n            name: \"Ryan Walsh\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Recruiter\",\n            avatar:\n              \"https://images.unsplash.com/photo-1527980965255-d3b416303d12?w=96&h=96&dpr=2&q=80\",\n            flag: \"ie\",\n            location: \"Ireland\",\n          },\n        ],\n      },\n    ],\n  },\n  {\n    id: \"sales\",\n    name: \"Sales\",\n    type: \"department\",\n    status: \"active\",\n    children: [\n      {\n        id: \"sales-accounts\",\n        name: \"Accounts\",\n        type: \"team\",\n        status: \"active\",\n        children: [\n          {\n            id: \"sales-accounts-1\",\n            name: \"Mia Park\",\n            type: \"member\",\n            status: \"active\",\n            role: \"Account Executive\",\n            avatar:\n              \"https://images.unsplash.com/photo-1485893086445-ed75865251e0?w=96&h=96&dpr=2&q=80\",\n            flag: \"kr\",\n            location: \"South Korea\",\n          },\n          {\n            id: \"sales-accounts-2\",\n            name: \"Noah Fischer\",\n            type: \"member\",\n            status: \"inactive\",\n            role: \"Account Manager\",\n            avatar:\n              \"https://images.unsplash.com/photo-1543299750-19d1d6297053?w=96&h=96&dpr=2&q=80\",\n            flag: \"at\",\n            location: \"Austria\",\n          },\n        ],\n      },\n    ],\n  },\n]\n\n// Collapsed rows are unmounted, so their images would load only when a\n// branch is first expanded and pop in after the fallback renders. Warming\n// them once at mount keeps expansion flicker-free.\nfunction collectImageUrls(rows: IData[]): string[] {\n  return rows.flatMap((row) => [\n    ...(row.avatar ? [row.avatar] : []),\n    ...(row.flag ? [`https://flagcdn.com/${row.flag.toLowerCase()}.svg`] : []),\n    ...(row.children ? collectImageUrls(row.children) : []),\n  ])\n}\n\nexport function Pattern() {\n  const [pagination, setPagination] = useState<PaginationState>({\n    pageIndex: 0,\n    pageSize: 4,\n  })\n  const [sorting, setSorting] = useState<SortingState>([])\n  const [expanded, setExpanded] = useState<ExpandedState>({\n    eng: true,\n    \"eng-platform\": true,\n  })\n\n  useEffect(() => {\n    for (const src of collectImageUrls(demoData)) {\n      const image = new Image()\n      image.src = src\n    }\n  }, [])\n\n  const columns = useMemo<ColumnDef<DataGridFeatures, IData>[]>(\n    () => [\n      {\n        accessorKey: \"name\",\n        id: \"name\",\n        header: ({ column }) => (\n          <DataGridColumnHeader title=\"Name\" column={column} />\n        ),\n        cell: ({ row }) => {\n          const item = row.original\n\n          return (\n            <div className=\"flex items-center gap-1\">\n              <DataGridTableRowExpand row={row} className=\"-ms-1.5 -me-1\" />\n              {item.type === \"member\" ? (\n                <>\n                  <Avatar className=\"size-6 shrink-0\">\n                    <AvatarImage src={item.avatar} alt={item.name} />\n                    <AvatarFallback>\n                      {item.name\n                        .split(\" \")\n                        .map((n) => n[0])\n                        .join(\"\")}\n                    </AvatarFallback>\n                  </Avatar>\n                  <a\n                    href=\"#\"\n                    className=\"text-foreground hover:text-primary font-medium\"\n                  >\n                    {item.name}\n                  </a>\n                </>\n              ) : (\n                <span className=\"text-foreground font-medium\">{item.name}</span>\n              )}\n            </div>\n          )\n        },\n        minSize: 260,\n        enableSorting: true,\n        enableHiding: false,\n        meta: {\n          autoSize: true,\n        },\n      },\n      {\n        accessorKey: \"role\",\n        header: ({ column }) => (\n          <DataGridColumnHeader title=\"Role\" column={column} />\n        ),\n        cell: ({ row }) => {\n          const item = row.original\n\n          return (\n            <div className=\"text-muted-foreground\">\n              {item.role ??\n                (item.type === \"department\" ? \"Department\" : \"Team\")}\n            </div>\n          )\n        },\n        size: 180,\n      },\n      {\n        accessorKey: \"location\",\n        header: ({ column }) => (\n          <DataGridColumnHeader title=\"Location\" column={column} />\n        ),\n        cell: ({ row }) => {\n          const item = row.original\n\n          if (!item.location || !item.flag) {\n            return <span className=\"text-muted-foreground\">-</span>\n          }\n\n          return (\n            <div className=\"flex items-center gap-1.5\">\n              <img\n                src={`https://flagcdn.com/${item.flag.toLowerCase()}.svg`}\n                alt={item.flag}\n                className=\"size-4 rounded-full object-cover\"\n              />\n              <div className=\"text-foreground font-medium\">{item.location}</div>\n            </div>\n          )\n        },\n        size: 180,\n      },\n      {\n        accessorKey: \"status\",\n        id: \"status\",\n        header: ({ column }) => (\n          <DataGridColumnHeader title=\"Status\" column={column} />\n        ),\n        cell: ({ row }) => {\n          if (row.original.status === \"active\") {\n            return <Badge variant=\"success-outline\">Active</Badge>\n          }\n\n          return <Badge variant=\"warning-outline\">Inactive</Badge>\n        },\n        size: 130,\n      },\n    ],\n    []\n  )\n\n  const table = useTable({\n    features: dataGridFeatures,\n    columns,\n    data: demoData,\n    pageCount: Math.ceil((demoData?.length || 0) / pagination.pageSize),\n    getRowId: (row: IData) => row.id,\n    getSubRows: (row) => row.children,\n    state: {\n      pagination,\n      sorting,\n      expanded,\n    },\n    // Keep expanded children on the same page as their parent.\n    paginateExpandedRows: false,\n    onPaginationChange: setPagination,\n    onSortingChange: setSorting,\n    onExpandedChange: setExpanded,\n  })\n\n  return (\n    <DataGrid\n      table={table}\n      recordCount={demoData?.length || 0}\n      tableLayout={{\n        columnsResizable: true,\n        columnsMovable: true,\n        columnsVisibility: true,\n      }}\n    >\n      <div className=\"w-full space-y-2.5\">\n        <Card className=\"overflow-hidden p-0\">\n          <DataGridContainer>\n            <DataGridScrollArea>\n              <DataGridTable />\n            </DataGridScrollArea>\n          </DataGridContainer>\n        </Card>\n        <DataGridPagination sizes={[4, 8, 16]} />\n      </div>\n    </DataGrid>\n  )\n}","target":"components/examples/c-data-grid-30.tsx"}],"meta":{"order":30,"gridSize":1,"previewHeight":"608"}}