Skip to content

Commit 7ca0b77

Browse files
committed
CustomizedTable and enhance TableGrid with customizable components and styles.
1 parent cfb71f3 commit 7ca0b77

File tree

5 files changed

+463
-37
lines changed

5 files changed

+463
-37
lines changed

src/app/page.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@ import BasicSearch from "@/components/containers/basic-search";
44
import FuzzySearchFilter from "@/components/containers/fuzzy-search-filter";
55
import ColumnVisibilityTable from "@/components/containers/column-visibility-table";
66
import ColumnPinningTable from "@/components/containers/toggle-column-pinning-table";
7+
import CustomizedTable from "@/components/containers/customized-table";
78

89
export default function Home() {
910
return (
10-
<div>
11-
<ColumnPinningTable />
12-
<ColumnVisibilityTable />
13-
<BasicTable />
14-
<HeaderGroupsTable />
15-
<BasicSearch />
16-
<FuzzySearchFilter />
17-
</div>
11+
<main className="min-h-screen p-4">
12+
<div className="space-y-8">
13+
<div className="border rounded-lg">
14+
<CustomizedTable />
15+
</div>
16+
<ColumnPinningTable />
17+
<ColumnVisibilityTable />
18+
<BasicTable />
19+
<HeaderGroupsTable />
20+
<BasicSearch />
21+
<FuzzySearchFilter />
22+
</div>
23+
</main>
1824
);
1925
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
"use client"
2+
import TableGrid from "@/components/ui/table-grid/table-grid"
3+
import dummyData from "@/data/dummy.json"
4+
import type { Column } from "@/components/ui/table-grid/table-grid"
5+
import { useTableGrid } from "@/hooks/use-table-grid"
6+
import { useState, useEffect } from "react"
7+
import { PiCaretDownFill, PiCaretUpFill, PiMagnifyingGlass } from "react-icons/pi"
8+
import { FiAlertCircle } from "react-icons/fi"
9+
10+
interface DataItem extends Record<string, unknown> {
11+
id: number
12+
name: string
13+
age: number
14+
email: string
15+
department: string
16+
role: string
17+
status: string
18+
}
19+
20+
const columns: Column<DataItem>[] = [
21+
{
22+
id: "name",
23+
header: "Name",
24+
accessorKey: "name",
25+
sortable: true,
26+
},
27+
{
28+
id: "department",
29+
header: "Department",
30+
accessorKey: "department",
31+
sortable: true,
32+
},
33+
{
34+
id: "role",
35+
header: "Role",
36+
accessorKey: "role",
37+
sortable: true,
38+
},
39+
{
40+
id: "status",
41+
header: "Status",
42+
accessorKey: "status",
43+
sortable: true,
44+
},
45+
{
46+
id: "email",
47+
header: "Email",
48+
accessorKey: "email",
49+
sortable: true,
50+
},
51+
]
52+
53+
// Custom Components
54+
const CustomHeader = ({
55+
column,
56+
sortIcon,
57+
onSort
58+
}: {
59+
column: Column<DataItem>
60+
sortIcon?: React.ReactNode
61+
onSort?: () => void
62+
}) => {
63+
const sortDirection = sortIcon?.toString().includes("PiCaretUpFill") ? "asc" : "desc"
64+
65+
return (
66+
<div className="flex items-center justify-between px-4 py-3">
67+
<span className="font-medium text-gray-700 dark:text-gray-200">
68+
{typeof column.header === 'function' ? column.header() : column.header}
69+
</span>
70+
{column.sortable && (
71+
<button
72+
onClick={onSort}
73+
className="ml-2 p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
74+
type="button"
75+
>
76+
{sortIcon ? (
77+
<div className="text-blue-500 dark:text-blue-400">
78+
{sortDirection === "asc" ? (
79+
<PiCaretUpFill size={16} />
80+
) : (
81+
<PiCaretDownFill size={16} />
82+
)}
83+
</div>
84+
) : (
85+
<PiCaretDownFill size={16} className="text-gray-400" />
86+
)}
87+
</button>
88+
)}
89+
</div>
90+
)
91+
}
92+
93+
const CustomCell = ({ value, column }: { value: unknown; column: Column<DataItem> }) => {
94+
if (column.accessorKey === 'status') {
95+
const statusColors: Record<string, string> = {
96+
active: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
97+
inactive: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
98+
pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
99+
}
100+
101+
const status = String(value).toLowerCase()
102+
const colorClass = statusColors[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'
103+
104+
return (
105+
<span className={`px-3 py-1 rounded-full text-sm font-medium ${colorClass}`}>
106+
{String(value)}
107+
</span>
108+
)
109+
}
110+
111+
return (
112+
<div className="px-4 py-3 text-gray-700 dark:text-gray-200">
113+
{String(value)}
114+
</div>
115+
)
116+
}
117+
118+
const CustomEmptyState = () => (
119+
<div className="flex flex-col items-center justify-center p-8 bg-white dark:bg-gray-800 rounded-lg shadow-sm">
120+
<FiAlertCircle className="w-12 h-12 text-gray-400 mb-4" />
121+
<h3 className="text-lg font-medium text-gray-900 dark:text-white">No Results Found</h3>
122+
<p className="text-gray-500 dark:text-gray-400 text-center mt-2 max-w-sm">
123+
We couldn&apos;t find any items matching your criteria. Try adjusting your search or filters.
124+
</p>
125+
</div>
126+
)
127+
128+
const CustomLoadingState = () => (
129+
<div className="flex flex-col items-center justify-center p-8">
130+
<div className="relative">
131+
<div className="w-12 h-12 rounded-full border-4 border-gray-200 dark:border-gray-700 animate-pulse" />
132+
<div className="absolute top-0 left-0 w-12 h-12 rounded-full border-4 border-blue-500 dark:border-blue-400 animate-spin border-t-transparent" />
133+
</div>
134+
<span className="mt-4 text-sm text-gray-500 dark:text-gray-400">Loading data...</span>
135+
</div>
136+
)
137+
138+
const CustomSearchInput = ({
139+
value,
140+
onChange,
141+
placeholder
142+
}: {
143+
value: string
144+
onChange: (value: string) => void
145+
placeholder?: string
146+
}) => (
147+
<div className="relative max-w-md w-full">
148+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
149+
<PiMagnifyingGlass className="h-5 w-5 text-gray-400" />
150+
</div>
151+
<input
152+
type="text"
153+
value={value}
154+
onChange={(e) => onChange(e.target.value)}
155+
placeholder={placeholder}
156+
className="block w-full pl-10 pr-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg
157+
bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
158+
focus:ring-2 focus:ring-blue-500 focus:border-transparent
159+
placeholder-gray-500 dark:placeholder-gray-400
160+
transition-colors duration-200"
161+
/>
162+
</div>
163+
)
164+
165+
const CustomizedTable = () => {
166+
const [isLoading, setIsLoading] = useState(true)
167+
const [showEmpty, setShowEmpty] = useState(false)
168+
169+
const {
170+
filteredData,
171+
handleSort,
172+
sortColumn,
173+
sortDirection,
174+
setFilterValue,
175+
filterValue,
176+
} = useTableGrid<DataItem>({
177+
data: showEmpty ? [] : dummyData,
178+
columns,
179+
enableFuzzySearch: true,
180+
})
181+
182+
useEffect(() => {
183+
const timer = setTimeout(() => {
184+
setIsLoading(false)
185+
}, 2000)
186+
return () => clearTimeout(timer)
187+
}, [])
188+
189+
return (
190+
<div className="p-6 space-y-6">
191+
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
192+
<div>
193+
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white">
194+
Team Members
195+
</h2>
196+
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
197+
Manage your team members and their account permissions here.
198+
</p>
199+
</div>
200+
<div className="flex gap-3">
201+
<button
202+
onClick={() => setIsLoading((prev) => !prev)}
203+
className="inline-flex items-center px-4 py-2 border border-transparent
204+
text-sm font-medium rounded-lg shadow-sm text-white
205+
bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2
206+
focus:ring-offset-2 focus:ring-blue-500 transition-colors"
207+
>
208+
Toggle Loading
209+
</button>
210+
<button
211+
onClick={() => setShowEmpty((prev) => !prev)}
212+
className="inline-flex items-center px-4 py-2 border border-gray-300
213+
dark:border-gray-600 text-sm font-medium rounded-lg shadow-sm
214+
text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800
215+
hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none
216+
focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
217+
transition-colors"
218+
>
219+
Toggle Empty
220+
</button>
221+
</div>
222+
</div>
223+
224+
<TableGrid<DataItem>
225+
columns={columns}
226+
data={filteredData}
227+
gridTemplateColumns="repeat(5, 1fr)"
228+
maxHeight="600px"
229+
variant="modern"
230+
onSort={handleSort}
231+
sortColumn={sortColumn}
232+
sortDirection={sortDirection}
233+
enableFuzzySearch={true}
234+
filterValue={filterValue}
235+
onFilterChange={setFilterValue}
236+
isLoading={isLoading}
237+
components={{
238+
Header: CustomHeader,
239+
Cell: CustomCell,
240+
EmptyState: CustomEmptyState,
241+
LoadingState: CustomLoadingState,
242+
SearchInput: CustomSearchInput,
243+
}}
244+
styleConfig={{
245+
container: {
246+
className: "border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden bg-white dark:bg-gray-800 shadow-sm",
247+
},
248+
headerCell: {
249+
className: "border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800",
250+
},
251+
row: {
252+
className: "border-b border-gray-100 dark:border-gray-700/50 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors",
253+
},
254+
searchContainer: {
255+
className: "mb-4",
256+
},
257+
}}
258+
/>
259+
</div>
260+
)
261+
}
262+
263+
export default CustomizedTable

0 commit comments

Comments
 (0)