add: transaction history & its datatable

This commit is contained in:
Mahyar Khanbolooki
2025-07-22 22:42:58 +03:30
parent 2454107da0
commit b9d14478a1
8 changed files with 1116 additions and 0 deletions
+167
View File
@@ -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",
+3
View File
@@ -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",
@@ -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 (
<section className='flex flex-col h-svh pb-10 pt-6 gap-4'>
<h1 className="font-medium text-base">لیست تراکنش ها</h1>
<div className="grid grid-cols-3 gap-2 h-28">
<div className="w-full h-full flex flex-col items-center justify-center p-2.5 text-center bg-container rounded-xl">
<div className="size-8 bg-background rounded-full grid items-center">
<MoneySend className='stroke-primary justify-self-center' size={20} />
</div>
<span className='text-xs2 pt-1'>مجموع واریز ها</span>
<span className='text-xs font-medium mt-1'>140,000,000 ریال</span>
<span className='text-xs2 text-green-60mt-0.5 0 bg-green-100 w-min px-1 py-px items-center justify-between rounded-md flex'>
<ArrowUpRight size={10} className='stroke-green-600' />
<div className="mt-0.5">30%</div>
</span>
</div>
<div className="w-full h-full flex flex-col items-center justify-between p-2.5 text-center bg-container rounded-xl">
<div className="size-8 bg-background rounded-full grid items-center">
<MoneyRecive className='stroke-primary justify-self-center' size={20} />
</div>
<span className='text-xs2 pt-1'>مجموع برداشت ها</span>
<span className='text-xs font-medium mt-1'>140,000,000 ریال</span>
<span className='text-xs2 text-green-60mt-0.5 0 bg-green-100 w-min px-1 py-px items-center justify-between rounded-md flex'>
<ArrowUpRight size={10} className='stroke-green-600' />
<div className="mt-0.5">30%</div>
</span>
</div>
<div className="w-full h-full flex flex-col items-center justify-between p-2.5 text-center bg-container rounded-xl">
<div className="size-8 bg-background rounded-full grid items-center">
<Wallet className='stroke-primary justify-self-center' size={20} />
</div>
<span className='text-xs2 pt-1'>موجودی کیف پول</span>
<span className='text-xs font-medium mt-1'>140,000,000 ریال</span>
<span className='text-xs2 text-red-600 mt-0.5 bg-red-100 w-min px-1 py-px items-center justify-between rounded-md flex'>
<ArrowDownLeft size={10} className='stroke-red-600' />
<div className="mt-0.5">30%</div>
</span>
</div>
</div>
<div className='w-full h-full bg-white rounded-3xl mt-0'>
<DataTableDemo />
</div>
</section>
)
}
export default TransactionsReviewIndex
+501
View File
@@ -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<Payment>[] = [
{
accessorKey: "id",
header: "شماره",
cell: ({ row }) => (
<div className="capitalize">{row.getValue("id")}</div>
),
},
{
accessorKey: "type",
header: () => "نوع تراکنش",
cell: ({ row }) => <div className="capitalize">{row.getValue("type")}</div>,
},
{
accessorKey: "date",
header: () => <div className="text-right">تاریخ</div>,
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 <div className="text-right">{date}</div>
},
},
{
accessorKey: "status",
header: () => <div className="text-right">وضعیت</div>,
cell: ({ row }) => {
const status = Boolean(row.getValue("status"))
const str = status ? 'پاسخ داده شده' : 'پاسخ داده نشده';
return (
<div className='text-right flex items-center justify-center gap-x-2'>
<span className={clsx('size-2 rounded-full', status ? 'bg-[#00BA4B]' : 'bg-transparent border border-primary')}></span>
{str}
</div>
)
},
},
{
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const status = Boolean(row.getValue("status"))
return (
<>
<EyeToggleIcon slash={status} />
</>
)
},
},
]
export function DataTableDemo() {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({})
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 (
<div className="w-full">
<div className="rounded-3xl w-full">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} className="font-light text-xs text-disabled-text text-center">
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
className="text-center w-full text-xs odd:bg-[#E9EBF433] even:bg-container border-0 border-b h-14 border-r-4 border-r-transparent active:border-r-primary"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 px-7">
<div className="flex-1 text-sm text-disabled-text">
نمایش: <span className="text-primary">{table.getRowModel().rows.length}</span> از {table.getFilteredRowModel().rows.length}
</div>
<div className="space-x-2 justify-center flex">
<Button
variant="ghost"
size="sm"
onClick={() => table.previousPage()}
hidden={!table.getCanPreviousPage()}
>
<ArrowRight2 size={12} className="stroke-[#A8ABBF]" />
</Button>
<Button
variant='secondary'
size="sm"
onClick={() => table.previousPage()}
hidden={!table.getCanPreviousPage()}
>
{table.getState().pagination.pageIndex}
</Button>
<Button
variant='default'
size="sm"
hidden={!table.getCanPreviousPage() && !table.getCanNextPage()}
>
{table.getState().pagination.pageIndex + 1}
</Button>
<Button
variant='secondary'
size="sm"
onClick={() => table.nextPage()}
hidden={!table.getCanNextPage()}
>
{table.getState().pagination.pageIndex + 2}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => table.nextPage()}
hidden={!table.getCanNextPage()}
>
<ArrowLeft2 size={12} className="stroke-[#A8ABBF]" />
</Button>
</div>
</div>
</div>
)
}
+201
View File
@@ -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<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
+22
View File
@@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
+48
View File
@@ -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<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
+120
View File
@@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
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}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}