ui icon indicating copy to clipboard operation
ui copied to clipboard

Collapsible Tables

Open LogicLaneTech opened this issue 2 years ago • 24 comments
trafficstars

How can I create collapsible tables with this?

LogicLaneTech avatar May 18 '23 07:05 LogicLaneTech

Place the table inside the collapsible content, is that what you are looking for? @TheDivineCodes

jocarrd avatar May 18 '23 08:05 jocarrd

No SIR

I am looking for this

https://codesandbox.io/s/collapsible-table-rows-in-react-ybb28

LogicLaneTech avatar May 18 '23 09:05 LogicLaneTech

Nice, so you need to wrap each row inside the collapsible trigger and put the content inside the collapsible content Something like this:

<table>
  <tr>
    <th>Column 1</th>
    <th>Column 2</th>
    <th>Column 3</th>
  </tr>
<tr>
  <Collapsible>
      <CollapsibleTrigger>  
        <td>Cell 1</td>
        <td>Cell 2</td>
        <td>Cell 3</td>
      </CollapsibleTrigger>
        <CollapsibleContent>
          Row collapsible content
        </CollapsibleContent>
    </Collapsible>
 </tr>
</table>

jocarrd avatar May 18 '23 09:05 jocarrd

For using that collapsible tag do I need to import something?

LogicLaneTech avatar May 18 '23 09:05 LogicLaneTech

Yes you have to add the component file as it says in the documentation, I recommend you use the cli tool and install only the components you need, in this case the collapsible, look at this

After installing it :

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

jocarrd avatar May 18 '23 09:05 jocarrd

Okay after rendering this is not giving me the same result as the one above

LogicLaneTech avatar May 18 '23 09:05 LogicLaneTech

Could you replicate it in codesandbox to try to review it? Thank you

jocarrd avatar May 18 '23 09:05 jocarrd

I used shadcn/ui to build a collapsible table. I stumbled on this issue before I built it and realized that except this issue and a bunch of references to tanstack/table, there is not good resource how to build it.

So I made a quick tutorial for those of you interested.

I was trying to solve it with the Accordion component until I saw this and @jocarrd mentioning Collapsible.

I hope this helps anyone else coming to this issue by searching on google (like I and most of you did). Feedback is always welcome :)

https://dev.to/mfts/build-an-expandable-data-table-with-2-shadcnui-components-4nge

mfts avatar Aug 16 '23 13:08 mfts

I was trying to solve it with the Accordion component until I saw this and @jocarrd mentioning Collapsible.

Nice article. Why not just use the Expanding API from tanstack? The collapsible doesn't have an animation or anything unique, so just using the inbuilt tanstack one gives more functionality imo.

Edit: Was late when I sent this. Just realized that the article doesn't even use tanstack lol. So collapsible is a good solution for that.

If you do use tanstack to create the advanced data table, look into the expandable feature from tanstack.

ajayvignesh01 avatar Dec 15 '23 09:12 ajayvignesh01

Just realized that the article doesn't even use tanstack lol. So collapsible is a good solution for that.

Yes I didn't use tanstack because it felt an overkill for what I need the table for.

mfts avatar Dec 16 '23 01:12 mfts

I used shadcn/ui to build a collapsible table. I stumbled on this issue before I built it and realized that except this issue and a bunch of references to tanstack/table, there is not good resource how to build it.

So I made a quick tutorial for those of you interested.

I was trying to solve it with the Accordion component until I saw this and @jocarrd mentioning Collapsible.

I hope this helps anyone else coming to this issue by searching on google (like I and most of you did). Feedback is always welcome :)

https://dev.to/mfts/build-an-expandable-data-table-with-2-shadcnui-components-4nge

Thanks for the nice setup. Unfortunatly, I have the following error "Error: React.Children.only expected to receive a single React element child.". It seems to be related to TableRow & CollapsibleContent at the same level... when I remove CollapsibleContent, the error disapear :/ Did you have the same?

laurent512 avatar Jan 23 '24 20:01 laurent512

I used shadcn/ui to build a collapsible table. I stumbled on this issue before I built it and realized that except this issue and a bunch of references to tanstack/table, there is not good resource how to build it. So I made a quick tutorial for those of you interested. I was trying to solve it with the Accordion component until I saw this and @jocarrd mentioning Collapsible. I hope this helps anyone else coming to this issue by searching on google (like I and most of you did). Feedback is always welcome :) https://dev.to/mfts/build-an-expandable-data-table-with-2-shadcnui-components-4nge

Thanks for the nice setup. Unfortunatly, I have the following error "Error: React.Children.only expected to receive a single React element child.". It seems to be related to TableRow & CollapsibleContent at the same level... when I remove CollapsibleContent, the error disapear :/ Did you have the same?

After few extra investigation, it seems connected to this problem : https://github.com/radix-ui/primitives/issues/1979

laurent512 avatar Jan 23 '24 22:01 laurent512

Error: React.Children.only expected to receive a single React element child

@laurent512 if you use asCild then just wrap everything inside it into a fragment <></> and it interprets the code as a single react element.

mfts avatar Feb 04 '24 04:02 mfts

Error: React.Children.only expected to receive a single React element child

@laurent512 if you use asCild then just wrap everything inside it into a fragment <></> and it interprets the code as a single react element.

Thanks for your article. I have everything wrapped in a fragment like you mentioned but it's still throwing the same error Error: React.Children.only expected to receive a single React element child.. Any advice?

This is my current code:

{/* ... */}
<TableBody>
  {categories.map((c: Category) => (
    <Collapsible key={c.id} asChild>
      <>
        <CategoryRow
          category={c}
          categoryTypes={categoryTypes}
          subCategoriesCell={
            <CollapsibleTrigger asChild>
              <>{c.subCategories.length}</>
            </CollapsibleTrigger>
          }
        />
        <CollapsibleContent asChild>
          <SubCategoryRows subCategories={c.subCategories} />
        </CollapsibleContent>
      </>
    </Collapsible>
  ))}
</TableBody>
{/* ... */}

akyrey avatar Feb 11 '24 14:02 akyrey

Hmmm that's super strange. I cannot spot any irregularities.

One thing you can try to change is this line:

Before:

<CollapsibleTrigger asChild>
   <>{c.subCategories.length}</>
</CollapsibleTrigger>

After:

<CollapsibleTrigger>
   {c.subCategories.length}
</CollapsibleTrigger>

mfts avatar Feb 13 '24 11:02 mfts

Hmmm that's super strange. I cannot spot any irregularities.

One thing you can try to change is this line:

Before:

<CollapsibleTrigger asChild>
   <>{c.subCategories.length}</>
</CollapsibleTrigger>

After:

<CollapsibleTrigger>
   {c.subCategories.length}
</CollapsibleTrigger>

Same result unfortunately :(

akyrey avatar Feb 13 '24 16:02 akyrey

Hmmm that's super strange. I cannot spot any irregularities.

One thing you can try to change is this line:

Before:

<CollapsibleTrigger asChild>
   <>{c.subCategories.length}</>
</CollapsibleTrigger>

After:

<CollapsibleTrigger>
   {c.subCategories.length}
</CollapsibleTrigger>

@akyrey I found the solution. The issue is not that part of the code. The issue is the fragment that is supposed to be used in the 'Collapsible' area. This will occur while using SSR (tested with nextjs 14). It seems as if fragments do not work with SSR in the same way you may expect them to...this may be a bug? The solution is to have the "use client" text at the top of your file to make it a client component (I would break out that part of the code into a separate file). I opened a bug to the NextJs team as well

richardmarquet avatar Feb 17 '24 01:02 richardmarquet

To update this thread again, if you would like to use this functionality in a server component, you can! As it turns out react has an optimization on the server side that basically gets rid of all keyless fragments. Give your fragment a key and you should be good to go :)

richardmarquet avatar Mar 01 '24 03:03 richardmarquet

Any help with using the Accordion instead the Collapsible?

abelqh9 avatar Mar 15 '24 15:03 abelqh9

I had a same request as well, main request was to click a campaign and a subtable would expand.

First approach was:

 <CollapsibleContent asChild>
    {openRows[row.id] && (
     <tr className="p-4">
        <SubTable data={geoData} />
      </tr>
      )}
</CollapsibleContent>

And then inside the <SubTable /> component, I used html table attributes with <tr cloSpan={15} >

Second Approach was :

Used Table component directly inside <GeoUnitsTable />, repeated the same steps mentioned by shadcn doc, but need to use <tr colSpan={15} > to wrap the while table.

<td colSpan={15} className="rounded-md border z-99">
    <Table className="static h-auto">
       ..
     </Table>
</td>

AndreaFan123 avatar Mar 27 '24 07:03 AndreaFan123

Did anyone find a solution for this, im stuggleing to make it work. Also @mfts i couldent find source code for you guide on the expandable rows. I have a issue that the expandable row is not full-width.

charlesoestergaard avatar Apr 04 '24 08:04 charlesoestergaard

This works for me

first, if you are working with nextJS the component needs to be a client component

"use client";

second, you have to make a little change in the ui/table.tsx (add a type="" attr)

// the empty type attribute is a workaround for a problem with radix dialog on tables
const TableRow = React.forwardRef<
  HTMLTableRowElement,
  React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
  <tr
    ref={ref}
    className={cn(
      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
      className
    )}
    {...props}
    // @ts-ignore
    type=""
  />
));
TableRow.displayName = "TableRow";

third, you need write something like this:

      <Table>
        <TableHeader>
          <TableRow>
            <TableHead className="w-[100px]">Invoice</TableHead>
            <TableHead>Status</TableHead>
            <TableHead>Method</TableHead>
            <TableHead className="text-right">Amount</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {invoices.map((invoice, i) => (
            <Collapsible asChild>
              <>
                <CollapsibleTrigger asChild>
                  <TableRow key={invoice.invoice}>
                    <TableCell className="font-medium">
                      {invoice.invoice}
                    </TableCell>
                    <TableCell>{invoice.paymentStatus}</TableCell>
                    <TableCell>{invoice.paymentMethod}</TableCell>
                    <TableCell className="text-right">
                      {invoice.totalAmount}
                    </TableCell>
                  </TableRow>
                </CollapsibleTrigger>
                <CollapsibleContent asChild>
                  <TableRow key={invoice.invoice}>
                    <TableCell className="font-medium">----</TableCell>
                    <TableCell>----</TableCell>
                    <TableCell>----</TableCell>
                    <TableCell className="text-right">----</TableCell>
                  </TableRow>
                </CollapsibleContent>
              </>
            </Collapsible>
          ))}
        </TableBody>
      </Table>

I also did some quick tests with the Accordion component but I couldn't get it to work as it should due to lack of time.

abelqh9 avatar Apr 04 '24 13:04 abelqh9

Since I found this several times, trying to find a way to simply add an expandable section with just shadcn. I'll add the solution I'm happy with. I recommend following the shadcn example, you dont need everything, but you do need the row selection portion. With that added in, swap out TableBody with this. Most important thing with this, was discovering the colSpan, that solved all my issues, as before all my attempts resulted in the expandable only being in the first column.

<TableBody>
              {table.getRowModel().rows?.length ? (
                table.getRowModel().rows.map((row) => {

                    return (
                        <>
                        <TableRow
                            key={row.id}
                            data-state={row.getIsSelected() && "selected"}
                        >
                            {row.getVisibleCells().map((cell) => (
                                <TableCell key={cell.id}>
                                {flexRender(cell.column.columnDef.cell, cell.getContext())}
                                </TableCell>
                            ))}
                        </TableRow >

                        {row.getIsSelected() && (
                            <TableRow>
                                <TableCell colSpan={row.getVisibleCells().length}>
                                    
                                            <div className="w-full h-64">
                                                <h1>Content Hereeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</h1>
                                            </div>
                                </TableCell>
                            </TableRow>
                        )}
                        </>
                    )
                })
              ) : (
                <TableRow>
                  <TableCell colSpan={columns.length} className="h-24 text-center">
                    No results.
                  </TableCell>
                </TableRow>
              )}
            </TableBody>

chad2320 avatar Jun 06 '24 04:06 chad2320

Anyone able to get it to work with Accordion? I keep getting Error: React.Children.only expected to receive a single React element child. despite adding use client and Fragments with keys as the child of each accordion element with asChild.

dylanhu7 avatar Jun 28 '24 14:06 dylanhu7

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Jul 21 '24 23:07 shadcn

Sorry to dig up a closed issue, this should probably be a discussion instead...

I was trying to get the Accordion component working with the shadcn Table component, and was having issues, similar to @dylanhu7

The shadcn AccordionTrigger has a Chevron arrow icon baked in, which breaks the asChild usage.

To get around this, I ended up using the Radix accordion primitives instead (to not break other usage of the Accordion component around my app, and for more granular control).

I managed to get a result I'm fairly happy with, with only one exception: I haven't managed to get the animate-accordion-down or animate-accordion-up data state transitions to work on this. I did get the arrow transition working though.

The other hacky workaround here is that the expanded Accordion.Content won't work right unless it itself is a row, with a cell that spans all columns.

Here is my minimum viable approach!

import * as Accordion from "@radix-ui/react-accordion";

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "~/components/ui/table";

export function AccordionTable() {
  <Table>
    <TableHeader>
      <TableRow>
        <TableHead>Invoice</TableHead>
        <TableHead>Status</TableHead>
        <TableHead>Method</TableHead>
        <TableHead>Amount</TableHead>
        <TableHead></TableHead> {/* Empty cell for the arrow, if desired */}
      </TableRow>
    </TableHeader>

    <TableBody>
      <Accordion.Root type="multiple" asChild>
        {invoices?.map((invoice) => (
          <Accordion.Item key={invoice.id} value={invoice.id} asChild>
            <>
              {/* Table row */}
              <Accordion.Trigger asChild>
                <TableRow className="hover:cursor-pointer [&[data-state=open]>td#arrow>svg]:rotate-90">
                  <TableCell>{invoice.id}</TableCell>
                  <TableCell>{invoice.status}</TableCell>
                  <TableCell>{invoice.method}</TableCell>
                  <TableCell>{invoice.amount}</TableCell>

                  {/* Arrow cell */}
                  <TableCell id="arrow">
                    <ChevronRightIcon className="size-4 shrink-0 transition-transform duration-200" />
                  </TableCell>
                </TableRow>
              </Accordion.Trigger>

              {/* Expanded row */}
              <Accordion.Content asChild>
                <TableRow>
                  <TableCell
                    colSpan={5} // make sure to span all columns
                    className="min-h-62 w-full bg-muted p-4"
                  >
                    <div>Expanded row content goes here</div>
                  </TableCell>
                </TableRow>
              </Accordion.Content>
            </>
          </Accordion.Item>
        ))}
      </Accordion.Root>
    </TableBody>
  </Table>;
}

dir avatar Jul 26 '24 15:07 dir