├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public └── DatabaseLogo.tsx ├── src ├── app │ ├── (main) │ │ ├── details │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── overview │ │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── not-found.tsx │ ├── settings │ │ ├── billing │ │ │ └── page.tsx │ │ ├── general │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── users │ │ │ └── page.tsx │ └── siteConfig.ts ├── components │ ├── Badge.tsx │ ├── Button.tsx │ ├── Calendar.tsx │ ├── Card.tsx │ ├── Checkbox.tsx │ ├── CommandBar.tsx │ ├── DatePicker.tsx │ ├── Dialog.tsx │ ├── Divider.tsx │ ├── Drawer.tsx │ ├── Dropdown.tsx │ ├── Input.tsx │ ├── Label.tsx │ ├── LineChart.tsx │ ├── Popover.tsx │ ├── ProgressBar.tsx │ ├── ProgressCircle.tsx │ ├── RadioCard.tsx │ ├── Searchbar.tsx │ ├── Select.tsx │ ├── Switch.tsx │ ├── TabNavigation.tsx │ ├── Table.tsx │ ├── Tooltip.tsx │ └── ui │ │ ├── data-table │ │ ├── DataTable.tsx │ │ ├── DataTableBulkEditor.tsx │ │ ├── DataTableColumnHeader.tsx │ │ ├── DataTableFilter.tsx │ │ ├── DataTableFilterbar.tsx │ │ ├── DataTablePagination.tsx │ │ ├── DataTableRowActions.tsx │ │ ├── DataTableViewOptions.tsx │ │ ├── TanstackTable.d.ts │ │ └── columns.tsx │ │ ├── icons │ │ └── ArrowAnimated.tsx │ │ ├── navigation │ │ ├── DropdownUserProfile.tsx │ │ ├── MobileSidebar.tsx │ │ ├── ModalAddWorkspace.tsx │ │ ├── Sidebar.tsx │ │ ├── SidebarWorkspacesDropdown.tsx │ │ └── UserProfile.tsx │ │ ├── overview │ │ ├── DashboardCategoryBarCard.tsx │ │ ├── DashboardChartCard.tsx │ │ ├── DashboardFilterbar.tsx │ │ └── DashboardProgressBarCard.tsx │ │ └── settings │ │ └── ModalAddUser.tsx ├── data │ ├── data.ts │ ├── generateData.js │ ├── overview-data.ts │ └── schema.ts └── lib │ ├── chartUtils.ts │ ├── useOnWindowResize.tsx │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | } 11 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tremor – Dashboard 2 | 3 | `Dashboard` 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 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | redirects: async () => { 5 | return [ 6 | { 7 | source: "/", 8 | destination: "/overview", 9 | permanent: true, 10 | }, 11 | ]; 12 | }, 13 | }; 14 | 15 | export default nextConfig; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "template-dashboard-3", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "generate": "node src/data/generateData.js" 11 | }, 12 | "dependencies": { 13 | "@atlaskit/pragmatic-drag-and-drop": "^1.5.2", 14 | "@atlaskit/pragmatic-drag-and-drop-flourish": "^1.2.2", 15 | "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3", 16 | "@atlaskit/pragmatic-drag-and-drop-live-region": "^1.3.0", 17 | "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^1.2.0", 18 | "@internationalized/date": "^3.7.0", 19 | "@radix-ui/react-checkbox": "^1.1.4", 20 | "@radix-ui/react-dialog": "^1.1.6", 21 | "@radix-ui/react-dropdown-menu": "^2.1.6", 22 | "@radix-ui/react-label": "^2.1.2", 23 | "@radix-ui/react-navigation-menu": "^1.2.5", 24 | "@radix-ui/react-popover": "^1.1.6", 25 | "@radix-ui/react-radio-group": "^1.2.3", 26 | "@radix-ui/react-select": "^2.1.6", 27 | "@radix-ui/react-slot": "^1.1.2", 28 | "@radix-ui/react-switch": "^1.1.3", 29 | "@radix-ui/react-tooltip": "^1.1.8", 30 | "@react-aria/datepicker": "^3.14.1", 31 | "@react-stately/datepicker": "^3.13.0", 32 | "@remixicon/react": "^4.6.0", 33 | "@tanstack/react-table": "^8.21.2", 34 | "clsx": "^2.1.1", 35 | "date-fns": "^3.6.0", 36 | "next": "14.2.23", 37 | "next-themes": "^0.4.6", 38 | "react": "18.2.0", 39 | "react-day-picker": "^8.10.1", 40 | "react-dom": "18.2.0", 41 | "recharts": "^2.15.1", 42 | "tailwind-merge": "^2.6.0", 43 | "tailwind-variants": "^0.3.1", 44 | "tiny-invariant": "^1.3.3", 45 | "use-debounce": "^10.0.4" 46 | }, 47 | "devDependencies": { 48 | "@tailwindcss/forms": "^0.5.10", 49 | "@types/node": "^22.13.14", 50 | "@types/react": "18.2.0", 51 | "@types/react-dom": "18.2.0", 52 | "@typescript-eslint/parser": "^8.28.0", 53 | "eslint": "^8.57.1", 54 | "eslint-config-next": "14.2.23", 55 | "postcss": "^8.5.3", 56 | "prettier": "^3.5.3", 57 | "prettier-plugin-tailwindcss": "^0.6.11", 58 | "tailwindcss": "^3.4.17", 59 | "typescript": "^5.8.2" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/DatabaseLogo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import type { SVGProps } from "react" 3 | export const DatabaseLogo = (props: SVGProps) => ( 4 | 77 | ) 78 | -------------------------------------------------------------------------------- /src/app/(main)/details/page.tsx: -------------------------------------------------------------------------------- 1 | import { columns } from "@/components/ui/data-table/columns" 2 | import { DataTable } from "@/components/ui/data-table/DataTable" 3 | import { usage } from "@/data/data" 4 | 5 | export default function Example() { 6 | return ( 7 | <> 8 |

9 | Details 10 |

11 |
12 | 13 |
14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function Layout({ 2 | children, 3 | }: Readonly<{ 4 | children: React.ReactNode 5 | }>) { 6 | return ( 7 |
8 |
9 | {children} 10 |
11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/(main)/overview/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { CategoryBarCard } from "@/components/ui/overview/DashboardCategoryBarCard" 3 | import { ChartCard } from "@/components/ui/overview/DashboardChartCard" 4 | import { Filterbar } from "@/components/ui/overview/DashboardFilterbar" 5 | import { ProgressBarCard } from "@/components/ui/overview/DashboardProgressBarCard" 6 | import { overviews } from "@/data/overview-data" 7 | import { OverviewData } from "@/data/schema" 8 | import { cx } from "@/lib/utils" 9 | import { subDays, toDate } from "date-fns" 10 | import React from "react" 11 | import { DateRange } from "react-day-picker" 12 | 13 | export type PeriodValue = "previous-period" | "last-year" | "no-comparison" 14 | 15 | const categories: { 16 | title: keyof OverviewData 17 | type: "currency" | "unit" 18 | }[] = [ 19 | { 20 | title: "Rows read", 21 | type: "unit", 22 | }, 23 | { 24 | title: "Rows written", 25 | type: "unit", 26 | }, 27 | { 28 | title: "Queries", 29 | type: "unit", 30 | }, 31 | { 32 | title: "Payments completed", 33 | type: "currency", 34 | }, 35 | { 36 | title: "Sign ups", 37 | type: "unit", 38 | }, 39 | { 40 | title: "Logins", 41 | type: "unit", 42 | }, 43 | { 44 | title: "Sign outs", 45 | type: "unit", 46 | }, 47 | { 48 | title: "Support calls", 49 | type: "unit", 50 | }, 51 | ] 52 | 53 | export type KpiEntry = { 54 | title: string 55 | percentage: number 56 | current: number 57 | allowed: number 58 | unit?: string 59 | } 60 | 61 | const data: KpiEntry[] = [ 62 | { 63 | title: "Rows read", 64 | percentage: 48.1, 65 | current: 48.1, 66 | allowed: 100, 67 | unit: "M", 68 | }, 69 | { 70 | title: "Rows written", 71 | percentage: 78.3, 72 | current: 78.3, 73 | allowed: 100, 74 | unit: "M", 75 | }, 76 | { 77 | title: "Storage", 78 | percentage: 26, 79 | current: 5.2, 80 | allowed: 20, 81 | unit: "GB", 82 | }, 83 | ] 84 | 85 | const data2: KpiEntry[] = [ 86 | { 87 | title: "Weekly active users", 88 | percentage: 21.7, 89 | current: 21.7, 90 | allowed: 100, 91 | unit: "%", 92 | }, 93 | { 94 | title: "Total users", 95 | percentage: 70, 96 | current: 28, 97 | allowed: 40, 98 | }, 99 | { 100 | title: "Uptime", 101 | percentage: 98.3, 102 | current: 98.3, 103 | allowed: 100, 104 | unit: "%", 105 | }, 106 | ] 107 | 108 | export type KpiEntryExtended = Omit< 109 | KpiEntry, 110 | "current" | "allowed" | "unit" 111 | > & { 112 | value: string 113 | color: string 114 | } 115 | 116 | const data3: KpiEntryExtended[] = [ 117 | { 118 | title: "Base tier", 119 | percentage: 68.1, 120 | value: "$200", 121 | color: "bg-indigo-600 dark:bg-indigo-500", 122 | }, 123 | { 124 | title: "On-demand charges", 125 | percentage: 20.8, 126 | value: "$61.1", 127 | color: "bg-purple-600 dark:bg-purple-500", 128 | }, 129 | { 130 | title: "Caching", 131 | percentage: 11.1, 132 | value: "$31.9", 133 | color: "bg-gray-400 dark:bg-gray-600", 134 | }, 135 | ] 136 | 137 | const overviewsDates = overviews.map((item) => toDate(item.date).getTime()) 138 | const maxDate = toDate(Math.max(...overviewsDates)) 139 | 140 | export default function Overview() { 141 | const [selectedDates, setSelectedDates] = React.useState< 142 | DateRange | undefined 143 | >({ 144 | from: subDays(maxDate, 30), 145 | to: maxDate, 146 | }) 147 | const [selectedPeriod, setSelectedPeriod] = 148 | React.useState("last-year") 149 | 150 | const [selectedCategories, setSelectedCategories] = React.useState( 151 | categories.map((category) => category.title), 152 | ) 153 | 154 | return ( 155 | <> 156 |
157 |

161 | Current billing cycle 162 |

163 |
164 | 174 | 184 | 195 |
196 |
197 |
198 |

202 | Overview 203 |

204 |
205 | setSelectedDates(dates)} 210 | selectedPeriod={selectedPeriod} 211 | onPeriodChange={(period) => setSelectedPeriod(period)} 212 | categories={categories} 213 | setSelectedCategories={setSelectedCategories} 214 | selectedCategories={selectedCategories} 215 | /> 216 |
217 |
222 | {categories 223 | .filter((category) => selectedCategories.includes(category.title)) 224 | .map((category) => { 225 | return ( 226 | 233 | ) 234 | })} 235 |
236 |
237 | 238 | ) 239 | } 240 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tremorlabs/template-dashboard/07ab00c4ae8d8a1904a4fdecf6a8928a454866a3/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | import { ThemeProvider } from "next-themes" 3 | import { Inter } from "next/font/google" 4 | import "./globals.css" 5 | import { siteConfig } from "./siteConfig" 6 | 7 | import { Sidebar } from "@/components/ui/navigation/Sidebar" 8 | 9 | const inter = Inter({ 10 | subsets: ["latin"], 11 | display: "swap", 12 | variable: "--font-inter", 13 | }) 14 | 15 | export const metadata: Metadata = { 16 | metadataBase: new URL("https://yoururl.com"), 17 | title: siteConfig.name, 18 | description: siteConfig.description, 19 | keywords: [], 20 | authors: [ 21 | { 22 | name: "yourname", 23 | url: "", 24 | }, 25 | ], 26 | creator: "yourname", 27 | openGraph: { 28 | type: "website", 29 | locale: "en_US", 30 | url: siteConfig.url, 31 | title: siteConfig.name, 32 | description: siteConfig.description, 33 | siteName: siteConfig.name, 34 | }, 35 | icons: { 36 | icon: "/favicon.ico", 37 | }, 38 | } 39 | 40 | export default function RootLayout({ 41 | children, 42 | }: Readonly<{ 43 | children: React.ReactNode 44 | }>) { 45 | return ( 46 | 47 | 51 |
52 | 53 | 54 |
{children}
55 |
56 |
57 | 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/Button" 2 | import { ArrowAnimated } from "@/components/ui/icons/ArrowAnimated" 3 | import Link from "next/link" 4 | import { DatabaseLogo } from "../../public/DatabaseLogo" 5 | import { siteConfig } from "./siteConfig" 6 | 7 | export default function NotFound() { 8 | return ( 9 |
10 | 11 | 12 | 13 |

14 | 404 15 |

16 |

17 | Page not found 18 |

19 |

20 | Sorry, we couldn’t find the page you’re looking for. 21 |

22 | 31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/app/settings/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { siteConfig } from "@/app/siteConfig" 4 | import { TabNavigation, TabNavigationLink } from "@/components/TabNavigation" 5 | import Link from "next/link" 6 | import { usePathname } from "next/navigation" 7 | 8 | const navigationSettings = [ 9 | { name: "General", href: siteConfig.baseLinks.settings.general }, 10 | { name: "Billing & Usage", href: siteConfig.baseLinks.settings.billing }, 11 | { name: "Users", href: siteConfig.baseLinks.settings.users }, 12 | ] 13 | 14 | export default function Layout({ 15 | children, 16 | }: Readonly<{ 17 | children: React.ReactNode 18 | }>) { 19 | const pathname = usePathname() 20 | return ( 21 |
22 |

23 | Settings 24 |

25 | 26 | {navigationSettings.map((item) => ( 27 | 32 | {item.name} 33 | 34 | ))} 35 | 36 |
{children}
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/app/settings/users/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Button } from "@/components/Button" 4 | import { 5 | DropdownMenu, 6 | DropdownMenuContent, 7 | DropdownMenuItem, 8 | DropdownMenuTrigger, 9 | } from "@/components/Dropdown" 10 | import { 11 | Select, 12 | SelectContent, 13 | SelectItem, 14 | SelectTrigger, 15 | SelectValue, 16 | } from "@/components/Select" 17 | import { Tooltip } from "@/components/Tooltip" 18 | import { ModalAddUser } from "@/components/ui/settings/ModalAddUser" 19 | import { invitedUsers, roles, users } from "@/data/data" 20 | import { RiAddLine, RiMore2Fill } from "@remixicon/react" 21 | 22 | export default function Users() { 23 | return ( 24 | <> 25 |
26 |
27 |
28 |

32 | Users 33 |

34 |

35 | Workspace administrators can add, manage, and remove users. 36 |

37 |
38 | 39 | 43 | 44 |
45 |
    49 | {users.map((user) => ( 50 |
  • 54 |
    55 | 61 |
    62 |

    63 | {user.name} 64 |

    65 |

    {user.email}

    66 |
    67 |
    68 |
    69 | {user.role === "admin" ? ( 70 | 76 |
    77 | 96 |
    97 |
    98 | ) : ( 99 | 118 | )} 119 | 120 | 121 | 130 | 131 | 132 | 133 | View details 134 | 135 | 139 | Delete 140 | 141 | 142 | 143 |
    144 |
  • 145 | ))} 146 |
147 |
148 |
149 |

153 | Pending invitations 154 |

155 |
    159 | {invitedUsers.map((user) => ( 160 |
  • 164 |
    165 | 171 |
    172 |

    173 | {user.email} 174 |

    175 |

    176 | Expires in {user.expires} days 177 |

    178 |
    179 |
    180 |
    181 | 197 | 198 | 199 | 208 | 209 | 210 | 214 | Revoke invitation 215 | 216 | 217 | 218 |
    219 |
  • 220 | ))} 221 |
222 |
223 | 224 | ) 225 | } 226 | -------------------------------------------------------------------------------- /src/app/siteConfig.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig = { 2 | name: "Dashboard", 3 | url: "https://dashboard.tremor.so", 4 | description: "The only dashboard you will ever need.", 5 | baseLinks: { 6 | home: "/", 7 | overview: "/overview", 8 | details: "/details", 9 | settings: { 10 | general: "/settings/general", 11 | billing: "/settings/billing", 12 | users: "/settings/users", 13 | }, 14 | }, 15 | } 16 | 17 | export type siteConfig = typeof siteConfig 18 | -------------------------------------------------------------------------------- /src/components/Badge.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Badge [v0.0.0] 2 | 3 | import React from "react" 4 | import { tv, type VariantProps } from "tailwind-variants" 5 | 6 | import { cx } from "@/lib/utils" 7 | 8 | const badgeVariants = tv({ 9 | base: cx( 10 | "inline-flex items-center gap-x-1 whitespace-nowrap rounded px-1.5 py-0.5 text-xs font-semibold ring-1", 11 | ), 12 | variants: { 13 | variant: { 14 | default: [ 15 | "bg-indigo-50 text-indigo-800 ring-indigo-500/30", 16 | "dark:bg-indigo-400/10 dark:text-indigo-400 dark:ring-indigo-400/30", 17 | ], 18 | neutral: [ 19 | "bg-gray-50 text-gray-700 ring-gray-500/30", 20 | "dark:bg-gray-400/10 dark:text-gray-300 dark:ring-gray-400/20", 21 | ], 22 | success: [ 23 | "bg-emerald-50 text-emerald-800 ring-emerald-600/30", 24 | "dark:bg-emerald-400/10 dark:text-emerald-400 dark:ring-emerald-400/20", 25 | ], 26 | error: [ 27 | "bg-red-50 text-red-800 ring-red-600/20", 28 | "dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20", 29 | ], 30 | warning: [ 31 | "bg-yellow-50 text-yellow-800 ring-yellow-600/30", 32 | "dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20", 33 | ], 34 | }, 35 | }, 36 | defaultVariants: { 37 | variant: "default", 38 | }, 39 | }) 40 | 41 | interface BadgeProps 42 | extends React.ComponentPropsWithoutRef<"span">, 43 | VariantProps {} 44 | 45 | const Badge = React.forwardRef( 46 | ({ className, variant, ...props }: BadgeProps, forwardedRef) => { 47 | return ( 48 | 53 | ) 54 | }, 55 | ) 56 | 57 | Badge.displayName = "Badge" 58 | 59 | export { Badge, badgeVariants, type BadgeProps } 60 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Button [v0.1.1] 2 | 3 | import { Slot } from "@radix-ui/react-slot" 4 | import { RiLoader2Fill } from "@remixicon/react" 5 | import React from "react" 6 | import { tv, type VariantProps } from "tailwind-variants" 7 | 8 | import { cx, focusRing } from "@/lib/utils" 9 | 10 | const buttonVariants = tv({ 11 | base: [ 12 | // base 13 | "relative inline-flex items-center justify-center whitespace-nowrap rounded-md border px-3 py-2 text-center text-sm font-medium shadow-sm transition-all duration-100 ease-in-out", 14 | // disabled 15 | "disabled:pointer-events-none disabled:shadow-none", 16 | // focus 17 | focusRing, 18 | ], 19 | variants: { 20 | variant: { 21 | primary: [ 22 | // border 23 | "border-transparent", 24 | // text color 25 | "text-white dark:text-gray-900", 26 | // background color 27 | "bg-indigo-600 dark:bg-indigo-500", 28 | // hover color 29 | "hover:bg-indigo-500 dark:hover:bg-indigo-600", 30 | // disabled 31 | "disabled:bg-indigo-100 disabled:text-gray-400", 32 | "disabled:dark:bg-indigo-800 disabled:dark:text-indigo-400", 33 | ], 34 | secondary: [ 35 | // border 36 | "border-gray-300 dark:border-gray-800", 37 | // text color 38 | "text-gray-900 dark:text-gray-50", 39 | // background color 40 | "bg-white dark:bg-gray-950", 41 | //hover color 42 | "hover:bg-gray-50 dark:hover:bg-gray-900/60", 43 | // disabled 44 | "disabled:text-gray-400", 45 | "disabled:dark:text-gray-600", 46 | ], 47 | light: [ 48 | // base 49 | "shadow-none", 50 | // border 51 | "border-transparent", 52 | // text color 53 | "text-gray-900 dark:text-gray-50", 54 | // background color 55 | "bg-gray-200 dark:bg-gray-900", 56 | // hover color 57 | "hover:bg-gray-300/70 dark:hover:bg-gray-800/80", 58 | // disabled 59 | "disabled:bg-gray-100 disabled:text-gray-400", 60 | "disabled:dark:bg-gray-800 disabled:dark:text-gray-600", 61 | ], 62 | ghost: [ 63 | // base 64 | "shadow-none", 65 | // border 66 | "border-transparent", 67 | // text color 68 | "text-gray-900 dark:text-gray-50", 69 | // hover color 70 | "bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800/80", 71 | // disabled 72 | "disabled:text-gray-400", 73 | "disabled:dark:text-gray-600", 74 | ], 75 | destructive: [ 76 | // text color 77 | "text-white", 78 | // border 79 | "border-transparent", 80 | // background color 81 | "bg-red-600 dark:bg-red-700", 82 | // hover color 83 | "hover:bg-red-700 dark:hover:bg-red-600", 84 | // disabled 85 | "disabled:bg-red-300 disabled:text-white", 86 | "disabled:dark:bg-red-950 disabled:dark:text-red-400", 87 | ], 88 | }, 89 | }, 90 | defaultVariants: { 91 | variant: "primary", 92 | }, 93 | }) 94 | 95 | interface ButtonProps 96 | extends React.ComponentPropsWithoutRef<"button">, 97 | VariantProps { 98 | asChild?: boolean 99 | isLoading?: boolean 100 | loadingText?: string 101 | } 102 | 103 | const Button = React.forwardRef( 104 | ( 105 | { 106 | asChild, 107 | isLoading = false, 108 | loadingText, 109 | className, 110 | disabled, 111 | variant, 112 | children, 113 | ...props 114 | }: ButtonProps, 115 | forwardedRef, 116 | ) => { 117 | const Component = asChild ? Slot : "button" 118 | return ( 119 | 125 | {isLoading ? ( 126 | 127 | 136 | ) : ( 137 | children 138 | )} 139 | 140 | ) 141 | }, 142 | ) 143 | 144 | Button.displayName = "Button" 145 | 146 | export { Button, buttonVariants, type ButtonProps } 147 | -------------------------------------------------------------------------------- /src/components/Calendar.tsx: -------------------------------------------------------------------------------- 1 | // Tremor Raw Calendar [v0.0.4] 2 | 3 | "use client" 4 | 5 | import { 6 | RiArrowLeftDoubleLine, 7 | RiArrowLeftSLine, 8 | RiArrowRightDoubleLine, 9 | RiArrowRightSLine, 10 | } from "@remixicon/react" 11 | import { addYears, format, isSameMonth } from "date-fns" 12 | import * as React from "react" 13 | import { 14 | DayPicker, 15 | useDayPicker, 16 | useDayRender, 17 | useNavigation, 18 | type DayPickerRangeProps, 19 | type DayPickerSingleProps, 20 | type DayProps, 21 | type Matcher, 22 | } from "react-day-picker" 23 | 24 | import { cx, focusRing } from "@/lib/utils" 25 | 26 | interface NavigationButtonProps 27 | extends React.HTMLAttributes { 28 | onClick: () => void 29 | icon: React.ElementType 30 | disabled?: boolean 31 | } 32 | 33 | const NavigationButton = React.forwardRef< 34 | HTMLButtonElement, 35 | NavigationButtonProps 36 | >( 37 | ( 38 | { onClick, icon, disabled, ...props }: NavigationButtonProps, 39 | forwardedRef, 40 | ) => { 41 | const Icon = icon 42 | return ( 43 | 68 | ) 69 | }, 70 | ) 71 | 72 | NavigationButton.displayName = "NavigationButton" 73 | 74 | type OmitKeys = { 75 | [P in keyof T as P extends K ? never : P]: T[P] 76 | } 77 | 78 | type KeysToOmit = "showWeekNumber" | "captionLayout" | "mode" 79 | 80 | type SingleProps = OmitKeys 81 | type RangeProps = OmitKeys 82 | 83 | type CalendarProps = 84 | | ({ 85 | mode: "single" 86 | } & SingleProps) 87 | | ({ 88 | mode?: undefined 89 | } & SingleProps) 90 | | ({ 91 | mode: "range" 92 | } & RangeProps) 93 | 94 | const Calendar = ({ 95 | mode = "single", 96 | weekStartsOn = 1, 97 | numberOfMonths = 1, 98 | enableYearNavigation = false, 99 | disableNavigation, 100 | locale, 101 | className, 102 | classNames, 103 | ...props 104 | }: CalendarProps & { enableYearNavigation?: boolean }) => { 105 | return ( 106 | ( 151 |