shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
62.84k stars 3.53k forks source link

Collapsible Tables #371

Open LogicLaneTech opened 1 year ago

LogicLaneTech commented 1 year ago

How can I create collapsible tables with this?

jocarrd commented 1 year ago

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

LogicLaneTech commented 1 year ago

No SIR

I am looking for this

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

jocarrd commented 1 year ago

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>
LogicLaneTech commented 1 year ago

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

jocarrd commented 1 year ago

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"
LogicLaneTech commented 1 year ago

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

jocarrd commented 1 year ago

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

mfts commented 10 months ago

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

ajayvignesh01 commented 6 months ago

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.

mfts commented 6 months ago

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.

laurent512 commented 5 months ago

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 commented 5 months ago

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

mfts commented 4 months ago

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.

akyrey commented 4 months ago

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>
{/* ... */}
mfts commented 4 months ago

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 commented 4 months ago

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 :(

richardmarquet commented 4 months ago

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 commented 4 months ago

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 :)

abelqh9 commented 3 months ago

Any help with using the Accordion instead the Collapsible?

AndreaFan123 commented 3 months ago

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 component, I used html table attributes with

Second Approach was :

Used Table component directly inside , repeated the same steps mentioned by shadcn doc, but need to use to wrap the while table.

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

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.

abelqh9 commented 2 months ago

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.

chad2320 commented 3 weeks ago

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>
dylanhu7 commented 4 days ago

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.