├── .eslintrc.json ├── src ├── app │ ├── globals.css │ ├── favicon.ico │ ├── (dashboard) │ │ ├── transactions │ │ │ ├── _components │ │ │ │ ├── TanstackTable.d.ts │ │ │ │ ├── DataTableColumnHeader.tsx │ │ │ │ ├── TableBulkEditor.tsx │ │ │ │ ├── DataTablePagination.tsx │ │ │ │ ├── DataTable.tsx │ │ │ │ ├── Columns.tsx │ │ │ │ └── DataTableDrawerFeed.tsx │ │ │ └── page.tsx │ │ ├── reports │ │ │ ├── _components │ │ │ │ ├── dateRanges.ts │ │ │ │ ├── FilterDate.tsx │ │ │ │ ├── FilterExpenseStatus.tsx │ │ │ │ ├── Header.tsx │ │ │ │ ├── FilterCountry.tsx │ │ │ │ └── FilterAmount.tsx │ │ │ ├── page.tsx │ │ │ └── loading.tsx │ │ └── layout.tsx │ ├── settings │ │ ├── audit │ │ │ ├── page.tsx │ │ │ └── _components │ │ │ │ └── Approvers.tsx │ │ └── layout.tsx │ ├── siteConfig.ts │ ├── not-found.tsx │ ├── layout.tsx │ ├── onboarding │ │ ├── layout.tsx │ │ ├── employees │ │ │ └── page.tsx │ │ └── products │ │ │ └── page.tsx │ └── login │ │ └── page.tsx ├── lib │ ├── useOnWindowResize.tsx │ ├── useScroll.ts │ ├── utils.ts │ └── chartUtils.ts ├── components │ ├── ui │ │ ├── Logo.tsx │ │ └── navigation │ │ │ ├── UserProfile.tsx │ │ │ ├── DropdownUserProfile.tsx │ │ │ └── MobileSidebar.tsx │ ├── Label.tsx │ ├── Card.tsx │ ├── Divider.tsx │ ├── Textarea.tsx │ ├── Badge.tsx │ ├── KeywordInput.tsx │ ├── Slider.tsx │ ├── RadioGroup.tsx │ ├── Checkbox.tsx │ ├── Tooltip.tsx │ ├── Switch.tsx │ ├── ProgressBar.tsx │ ├── Accordion.tsx │ ├── RadioCardGroup.tsx │ ├── TabNavigation.tsx │ ├── Popover.tsx │ ├── Dialog.tsx │ ├── Tabs.tsx │ ├── Button.tsx │ ├── Table.tsx │ ├── CommandBar.tsx │ ├── Input.tsx │ ├── BarList.tsx │ ├── Drawer.tsx │ └── CategoryBar.tsx └── data │ ├── data.ts │ ├── generateData.ts │ └── schema.ts ├── postcss.config.mjs ├── .prettierrc ├── .vscode └── settings.json ├── tsconfig.scripts.json ├── next.config.mjs ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── LICENSE.md ├── README.md ├── package.json └── tailwind.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tremorlabs/template-insights/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/app/(dashboard)/transactions/_components/TanstackTable.d.ts: -------------------------------------------------------------------------------- 1 | import "@tanstack/react-table" 2 | 3 | declare module "@tanstack/react-table" { 4 | interface ColumnMeta { 5 | className?: string 6 | displayName: string 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": false, 4 | "trailingComma": "all", 5 | "endOfLine": "lf", 6 | "semi": false, 7 | "tabWidth": 2, 8 | "plugins": ["prettier-plugin-tailwindcss"], 9 | "tailwindFunctions": ["tv", "cx"] 10 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tailwindCSS.experimental.classRegex": [ 3 | ["clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 4 | ["cx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] 5 | ], 6 | "editor.formatOnSave": true, 7 | "editor.codeActionsOnSave": { 8 | "source.organizeImports": "explicit" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.scripts.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "isolatedModules": false 10 | }, 11 | "include": ["src/data"], 12 | "exclude": ["node_modules"] 13 | } -------------------------------------------------------------------------------- /src/lib/useOnWindowResize.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw useOnWindowResize [v0.0.0] 2 | 3 | import * as React from "react" 4 | 5 | export const useOnWindowResize = (handler: { (): void }) => { 6 | React.useEffect(() => { 7 | const handleResize = () => { 8 | handler() 9 | } 10 | handleResize() 11 | window.addEventListener("resize", handleResize) 12 | 13 | return () => window.removeEventListener("resize", handleResize) 14 | }, [handler]) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ui/Logo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import type { SVGProps } from "react" 3 | const Logo = (props: SVGProps) => ( 4 | 5 | 9 | 10 | ) 11 | export { Logo } 12 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | redirects: async () => { 5 | return [ 6 | { 7 | source: "/", 8 | destination: "/reports", 9 | permanent: true, 10 | }, 11 | ] 12 | }, 13 | images: { 14 | remotePatterns: [ 15 | { 16 | protocol: "https", 17 | hostname: "images.unsplash.com", 18 | port: "", 19 | pathname: "/**", 20 | }, 21 | ], 22 | }, 23 | } 24 | 25 | export default nextConfig 26 | -------------------------------------------------------------------------------- /src/lib/useScroll.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react" 2 | 3 | export default function useScroll(threshold: number) { 4 | const [scrolled, setScrolled] = useState(false) 5 | 6 | const onScroll = useCallback(() => { 7 | setScrolled(window.scrollY > threshold) 8 | }, [threshold]) 9 | 10 | useEffect(() => { 11 | window.addEventListener("scroll", onScroll) 12 | onScroll() 13 | return () => window.removeEventListener("scroll", onScroll) 14 | }, [onScroll]) 15 | 16 | return scrolled 17 | } 18 | -------------------------------------------------------------------------------- /src/app/settings/audit/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Divider } from "@/components/Divider" 4 | import Approvers from "./_components/Approvers" 5 | import AuditRules from "./_components/AuditRules" 6 | import TransactionPolicy from "./_components/TransactionPolicy" 7 | 8 | export default function Audit() { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /src/app/siteConfig.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig = { 2 | name: "Insights", 3 | url: "https://insights.tremor.so", 4 | description: "The only reporting and audit dashboard you will ever need.", 5 | baseLinks: { 6 | reports: "/reports", 7 | transactions: "/transactions", 8 | settings: { 9 | audit: "/settings/audit", 10 | users: "/settings/users", 11 | billing: "/settings/billing", 12 | }, 13 | login: "/login", 14 | onboarding: "/onboarding/products", 15 | }, 16 | } 17 | 18 | export type siteConfig = typeof siteConfig 19 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/data/data.ts: -------------------------------------------------------------------------------- 1 | export const departments: { value: string; label: string }[] = [ 2 | { 3 | value: "all-areas", 4 | label: "All areas", 5 | }, 6 | { 7 | value: "IT", 8 | label: "IT", 9 | }, 10 | { 11 | value: "sales", 12 | label: "Sales", 13 | }, 14 | { 15 | value: "marketing", 16 | label: "Marketing", 17 | }, 18 | ] 19 | 20 | export const roles: { value: string; label: string }[] = [ 21 | { 22 | value: "admin", 23 | label: "Admin", 24 | }, 25 | { 26 | value: "member", 27 | label: "Member", 28 | }, 29 | { 30 | value: "viewer", 31 | label: "Viewer", 32 | }, 33 | { 34 | value: "contributor", 35 | label: "Contributor", 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "allowUnusedLabels": false, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./src/*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /src/app/(dashboard)/reports/_components/dateRanges.ts: -------------------------------------------------------------------------------- 1 | export const DATE_RANGES = { 2 | "7": { days: 7, label: "Last 7 Days" }, 3 | "30": { days: 30, label: "Last 30 Days" }, 4 | "60": { days: 60, label: "Last 60 Days" }, 5 | "90": { days: 90, label: "Last 90 Days" }, 6 | "180": { days: 180, label: "Last 180 Days" }, 7 | "365": { days: 365, label: "Last 365 Days" }, 8 | } as const 9 | 10 | export type RangeKey = keyof typeof DATE_RANGES 11 | 12 | export const DEFAULT_RANGE: RangeKey = "180" 13 | 14 | export const RANGE_DAYS: { [K in RangeKey]: number } = Object.fromEntries( 15 | Object.entries(DATE_RANGES).map(([key, { days }]) => [key, days]), 16 | ) as { [K in RangeKey]: number } 17 | 18 | export const RANGE_LABELS: { [K in RangeKey]: string } = Object.fromEntries( 19 | Object.entries(DATE_RANGES).map(([key, { label }]) => [key, label]), 20 | ) as { [K in RangeKey]: string } 21 | -------------------------------------------------------------------------------- /src/components/Label.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Label [v0.0.1] 2 | 3 | import * as LabelPrimitives from "@radix-ui/react-label" 4 | import React from "react" 5 | 6 | import { cx } from "@/lib/utils" 7 | 8 | interface LabelProps 9 | extends React.ComponentPropsWithoutRef { 10 | disabled?: boolean 11 | } 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | LabelProps 16 | >(({ className, disabled, ...props }, forwardedRef) => ( 17 | 34 | )) 35 | 36 | Label.displayName = "Label" 37 | 38 | export { Label } 39 | -------------------------------------------------------------------------------- /src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Card [v0.0.1] 2 | 3 | import { Slot } from "@radix-ui/react-slot" 4 | import React from "react" 5 | 6 | import { cx } from "@/lib/utils" 7 | 8 | interface CardProps extends React.ComponentPropsWithoutRef<"div"> { 9 | asChild?: boolean 10 | } 11 | 12 | const Card = React.forwardRef( 13 | ({ className, asChild, ...props }, forwardedRef) => { 14 | const Component = asChild ? Slot : "div" 15 | return ( 16 | 30 | ) 31 | }, 32 | ) 33 | 34 | Card.displayName = "Card" 35 | 36 | export { Card, type CardProps } 37 | -------------------------------------------------------------------------------- /src/app/(dashboard)/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React from "react" 3 | 4 | import { cx } from "@/lib/utils" 5 | 6 | import { Sidebar } from "@/components/ui/navigation/Sidebar" 7 | 8 | export default function Layout({ 9 | children, 10 | }: Readonly<{ 11 | children: React.ReactNode 12 | }>) { 13 | const [isCollapsed, setIsCollapsed] = React.useState(false) 14 | const toggleSidebar = () => { 15 | setIsCollapsed(!isCollapsed) 16 | } 17 | return ( 18 |
19 | 20 |
26 |
27 | {children} 28 |
29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 Tremor Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/app/(dashboard)/reports/page.tsx: -------------------------------------------------------------------------------- 1 | import Header from "./_components/Header" 2 | import { TransactionChart } from "./_components/TransactionChart" 3 | 4 | export default function Page() { 5 | return ( 6 | <> 7 |
8 |
9 |
10 | 15 | {/* optimized for mobile view */} 16 | 21 | 26 | {/* optimized for mobile view */} 27 | 32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/(dashboard)/transactions/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { getColumns } from "@/app/(dashboard)/transactions/_components/Columns" 3 | import { DataTable } from "@/app/(dashboard)/transactions/_components/DataTable" 4 | import { DataTableDrawer } from "@/app/(dashboard)/transactions/_components/DataTableDrawer" 5 | import { Transaction } from "@/data/schema" 6 | import { transactions } from "@/data/transactions" 7 | import { Row } from "@tanstack/react-table" 8 | import React from "react" 9 | 10 | export default function Example() { 11 | const [row, setRow] = React.useState | null>(null) 12 | const [isOpen, setIsOpen] = React.useState(false) 13 | const datas = row?.original 14 | 15 | const columns = getColumns({ 16 | onEditClick: (row) => { 17 | setRow(row) 18 | setIsOpen(true) 19 | }, 20 | }) 21 | 22 | return ( 23 | <> 24 |

25 | Details 26 |

27 |
28 | { 32 | setRow(row) 33 | setIsOpen(true) 34 | }} 35 | /> 36 | 37 |
38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/app/(dashboard)/reports/_components/FilterDate.tsx: -------------------------------------------------------------------------------- 1 | import { Label } from "@/components/Label" 2 | import { 3 | Select, 4 | SelectContent, 5 | SelectItem, 6 | SelectTrigger, 7 | SelectValue, 8 | } from "@/components/Select" 9 | import { useQueryState } from "nuqs" 10 | import { DEFAULT_RANGE, RANGE_DAYS, RANGE_LABELS, RangeKey } from "./dateRanges" 11 | 12 | const FilterDate = () => { 13 | const [range, setRange] = useQueryState("range", { 14 | defaultValue: DEFAULT_RANGE, 15 | parse: (value): RangeKey => 16 | Object.keys(RANGE_DAYS).includes(value) 17 | ? (value as RangeKey) 18 | : DEFAULT_RANGE, 19 | }) 20 | 21 | const handleValueChange = (value: RangeKey) => { 22 | setRange(value) 23 | } 24 | 25 | return ( 26 |
27 | 30 | 42 |
43 | ) 44 | } 45 | 46 | export { FilterDate } 47 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/Button" 2 | import { Logo } from "@/components/ui/Logo" 3 | import Link from "next/link" 4 | import { siteConfig } from "./siteConfig" 5 | 6 | export default function NotFound() { 7 | return ( 8 |
9 | 13 |
14 |
19 | 20 |
21 |

22 | Insights 23 |

24 |
25 | 31 |

32 | Page not found 33 |

34 |

35 | Sorry, we could not find the page you are looking for. 36 |

37 | 40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { GeistSans } from "geist/font/sans" 2 | import type { Metadata } from "next" 3 | import { ThemeProvider } from "next-themes" 4 | import { NuqsAdapter } from "nuqs/adapters/next/app" 5 | import React from "react" 6 | import "./globals.css" 7 | import { siteConfig } from "./siteConfig" 8 | 9 | export const metadata: Metadata = { 10 | metadataBase: new URL("https://yoururl.com"), 11 | title: siteConfig.name, 12 | description: siteConfig.description, 13 | keywords: [], 14 | authors: [ 15 | { 16 | name: "yourname", 17 | url: "", 18 | }, 19 | ], 20 | creator: "yourname", 21 | openGraph: { 22 | type: "website", 23 | locale: "en_US", 24 | url: siteConfig.url, 25 | title: siteConfig.name, 26 | description: siteConfig.description, 27 | siteName: siteConfig.name, 28 | }, 29 | icons: { 30 | icon: "/favicon.ico", 31 | }, 32 | } 33 | 34 | export default function RootLayout({ 35 | children, 36 | }: Readonly<{ 37 | children: React.ReactNode 38 | }>) { 39 | return ( 40 | 41 | 44 | 49 | 50 |
{children}
51 |
52 |
53 | 54 | 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/components/Divider.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Divider [v0.0.0] 2 | 3 | import React from "react" 4 | 5 | import { cx } from "@/lib/utils" 6 | 7 | interface DividerProps extends React.ComponentPropsWithoutRef<"div"> { } 8 | 9 | const Divider = React.forwardRef( 10 | ({ className, children, ...props }, forwardedRef) => ( 11 |
23 | {children ? ( 24 | <> 25 |
33 |
{children}
34 |
42 | 43 | ) : ( 44 |
52 | )} 53 |
54 | ), 55 | ) 56 | 57 | Divider.displayName = "Divider" 58 | 59 | export { Divider } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tremor – Insights 2 | 3 | `Insights` is a SaaS application template from [Tremor](https://tremor.so). It's built 4 | using [`Tremor Raw`](https://raw.tremor.so/docs/getting-started/installation) 5 | and [Next.js](https://nextjs.org). 6 | 7 | ## Getting started 8 | 9 | 1. Install the dependencies. We recommend using pnpm. If you want to use `npm`, 10 | just replace `pnpm` with `npm`. 11 | 12 | ```bash 13 | pnpm install 14 | ``` 15 | 16 | 2. Then, start the development server: 17 | 18 | ```bash 19 | pnpm run dev 20 | ``` 21 | 22 | 3. Visit [http://localhost:3000](http://localhost:3000) in your browser to view 23 | the template. 24 | 25 | ## Notes 26 | 27 | This project uses 28 | [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to 29 | automatically optimize and load Inter, a custom Google Font. 30 | 31 | This project uses 32 | [`Tremor Raw`](https://raw.tremor.so/docs/getting-started/installation) 33 | components for the UI. 34 | 35 | ## License 36 | 37 | This site template is a commercial product and is licensed under the 38 | [Tremor License](https://blocks.tremor.so/license). 39 | 40 | ## Learn more 41 | 42 | For a deeper understanding of the technologies used in this template, check out 43 | the resources listed below: 44 | 45 | - [Tremor Raw](https://raw.tremor.so) - Tremor Raw documentation 46 | - [Tailwind CSS](https://tailwindcss.com) - A utility-first CSS framework 47 | - [Next.js](https://nextjs.org/docs) - Next.js documentation 48 | - [Radix UI](https://www.radix-ui.com) - Radix UI Website 49 | - [Recharts](https://recharts.org) - Recharts documentation and website 50 | - [Tanstack](https://tanstack.com/table/latest) - TanStack table documentation 51 | -------------------------------------------------------------------------------- /src/app/(dashboard)/transactions/_components/DataTableColumnHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Column } from "@tanstack/react-table" 2 | import { ChevronDown, ChevronUp } from "lucide-react" 3 | 4 | import { cx } from "@/lib/utils" 5 | 6 | interface DataTableColumnHeaderProps 7 | extends React.HTMLAttributes { 8 | column: Column 9 | title: string 10 | } 11 | 12 | export function DataTableColumnHeader({ 13 | column, 14 | title, 15 | className, 16 | }: DataTableColumnHeaderProps) { 17 | if (!column.getCanSort()) { 18 | return
{title}
19 | } 20 | 21 | return ( 22 |
30 | {title} 31 | {column.getCanSort() ? ( 32 |
33 |
48 | ) : null} 49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/app/(dashboard)/transactions/_components/TableBulkEditor.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | CommandBar, 5 | CommandBarBar, 6 | CommandBarCommand, 7 | CommandBarSeperator, 8 | CommandBarValue, 9 | } from "@/components/CommandBar" 10 | import { RowSelectionState, Table } from "@tanstack/react-table" 11 | 12 | type DataTableBulkEditorProps = { 13 | table: Table 14 | rowSelection: RowSelectionState 15 | } 16 | 17 | function DataTableBulkEditor({ 18 | table, 19 | rowSelection, 20 | }: DataTableBulkEditorProps) { 21 | const hasSelectedRows = Object.keys(rowSelection).length > 0 22 | return ( 23 | 24 | 25 | 26 | {Object.keys(rowSelection).length} selected 27 | 28 | 29 | { 32 | console.log("Edit") 33 | }} 34 | shortcut={{ shortcut: "e" }} 35 | /> 36 | 37 | { 40 | console.log("Delete") 41 | }} 42 | shortcut={{ shortcut: "d" }} 43 | /> 44 | 45 | { 48 | table.resetRowSelection() 49 | }} 50 | shortcut={{ shortcut: "Escape", label: "esc" }} 51 | // don't disable this command 52 | /> 53 | 54 | 55 | ) 56 | } 57 | 58 | export { DataTableBulkEditor } 59 | -------------------------------------------------------------------------------- /src/components/Textarea.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Textarea [v0.0.1] 2 | 3 | import React from "react" 4 | 5 | import { cx, focusInput, hasErrorInput } from "@/lib/utils" 6 | 7 | interface TextareaProps 8 | extends React.TextareaHTMLAttributes { 9 | hasError?: boolean 10 | } 11 | 12 | const Textarea = React.forwardRef( 13 | ({ className, hasError, ...props }: TextareaProps, forwardedRef) => { 14 | return ( 15 |