From b9d14478a1f1c329f7e03d85e1c9dcab6f319543 Mon Sep 17 00:00:00 2001 From: Mahyar Khanbolooki Date: Tue, 22 Jul 2025 22:42:58 +0330 Subject: [PATCH] add: transaction history & its datatable --- package-lock.json | 167 +++++++ package.json | 3 + src/app/[name]/transactions/review/page.tsx | 54 +++ src/components/ui/datatable.tsx | 501 ++++++++++++++++++++ src/components/ui/dropdown-menu.tsx | 201 ++++++++ src/components/ui/input.tsx | 22 + src/components/ui/scrollarea.tsx | 48 ++ src/components/ui/table.tsx | 120 +++++ 8 files changed, 1116 insertions(+) create mode 100644 src/app/[name]/transactions/review/page.tsx create mode 100644 src/components/ui/datatable.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/scrollarea.tsx create mode 100644 src/components/ui/table.tsx diff --git a/package-lock.json b/package-lock.json index 1c0959d..af5320a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "0.1.0", "dependencies": { "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@tanstack/react-query": "^5.81.5", "@tanstack/react-query-devtools": "^5.81.5", + "@tanstack/react-table": "^8.21.3", "axios": "^1.10.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1201,6 +1204,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", + "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", @@ -1259,6 +1291,46 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", @@ -1399,6 +1471,68 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz", + "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slider": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", @@ -1979,6 +2113,39 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", diff --git a/package.json b/package.json index b079fe2..9cdd7c4 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,15 @@ }, "dependencies": { "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@tanstack/react-query": "^5.81.5", "@tanstack/react-query-devtools": "^5.81.5", + "@tanstack/react-table": "^8.21.3", "axios": "^1.10.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/app/[name]/transactions/review/page.tsx b/src/app/[name]/transactions/review/page.tsx new file mode 100644 index 0000000..4c2d7d7 --- /dev/null +++ b/src/app/[name]/transactions/review/page.tsx @@ -0,0 +1,54 @@ +import { DataTableDemo } from '@/components/ui/datatable' +import { MoneyRecive, MoneySend, Wallet } from 'iconsax-react' +import { ArrowDownLeft, ArrowUpRight } from 'lucide-react' +import React from 'react' + +function TransactionsReviewIndex() { + return ( +
+

لیست تراکنش ها

+ +
+
+
+ +
+ مجموع واریز ها + 140,000,000 ریال + + +
30%
+
+
+
+
+ +
+ مجموع برداشت ها + 140,000,000 ریال + + +
30%
+
+
+
+
+ +
+ موجودی کیف پول + 140,000,000 ریال + + +
30%
+
+
+
+ +
+ +
+
+ ) +} + +export default TransactionsReviewIndex \ No newline at end of file diff --git a/src/components/ui/datatable.tsx b/src/components/ui/datatable.tsx new file mode 100644 index 0000000..3ea9721 --- /dev/null +++ b/src/components/ui/datatable.tsx @@ -0,0 +1,501 @@ +"use client" + +import * as React from "react" +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table" + +import { Button } from "@/components/ui/button" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import clsx from "clsx" +import { EyeToggleIcon } from "../icons/EyeToggleIcon" +import { ArrowLeft2, ArrowRight2 } from "iconsax-react" + +const data: Payment[] = [ + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "m5gr84i9", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "3u1reuv4", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "derv1ws0", + type: 'تایپ', + status: false, + date: 14040401, + }, + { + id: "5kma53ae", + type: 'تایپ', + status: true, + date: 14040401, + }, + { + id: "bhqecj4p", + type: 'تایپ', + status: true, + date: 14040401, + }, +] + +export type Payment = { + id: string + type: string + // status: "pending" | "processing" | "success" | "failed" + status: boolean + date: number +} + +export const columns: ColumnDef[] = [ + { + accessorKey: "id", + header: "شماره", + cell: ({ row }) => ( +
{row.getValue("id")}
+ ), + }, + { + accessorKey: "type", + header: () => "نوع تراکنش", + cell: ({ row }) =>
{row.getValue("type")}
, + }, + { + accessorKey: "date", + header: () =>
تاریخ
, + cell: ({ row }) => { + const date = parseFloat(row.getValue("date")) + + // Format the amount as a dollar amount + // const formatted = new Intl.NumberFormat("en-US", { + // style: "currency", + // currency: "USD", + // }).format(amount) + // TODO: convert to jalali + + return
{date}
+ }, + }, + { + accessorKey: "status", + header: () =>
وضعیت
, + cell: ({ row }) => { + const status = Boolean(row.getValue("status")) + + const str = status ? 'پاسخ داده شده' : 'پاسخ داده نشده'; + + return ( +
+ + {str} +
+ ) + }, + }, + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + const status = Boolean(row.getValue("status")) + + return ( + <> + + + ) + }, + }, +] + +export function DataTableDemo() { + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + const [columnVisibility, setColumnVisibility] = + React.useState({}) + const [rowSelection, setRowSelection] = React.useState({}) + + const table = useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }) + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ نمایش: {table.getRowModel().rows.length} از {table.getFilteredRowModel().rows.length} +
+
+ + + + + + +
+
+
+ ) +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..5a20503 --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,201 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..69b64fb --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/components/ui/scrollarea.tsx b/src/components/ui/scrollarea.tsx new file mode 100644 index 0000000..0b4a48d --- /dev/null +++ b/src/components/ui/scrollarea.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..c0df655 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,120 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}