Created
June 23, 2025 04:46
-
-
Save jakeleveroni/0fd40e07d4c215b26b491465d2d5cb17 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| // init project bun init --react=tailwind | |
| // add this to index.css | |
| @import "tailwindcss"; | |
| html { | |
| font-family: sans-serif; | |
| font-size: 14px; | |
| } | |
| table { | |
| border: 1px solid lightgray; | |
| } | |
| tbody { | |
| border-bottom: 1px solid lightgray; | |
| } | |
| th { | |
| border-bottom: 1px solid lightgray; | |
| border-right: 1px solid lightgray; | |
| padding: 2px 4px; | |
| } | |
| tfoot { | |
| color: gray; | |
| } | |
| tfoot th { | |
| font-weight: normal; | |
| } | |
| */ | |
| import { useMemo, useReducer, useRef, useState } from 'react' | |
| import './index.css' | |
| import { | |
| createColumnHelper, | |
| flexRender, | |
| getCoreRowModel, | |
| useReactTable, | |
| } from '@tanstack/react-table' | |
| export type Person = { | |
| firstName: string | |
| lastName: string | |
| age: number | |
| visits: number | |
| status: string | |
| progress: number | |
| } | |
| type Filter = { | |
| name: string; | |
| state: ColumnConfig; | |
| } | |
| type ColumnConfig = Record<keyof Person, boolean>; | |
| const defaultData: Person[] = [ | |
| { | |
| firstName: 'tanner', | |
| lastName: 'linsley', | |
| age: 24, | |
| visits: 100, | |
| status: 'In Relationship', | |
| progress: 50, | |
| }, | |
| { | |
| firstName: 'tandy', | |
| lastName: 'miller', | |
| age: 40, | |
| visits: 40, | |
| status: 'Single', | |
| progress: 80, | |
| }, | |
| { | |
| firstName: 'joe', | |
| lastName: 'dirte', | |
| age: 45, | |
| visits: 20, | |
| status: 'Complicated', | |
| progress: 10, | |
| }, | |
| ] | |
| const columnHelper = createColumnHelper<Person>() | |
| export function Table() { | |
| const filterNameRef = useRef<HTMLInputElement>(null); | |
| const [data, _setData] = useState(() => [...defaultData]) | |
| const [filters, setFilters] = useState<Filter[]>([]); | |
| const [checkboxes, setCheckboxes] = useState<ColumnConfig>({ | |
| age: true, | |
| firstName: true, | |
| lastName: true, | |
| progress: true, | |
| status: true, | |
| visits: true, | |
| }); | |
| const handleCheckboxChange = (name: keyof Person) => { | |
| setCheckboxes(prev => ({ | |
| ...prev, | |
| [name]: !prev[name] | |
| })); | |
| }; | |
| const columns = useMemo(() => [ | |
| ...(checkboxes.firstName ? [columnHelper.accessor('firstName', { | |
| cell: info => info.getValue(), | |
| footer: info => info.column.id, | |
| })] : []), | |
| ...(checkboxes.lastName ? [columnHelper.accessor('lastName', { | |
| id: 'lastName', | |
| cell: info => <i>{info.getValue()}</i>, | |
| header: () => <span>Last Name</span>, | |
| footer: info => info.column.id, | |
| })] : []), | |
| ...(checkboxes.age ? [columnHelper.accessor('age', { | |
| header: () => 'Age', | |
| cell: info => info.renderValue(), | |
| footer: info => info.column.id, | |
| })] : []), | |
| ...(checkboxes.visits ? [columnHelper.accessor('visits', { | |
| header: () => <span>Visits</span>, | |
| footer: info => info.column.id, | |
| })] : []), | |
| ...(checkboxes.status ? [columnHelper.accessor('status', { | |
| header: 'Status', | |
| footer: info => info.column.id, | |
| })] : []), | |
| ...(checkboxes.progress ? [columnHelper.accessor('progress', { | |
| header: 'Profile Progress', | |
| footer: info => info.column.id, | |
| })] : []), | |
| ], [checkboxes]) | |
| const table = useReactTable({ | |
| data, | |
| columns, | |
| getCoreRowModel: getCoreRowModel(), | |
| }) | |
| return ( | |
| <div className="flex justify-between p-2"> | |
| <div className="p-6 max-w-md mx-auto bg-white rounded-lg shadow-md"> | |
| <h2 className="text-xl font-bold mb-4">Select Options</h2> | |
| <div className="space-y-3 mb-4"> | |
| {Object.entries(checkboxes).map(([name, checked]) => ( | |
| <label key={name} className="flex items-center space-x-3 cursor-pointer"> | |
| <input | |
| type="checkbox" | |
| checked={checked} | |
| onChange={() => handleCheckboxChange(name as keyof Person)} | |
| className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500" | |
| /> | |
| <span className="text-gray-700 capitalize"> | |
| {name.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())} | |
| </span> | |
| </label> | |
| ))} | |
| </div> | |
| <div className='text-left'> | |
| <h3 className="font-bold text-lg">Filters</h3> | |
| <div className="flex gap-4 py-2" > | |
| {filters.map(x => <button className="appearance-none bg-blue-500 text-white hover:bg-blue-600 p-2 rounded-md hover:bg-gray" onClick={() => setCheckboxes(x.state)}>{x.name}</button>)} | |
| <button className="bg-blue-500 text-white font-bold py-2 px-3 rounded hover:bg-blue-600" onClick={() => setCheckboxes({ | |
| age: true, | |
| firstName: true, | |
| lastName: true, | |
| progress: true, | |
| status: true, | |
| visits: true, | |
| })}> | |
| All | |
| </button> | |
| <button className="bg-blue-500 text-white font-bold py-2 px-3 rounded hover:bg-blue-600" onClick={() => setCheckboxes({ | |
| age: false, | |
| firstName: false, | |
| lastName: false, | |
| progress: false, | |
| status: false, | |
| visits: false, | |
| })}> | |
| Clear | |
| </button> | |
| </div> | |
| <input className='outline p-2 me-2 rounded-md' type="text" ref={filterNameRef} placeholder="Filter name"/> | |
| <button className="bg-blue-500 text-white font-bold py-2 px-3 rounded hover:bg-blue-600" | |
| onClick={() => setFilters([...filters, { name: filterNameRef.current?.value ?? 'None', state: checkboxes }])}> | |
| Create Filter | |
| </button> | |
| </div> | |
| </div> | |
| <code> | |
| {table.getHeaderGroups()[0].headers.length === 0 && <p>No columns selected</p>} | |
| <table className="min-w-[640px]"> | |
| <thead> | |
| {table.getHeaderGroups().map(headerGroup => ( | |
| <tr key={headerGroup.id}> | |
| {headerGroup.headers.map(header => ( | |
| <th key={header.id}> | |
| {header.isPlaceholder | |
| ? null | |
| : flexRender( | |
| header.column.columnDef.header, | |
| header.getContext() | |
| )} | |
| </th> | |
| ))} | |
| </tr> | |
| ))} | |
| </thead> | |
| <tbody> | |
| {table.getRowModel().rows.map(row => ( | |
| <tr key={row.id}> | |
| {row.getVisibleCells().map(cell => ( | |
| <td key={cell.id}> | |
| {flexRender(cell.column.columnDef.cell, cell.getContext())} | |
| </td> | |
| ))} | |
| </tr> | |
| ))} | |
| </tbody> | |
| <tfoot> | |
| {table.getFooterGroups().map(footerGroup => ( | |
| <tr key={footerGroup.id}> | |
| {footerGroup.headers.map(header => ( | |
| <th key={header.id}> | |
| {header.isPlaceholder | |
| ? null | |
| : flexRender( | |
| header.column.columnDef.footer, | |
| header.getContext() | |
| )} | |
| </th> | |
| ))} | |
| </tr> | |
| ))} | |
| </tfoot> | |
| </table> | |
| </code> | |
| </div> | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment