MynaUI
No results found.
Theme
Toggle Theme (Dark)
Documentation
Getting Started
Design System
Changelog
FAQ
Legal
Icons
Elements
Accordion
Alert Dialog
Alert
Avatar Groups
Avatar
Badge
Breadcrumb
Button Groups
Button
Calendar
Combobox
Command
Context Menu
Data Table
Dialog
Drawer
Dropdown Menu
Menubar
Pagination
Popover
Progress
Rating
Sheet
Skeleton
Spinner
Table
Tabs
Toast
Toggle and Toggle Group
Tooltip
Forms
Checkbox
Date Picker
Input OTP
Input
Radio
Select
Slider
Switch
Textarea
Marketing
404
Banners
Blog List
Blog Post
Call to Action
Cookies
FAQ
Features
Footer
Header
Hero
Newsletters
Statistics
Testimonial Logos
Application
App Headers
App Sheets
Application Dialogs
Card Headers
Cards
Containers
Dividers
Empty States
Forgot Password
Login
Notifications
Registration
Section Headers
Documentation
  • Getting Started
  • Copybook New
  • Design System
  • Icons Updated
  • Changelog
  • Legal
  • Figma
  • Request Components
  • Request Icons
Elements
  • Accordion
  • Alert Dialog
  • Alert
  • Avatar Groups
  • Avatar
  • Badge
  • Breadcrumb
  • Button Groups
  • Button
  • Calendar
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Dialog
  • Drawer
  • Dropdown Menu
  • Menubar
  • Pagination
  • Popover
  • Progress
  • Rating
  • Sheet
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Toast
  • Toggle and Toggle Group
  • Tooltip
Forms
  • Checkbox
  • Date Picker
  • Input OTP
  • Input
  • Radio
  • Select
  • Slider
  • Switch
  • Textarea
Marketing
  • 404
  • Banners
  • Blog List
  • Blog Post
  • Call to Action
  • Cookies
  • FAQ
  • Features
  • Footer
  • Header
  • Hero
  • Newsletters
  • Statistics
  • Testimonial Logos
Application
  • App Headers
  • App Sheets
  • Application Dialogs
  • Card Headers
  • Cards
  • Containers
  • Dividers
  • Empty States
  • Forgot Password
  • Login
  • Notifications
  • Registration
  • Section Headers
Newsletter

Get the latest news and updates from MynaUI

Subscribe for Updates

Data Table

Data tables display information in a grid-like format of rows and columns.

"use client";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Pencil, Trash } from "@mynaui/icons-react";
import { useMemo, useState } from "react";

type Bookmark = {
  id: number;
  title: string;
  url: string;
  tags: string[];
  description: string;
  createdAt: string;
};

type SortColumn = keyof Bookmark;

export default function Component() {
  const [bookmarks] = useState<Bookmark[]>([
    {
      id: 1,
      title: "Vercel",
      url: "https://vercel.com",
      tags: ["web", "deployment"],
      description:
        "Vercel is a cloud platform for static sites and serverless functions.",
      createdAt: "2023-05-01",
    },
    {
      id: 2,
      title: "Tailwind CSS",
      url: "https://tailwindcss.com",
      tags: ["css", "framework"],
      description:
        "Tailwind CSS is a utility-first CSS framework for rapidly building custom designs.",
      createdAt: "2023-04-15",
    },
    {
      id: 3,
      title: "React",
      url: "https://reactjs.org",
      tags: ["javascript", "library"],
      description:
        "React is a JavaScript library for building user interfaces.",
      createdAt: "2023-03-20",
    },
    {
      id: 4,
      title: "Next.js",
      url: "https://nextjs.org",
      tags: ["react", "framework"],
      description:
        "Next.js is a React framework that enables server-side rendering and more.",
      createdAt: "2023-02-10",
    },
    {
      id: 5,
      title: "Prisma",
      url: "https://www.prisma.io",
      tags: ["database", "orm"],
      description:
        "Prisma is an open-source database toolkit that includes an ORM.",
      createdAt: "2023-01-01",
    },
  ]);
  const [searchTerm, setSearchTerm] = useState("");
  const [sortColumn, setSortColumn] = useState<SortColumn>("title");
  const [sortDirection, setSortDirection] = useState("asc");
  const filteredBookmarks = useMemo(() => {
    return bookmarks.filter((bookmark) =>
      bookmark.title.toLowerCase().includes(searchTerm.toLowerCase()),
    );
  }, [bookmarks, searchTerm]);

  const sortedBookmarks = useMemo(() => {
    return filteredBookmarks.sort((a, b) => {
      if (a[sortColumn] < b[sortColumn])
        return sortDirection === "asc" ? -1 : 1;
      if (a[sortColumn] > b[sortColumn])
        return sortDirection === "asc" ? 1 : -1;
      return 0;
    });
  }, [filteredBookmarks, sortColumn, sortDirection]);

  const handleSort = (column: SortColumn) => {
    if (sortColumn === column) {
      setSortDirection(sortDirection === "asc" ? "desc" : "asc");
    } else {
      setSortColumn(column);
      setSortDirection("asc");
    }
  };
  return (
    <div className="mx-auto w-full max-w-6xl rounded border">
      <div className="flex flex-wrap items-center justify-between gap-4 border-b p-4 md:py-2">
        <h1 className="text-xl font-bold">Bookmarks</h1>
        <Input
          placeholder="Search bookmarks..."
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          className="md:w-96"
        />
      </div>
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead
              className="cursor-pointer"
              onClick={() => handleSort("title")}
            >
              Title
              {sortColumn === "title" && (
                <span className="ml-1">
                  {sortDirection === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="cursor-pointer"
              onClick={() => handleSort("url")}
            >
              URL
              {sortColumn === "url" && (
                <span className="ml-1">
                  {sortDirection === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="cursor-pointer"
              onClick={() => handleSort("tags")}
            >
              Tags
              {sortColumn === "tags" && (
                <span className="ml-1">
                  {sortDirection === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="cursor-pointer"
              onClick={() => handleSort("description")}
            >
              Description
              {sortColumn === "description" && (
                <span className="ml-1">
                  {sortDirection === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="cursor-pointer"
              onClick={() => handleSort("createdAt")}
            >
              Created
              {sortColumn === "createdAt" && (
                <span className="ml-1">
                  {sortDirection === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead>Actions</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {sortedBookmarks.map((bookmark) => (
            <TableRow key={bookmark.id}>
              <TableCell className="font-medium">{bookmark.title}</TableCell>
              <TableCell>
                <a
                  href="#"
                  target="_blank"
                  className="text-blue-500 hover:underline"
                >
                  {bookmark.url}
                </a>
              </TableCell>
              <TableCell className="flex flex-wrap gap-1">
                {bookmark.tags.map((tag, index) => (
                  <Badge variant="outline" key={index}>
                    {tag}
                  </Badge>
                ))}
              </TableCell>
              <TableCell>{bookmark.description}</TableCell>
              <TableCell>{bookmark.createdAt}</TableCell>
              <TableCell className="flex gap-1">
                <Button variant="ghost" size="icon">
                  <Pencil className="size-4" />
                </Button>
                <Button variant="ghost" size="icon">
                  <Trash className="size-4" />
                </Button>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
"use client";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { ArrowUp, Pencil, Trash } from "@mynaui/icons-react";
import { useMemo, useState } from "react";

interface Tag {
  name: string;
  bookmarks: number;
  description: string;
  relatedTags: string[];
}

interface SortState {
  key: keyof Tag;
  order: "asc" | "desc";
}

export default function WithSort() {
  const [search, setSearch] = useState<string>("");
  const [sort, setSort] = useState<SortState>({ key: "name", order: "asc" });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const tags: Tag[] = [
    {
      name: "React",
      bookmarks: 1234,
      description: "A JavaScript library for building user interfaces",
      relatedTags: ["JavaScript", "Frontend", "UI"],
    },
    {
      name: "Node.js",
      bookmarks: 2345,
      description:
        "A JavaScript runtime built on Chrome's V8 JavaScript engine",
      relatedTags: ["JavaScript", "Backend", "Server"],
    },
    {
      name: "Python",
      bookmarks: 3456,
      description:
        "A high-level programming language known for its readability and versatility",
      relatedTags: ["Programming", "Data Science", "Machine Learning"],
    },
    {
      name: "Vue.js",
      bookmarks: 1567,
      description:
        "A progressive JavaScript framework for building user interfaces",
      relatedTags: ["JavaScript", "Frontend", "UI"],
    },
    {
      name: "Ruby on Rails",
      bookmarks: 2678,
      description: "A server-side web application framework written in Ruby",
      relatedTags: ["Ruby", "Backend", "Web Development"],
    },
    {
      name: "Angular",
      bookmarks: 3789,
      description:
        "A TypeScript-based web application framework for building single-page applications",
      relatedTags: ["TypeScript", "Frontend", "SPA"],
    },
  ];
  const filteredTags = useMemo(() => {
    return tags
      .filter((tag) => {
        const searchValue = search.toLowerCase();
        return (
          tag.name.toLowerCase().includes(searchValue) ||
          tag.description.toLowerCase().includes(searchValue) ||
          tag.relatedTags.some((relatedTag) =>
            relatedTag.toLowerCase().includes(searchValue),
          )
        );
      })
      .sort((a, b) => {
        if (sort.order === "asc") {
          return a[sort.key] > b[sort.key] ? 1 : -1;
        } else {
          return a[sort.key] < b[sort.key] ? 1 : -1;
        }
      });
  }, [search, sort.key, sort.order, tags]);
  return (
    <div className="mx-auto w-full max-w-6xl rounded border">
      <div className="flex flex-wrap items-center justify-between gap-4 border-b p-4 md:py-2">
        <h1 className="text-xl font-bold">Tag Cloud</h1>
        <div className="flex items-center gap-2">
          <Input
            value={search}
            placeholder="Search tags..."
            onChange={(e) => setSearch(e.target.value)}
          />
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="outline">
                <ArrowUp className="size-4 stroke-2 text-muted-foreground" />
                Sort by
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent className="w-[200px]" align="end">
              <DropdownMenuRadioGroup
                value={sort.key}
                onValueChange={(key) =>
                  setSort({ key: key as keyof Tag, order: sort.order })
                }
              >
                <DropdownMenuRadioItem value="name">Name</DropdownMenuRadioItem>
                <DropdownMenuRadioItem value="bookmarks">
                  Bookmarks
                </DropdownMenuRadioItem>
                <DropdownMenuRadioItem value="description">
                  Description
                </DropdownMenuRadioItem>
              </DropdownMenuRadioGroup>
              <DropdownMenuSeparator />
              <DropdownMenuRadioGroup
                value={sort.order}
                onValueChange={(order) =>
                  setSort({ key: sort.key, order: order as "asc" | "desc" })
                }
              >
                <DropdownMenuRadioItem value="asc">
                  Ascending
                </DropdownMenuRadioItem>
                <DropdownMenuRadioItem value="desc">
                  Descending
                </DropdownMenuRadioItem>
              </DropdownMenuRadioGroup>
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
      </div>
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead
              className="w-[200px]"
              onClick={() =>
                setSort({
                  key: "name",
                  order:
                    sort.key === "name"
                      ? sort.order === "asc"
                        ? "desc"
                        : "asc"
                      : "asc",
                })
              }
            >
              Tag Name
              {sort.key === "name" && (
                <span className="ml-1">
                  {sort.order === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="w-[150px] text-right"
              onClick={() =>
                setSort({
                  key: "bookmarks",
                  order:
                    sort.key === "bookmarks"
                      ? sort.order === "asc"
                        ? "desc"
                        : "asc"
                      : "asc",
                })
              }
            >
              Bookmarks
              {sort.key === "bookmarks" && (
                <span className="ml-1">
                  {sort.order === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="flex-1"
              onClick={() =>
                setSort({
                  key: "description",
                  order:
                    sort.key === "description"
                      ? sort.order === "asc"
                        ? "desc"
                        : "asc"
                      : "asc",
                })
              }
            >
              Description
              {sort.key === "description" && (
                <span className="ml-1">
                  {sort.order === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead
              className="w-[200px]"
              onClick={() =>
                setSort({
                  key: "relatedTags",
                  order:
                    sort.key === "relatedTags"
                      ? sort.order === "asc"
                        ? "desc"
                        : "asc"
                      : "asc",
                })
              }
            >
              Related Tags
              {sort.key === "relatedTags" && (
                <span className="ml-1">
                  {sort.order === "asc" ? "\u2191" : "\u2193"}
                </span>
              )}
            </TableHead>
            <TableHead className="w-[100px]">Actions</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {filteredTags.map((tag) => (
            <TableRow key={tag.name}>
              <TableCell className="font-medium">{tag.name}</TableCell>
              <TableCell className="text-right">
                {tag.bookmarks.toLocaleString()}
              </TableCell>
              <TableCell>{tag.description}</TableCell>
              <TableCell>
                <div className="flex flex-wrap gap-2">
                  {tag.relatedTags.map((relatedTag) => (
                    <Badge variant="outline" key={relatedTag}>
                      {relatedTag}
                    </Badge>
                  ))}
                </div>
              </TableCell>
              <TableCell className="flex items-center justify-end gap-2">
                <Button variant="ghost" size="icon">
                  <Pencil className="size-5" />
                </Button>
                <Button variant="ghost" size="icon">
                  <Trash className="size-5" />
                </Button>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
"use client";

import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Eye, Pencil, Trash } from "@mynaui/icons-react";
import { useCallback, useMemo, useState } from "react";

const initialColumns = [
  "username",
  "bookmarks",
  "folders",
  "tags",
  "joined",
  "actions",
];

const usersData = [
  {
    username: "johndoe",
    bookmarks: 25,
    folders: 10,
    tags: 50,
    joined: "2021-03-15",
  },
  {
    username: "janesmith",
    bookmarks: 18,
    folders: 7,
    tags: 32,
    joined: "2022-01-01",
  },
  {
    username: "bobwilson",
    bookmarks: 32,
    folders: 15,
    tags: 60,
    joined: "2020-09-01",
  },
  {
    username: "sarahjones",
    bookmarks: 14,
    folders: 5,
    tags: 22,
    joined: "2023-02-28",
  },
  {
    username: "mikeanderson",
    bookmarks: 28,
    folders: 12,
    tags: 45,
    joined: "2021-11-10",
  },
];

export default function WithVisibility() {
  const [selectedColumns, setSelectedColumns] = useState(initialColumns);

  const handleColumnToggle = useCallback((column: string) => {
    setSelectedColumns((prevColumns) =>
      prevColumns.includes(column)
        ? prevColumns.filter((col) => col !== column)
        : [...prevColumns, column],
    );
  }, []);

  const columns = useMemo(
    () => ["username", "bookmarks", "folders", "tags", "joined", "actions"],
    [],
  );

  return (
    <Card className="mx-auto w-full max-w-6xl">
      <CardHeader className="flex flex-row justify-between">
        <div className="space-y-2">
          <CardTitle>User Profile Data</CardTitle>
          <CardDescription>
            View and manage user profile data, including bookmarks, folders, and
            tags.
          </CardDescription>
        </div>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="outline">
              <Eye className="size-4 stroke-2 text-muted-foreground" />
              Show/Hide
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end" className="w-[200px]">
            {columns.map((column) => (
              <DropdownMenuCheckboxItem
                key={column}
                checked={selectedColumns.includes(column)}
                onCheckedChange={() => handleColumnToggle(column)}
              >
                {column.charAt(0).toUpperCase() + column.slice(1)}
              </DropdownMenuCheckboxItem>
            ))}
          </DropdownMenuContent>
        </DropdownMenu>
      </CardHeader>
      <CardContent>
        <Table>
          <TableHeader>
            <TableRow>
              {selectedColumns.map((column) => (
                <TableHead key={column}>
                  {column.charAt(0).toUpperCase() + column.slice(1)}
                </TableHead>
              ))}
            </TableRow>
          </TableHeader>
          <TableBody>
            {usersData.map((user, index) => (
              <TableRow key={index}>
                {selectedColumns.map((column) => (
                  <TableCell key={column}>
                    {column === "username" ? (
                      <a href="#" className="font-medium">
                        {user.username}
                      </a>
                    ) : column === "bookmarks" ? (
                      user.bookmarks
                    ) : column === "folders" ? (
                      user.folders
                    ) : column === "tags" ? (
                      user.tags
                    ) : column === "joined" ? (
                      user.joined
                    ) : column === "actions" ? (
                      <div className="flex items-center gap-2">
                        <Button variant="outline" size="icon">
                          <Pencil className="size-4" />
                        </Button>
                        <Button variant="outline" size="icon">
                          <Trash className="size-4" />
                        </Button>
                      </div>
                    ) : null}
                  </TableCell>
                ))}
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </CardContent>
    </Card>
  );
}
"use client";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Trash } from "@mynaui/icons-react";
import { useState } from "react";

interface Task {
  id: number;
  task: string;
  assignee: string;
  due: string;
  status: string;
}

export default function WithSelection() {
  const tasks: Task[] = [
    {
      id: 1,
      task: "Design landing page",
      assignee: "Alice",
      due: "2024-06-01",
      status: "In Progress",
    },
    {
      id: 2,
      task: "Setup database",
      assignee: "Bob",
      due: "2024-06-05",
      status: "Pending",
    },
    {
      id: 3,
      task: "Implement auth",
      assignee: "Charlie",
      due: "2024-06-10",
      status: "Pending",
    },
    {
      id: 4,
      task: "Write documentation",
      assignee: "Dana",
      due: "2024-06-15",
      status: "Pending",
    },
  ];
  const [selected, setSelected] = useState<number[]>([]);

  const allSelected = selected.length === tasks.length;

  const toggleAll = (checked: boolean) => {
    setSelected(checked ? tasks.map((t) => t.id) : []);
  };

  const toggleOne = (id: number, checked: boolean) => {
    setSelected((prev) =>
      checked ? [...prev, id] : prev.filter((item) => item !== id),
    );
  };

  const deleteSelected = () => {
    // handle deletion logic
    alert(`Delete tasks: ${selected.join(", ")}`);
  };

  return (
    <div className="mx-auto w-full max-w-6xl rounded border">
      <div className="flex items-center justify-between border-b p-4">
        <h1 className="text-xl font-bold">Task List</h1>
        <Button
          variant="destructive"
          disabled={!selected.length}
          onClick={deleteSelected}
        >
          <Trash className="size-4" />
          <span className="sr-only">Delete selected tasks</span>
        </Button>
      </div>
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead className="w-4">
              <Checkbox
                id="select-all"
                checked={allSelected}
                onCheckedChange={(c) => toggleAll(Boolean(c))}
                aria-label="Select all tasks"
              />
            </TableHead>
            <TableHead>Task</TableHead>
            <TableHead>Assignee</TableHead>
            <TableHead>Due</TableHead>
            <TableHead>Status</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {tasks.map((task) => {
            const checked = selected.includes(task.id);
            return (
              <TableRow key={task.id} className={checked ? "bg-muted" : undefined}>
                <TableCell>
                  <Checkbox
                    id={`select-${task.id}`}
                    checked={checked}
                    onCheckedChange={(c) => toggleOne(task.id, Boolean(c))}
                    aria-label={`Select ${task.task}`}
                  />
                </TableCell>
                <TableCell className="font-medium">{task.task}</TableCell>
                <TableCell>{task.assignee}</TableCell>
                <TableCell>{task.due}</TableCell>
                <TableCell>{task.status}</TableCell>
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
    </div>
  );
}
"use client";

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { useState } from "react";

interface Customer {
  id: number;
  name: string;
  email: string;
  joined: string;
}

const customers: Customer[] = [
  { id: 1, name: "Alice Johnson", email: "alice@example.com", joined: "2024-05-01" },
  { id: 2, name: "Bob Smith", email: "bob@example.com", joined: "2024-05-03" },
  { id: 3, name: "Charlie Lee", email: "charlie@example.com", joined: "2024-05-04" },
  { id: 4, name: "Dana Miller", email: "dana@example.com", joined: "2024-05-06" },
  { id: 5, name: "Evan Brown", email: "evan@example.com", joined: "2024-05-09" },
  { id: 6, name: "Fay Green", email: "fay@example.com", joined: "2024-05-10" },
  { id: 7, name: "George King", email: "george@example.com", joined: "2024-05-11" },
  { id: 8, name: "Hannah Scott", email: "hannah@example.com", joined: "2024-05-12" },
  { id: 9, name: "Ian Clark", email: "ian@example.com", joined: "2024-05-13" },
  { id: 10, name: "Jane Doe", email: "jane@example.com", joined: "2024-05-14" },
];

export default function WithPagination() {
  const perPage = 5;
  const pageCount = Math.ceil(customers.length / perPage);
  const [page, setPage] = useState(1);

  const currentCustomers = customers.slice((page - 1) * perPage, page * perPage);

  const goToPage = (p: number) => {
    setPage(p);
  };

  return (
    <div className="mx-auto w-full max-w-6xl rounded border">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>Name</TableHead>
            <TableHead>Email</TableHead>
            <TableHead>Joined</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {currentCustomers.map((customer) => (
            <TableRow key={customer.id}>
              <TableCell className="font-medium">{customer.name}</TableCell>
              <TableCell>{customer.email}</TableCell>
              <TableCell>{customer.joined}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
      <Pagination className="py-4">
        <PaginationContent>
          <PaginationItem>
            <PaginationPrevious
              href="#"
              onClick={(e) => {
                e.preventDefault();
                if (page > 1) goToPage(page - 1);
              }}
            />
          </PaginationItem>
          {Array.from({ length: pageCount }, (_, i) => (
            <PaginationItem key={i}>
              <PaginationLink
                href="#"
                isActive={page === i + 1}
                onClick={(e) => {
                  e.preventDefault();
                  goToPage(i + 1);
                }}
              >
                {i + 1}
              </PaginationLink>
            </PaginationItem>
          ))}
          <PaginationItem>
            <PaginationNext
              href="#"
              onClick={(e) => {
                e.preventDefault();
                if (page < pageCount) goToPage(page + 1);
              }}
            />
          </PaginationItem>
        </PaginationContent>
      </Pagination>
    </div>
  );
}

Not affiliated with Figma, TailwindCSS or shadcn/ui.