├── .env.example
├── .eslintrc.cjs
├── .gitignore
├── LICENSE
├── README.md
├── components.json
├── demo
├── dark.png
├── example-01-dark.png
├── example-01-light.png
└── light.png
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── prettier.config.cjs
├── prisma
└── schema.prisma
├── public
└── favicon.ico
├── src
├── components
│ ├── common
│ │ ├── alert-modal.tsx
│ │ ├── heading.tsx
│ │ ├── loading.tsx
│ │ └── modal.tsx
│ ├── constants
│ │ └── side-nav.tsx
│ ├── dashboard
│ │ ├── date-range-picker.tsx
│ │ ├── overview.tsx
│ │ └── recent-sales.tsx
│ ├── layout
│ │ ├── header.tsx
│ │ ├── index.tsx
│ │ ├── mobile-sidebar.tsx
│ │ ├── side-nav.tsx
│ │ ├── sidebar.tsx
│ │ ├── subnav-accordion.tsx
│ │ ├── theme-toggle.tsx
│ │ ├── user-avatar.tsx
│ │ └── user-nav.tsx
│ ├── page-component
│ │ └── example
│ │ │ ├── employee
│ │ │ ├── cell-action.tsx
│ │ │ ├── client.tsx
│ │ │ ├── columns.tsx
│ │ │ └── employee-form.tsx
│ │ │ ├── example-02
│ │ │ └── multi-select-input.tsx
│ │ │ └── example-03
│ │ │ ├── email-form.tsx
│ │ │ ├── form-context.tsx
│ │ │ ├── form-step.tsx
│ │ │ ├── password-form.tsx
│ │ │ └── username-form.tsx
│ └── ui
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── data-table
│ │ ├── data-table-column-header.tsx
│ │ ├── data-table-pagination.tsx
│ │ ├── data-table-toolbar.tsx
│ │ ├── data-table-view-options.tsx
│ │ └── data-table.tsx
│ │ ├── date-picker.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── hover-card.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle-group.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
├── env.mjs
├── hooks
│ └── useSidebar.tsx
├── lib
│ ├── utils.ts
│ └── validators.tsx
├── pages
│ ├── _app.tsx
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth].ts
│ │ └── trpc
│ │ │ └── [trpc].ts
│ ├── example
│ │ ├── employees
│ │ │ ├── [id]
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── example-02
│ │ │ └── index.tsx
│ │ └── example-03
│ │ │ └── index.tsx
│ └── index.tsx
├── providers
│ └── modal-provider.tsx
├── server
│ ├── api
│ │ ├── root.ts
│ │ ├── routers
│ │ │ └── employee.ts
│ │ └── trpc.ts
│ ├── auth.ts
│ └── db.ts
├── styles
│ └── globals.css
├── types
│ └── index.tsx
└── utils
│ └── api.ts
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | # Since the ".env" file is gitignored, you can use the ".env.example" file to
2 | # build a new ".env" file when you clone the repo. Keep this file up-to-date
3 | # when you add new variables to `.env`.
4 |
5 | # This file will be committed to version control, so make sure not to have any
6 | # secrets in it. If you are cloning this repo, create a copy of this file named
7 | # ".env" and populate it with your secrets.
8 |
9 | # When adding additional environment variables, the schema in "/src/env.mjs"
10 | # should be updated accordingly.
11 |
12 | # Prisma
13 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env
14 | DATABASE_URL="file:./db.sqlite"
15 |
16 | # Next Auth
17 | # You can generate a new secret on the command line with:
18 | # openssl rand -base64 32
19 | # https://next-auth.js.org/configuration/options#secret
20 | # NEXTAUTH_SECRET=""
21 | NEXTAUTH_URL="http://localhost:3000"
22 |
23 | # Next Auth Discord Provider
24 | DISCORD_CLIENT_ID=""
25 | DISCORD_CLIENT_SECRET=""
26 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const path = require("path");
3 |
4 | /** @type {import("eslint").Linter.Config} */
5 | const config = {
6 | overrides: [
7 | {
8 | extends: [
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | ],
11 | files: ["*.ts", "*.tsx"],
12 | parserOptions: {
13 | project: path.join(__dirname, "tsconfig.json"),
14 | },
15 | },
16 | ],
17 | parser: "@typescript-eslint/parser",
18 | parserOptions: {
19 | project: path.join(__dirname, "tsconfig.json"),
20 | },
21 | plugins: ["@typescript-eslint"],
22 | extends: ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
23 | rules: {
24 | "@typescript-eslint/consistent-type-imports": [
25 | "warn",
26 | {
27 | prefer: "type-imports",
28 | fixStyle: "inline-type-imports",
29 | },
30 | ],
31 | "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
32 | },
33 | };
34 |
35 | module.exports = config;
36 |
--------------------------------------------------------------------------------
/.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 |
8 | # testing
9 | /coverage
10 |
11 | # database
12 | /prisma/db.sqlite
13 | /prisma/db.sqlite-journal
14 |
15 | # next.js
16 | /.next/
17 | /out/
18 | next-env.d.ts
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # local env files
34 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
35 | .env
36 | .env*.local
37 |
38 | # vercel
39 | .vercel
40 |
41 | # typescript
42 | *.tsbuildinfo
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 t3-app-template
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [T3 App Template]: T3 Stack with shadcn-ui Admin Layout
2 |
3 | ## Overview
4 |
5 | This project is a management interface developed using the [T3 Stack](https://create.t3.gg/), encapsulated with [shadcn-ui](https://ui.shadcn.com/)
6 | . It aims to provide a responsive, user-friendly, and feature-rich admin interface suitable for various devices and screen sizes.
7 |
8 | ## Features
9 |
10 | - Theme Switching: Allows users to switch the interface theme according to their preference or requirement.
11 | - Responsive Sidebar: A flexible sidebar that adapts to different screen sizes and devices.
12 | - Sidebar Shrinking: Users can expand or collapse the sidebar as needed to optimize screen space.
13 | - Secondary Menu in Sidebar: For better organization and access, the sidebar includes multi-level menus.
14 | - Mobile Sidebar: Specially designed for mobile devices to ensure smooth usage on smaller screens.
15 | - Tanstack Table: Use tanstack table to implement curd operation
16 | - NextAuth: Implemented Github login using NextAuth
17 | - Global: Header, Loading and Modal
18 | - Select: Implement multi-select input example
19 | - Step Form: Implement step-form example
20 | - Other Features...: Waiting for further updates
21 |
22 | ## Quick Start
23 |
24 | 1. Clone the repository: git clone https://github.com/gaofubin/t3-app-template.git
25 | 2. Install dependencies: npm install
26 | 3. Start the project: npm run dev
27 |
28 | ## Dashboard Demo
29 |
30 | > Demo Link: https://t3-app-template.vercel.app/
31 |
32 | 
33 |
34 | 
35 |
36 | 
37 |
38 | 
39 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/styles/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/demo/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaofubin/t3-app-template/db31bd3c03b7eb0a099be07c261d856f3b6d4110/demo/dark.png
--------------------------------------------------------------------------------
/demo/example-01-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaofubin/t3-app-template/db31bd3c03b7eb0a099be07c261d856f3b6d4110/demo/example-01-dark.png
--------------------------------------------------------------------------------
/demo/example-01-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaofubin/t3-app-template/db31bd3c03b7eb0a099be07c261d856f3b6d4110/demo/example-01-light.png
--------------------------------------------------------------------------------
/demo/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaofubin/t3-app-template/db31bd3c03b7eb0a099be07c261d856f3b6d4110/demo/light.png
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
3 | * for Docker builds.
4 | */
5 | await import("./src/env.mjs");
6 |
7 | /** @type {import("next").NextConfig} */
8 | const config = {
9 | reactStrictMode: true,
10 |
11 | /**
12 | * If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
13 | * out.
14 | *
15 | * @see https://github.com/vercel/next.js/issues/41980
16 | */
17 | i18n: {
18 | locales: ["en"],
19 | defaultLocale: "en",
20 | },
21 | };
22 | export default config;
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "t3-app-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "next build",
7 | "dev": "next dev",
8 | "postinstall": "prisma generate",
9 | "lint": "next lint",
10 | "start": "next start"
11 | },
12 | "dependencies": {
13 | "@clerk/nextjs": "^4.29.3",
14 | "@hookform/resolvers": "^3.3.2",
15 | "@next-auth/prisma-adapter": "^1.0.5",
16 | "@prisma/client": "^4.11.0",
17 | "@radix-ui/react-accordion": "^1.1.2",
18 | "@radix-ui/react-alert-dialog": "^1.0.5",
19 | "@radix-ui/react-aspect-ratio": "^1.0.3",
20 | "@radix-ui/react-avatar": "^1.0.4",
21 | "@radix-ui/react-checkbox": "^1.0.4",
22 | "@radix-ui/react-collapsible": "^1.0.3",
23 | "@radix-ui/react-context-menu": "^2.1.5",
24 | "@radix-ui/react-dialog": "^1.0.5",
25 | "@radix-ui/react-dropdown-menu": "^2.0.6",
26 | "@radix-ui/react-hover-card": "^1.0.7",
27 | "@radix-ui/react-icons": "^1.3.0",
28 | "@radix-ui/react-label": "^2.0.2",
29 | "@radix-ui/react-menubar": "^1.0.4",
30 | "@radix-ui/react-navigation-menu": "^1.1.4",
31 | "@radix-ui/react-popover": "^1.0.7",
32 | "@radix-ui/react-progress": "^1.0.3",
33 | "@radix-ui/react-radio-group": "^1.1.3",
34 | "@radix-ui/react-scroll-area": "^1.0.5",
35 | "@radix-ui/react-select": "^2.0.0",
36 | "@radix-ui/react-separator": "^1.0.3",
37 | "@radix-ui/react-slider": "^1.1.2",
38 | "@radix-ui/react-slot": "^1.0.2",
39 | "@radix-ui/react-switch": "^1.0.3",
40 | "@radix-ui/react-tabs": "^1.0.4",
41 | "@radix-ui/react-toast": "^1.1.5",
42 | "@radix-ui/react-toggle": "^1.0.3",
43 | "@radix-ui/react-toggle-group": "^1.0.4",
44 | "@radix-ui/react-tooltip": "^1.0.7",
45 | "@tanstack/react-query": "^4.28.0",
46 | "@tanstack/react-table": "^8.11.6",
47 | "@trpc/client": "^10.18.0",
48 | "@trpc/next": "^10.18.0",
49 | "@trpc/react-query": "^10.18.0",
50 | "@trpc/server": "^10.18.0",
51 | "class-variance-authority": "^0.7.0",
52 | "clsx": "^2.0.0",
53 | "cmdk": "^0.2.0",
54 | "date-fns": "^2.30.0",
55 | "lucide-react": "^0.294.0",
56 | "next": "^13.2.4",
57 | "next-auth": "^4.21.0",
58 | "next-themes": "^0.2.1",
59 | "react": "18.2.0",
60 | "react-day-picker": "^8.9.1",
61 | "react-dom": "18.2.0",
62 | "react-hook-form": "^7.48.2",
63 | "react-hot-toast": "^2.4.1",
64 | "react-icons": "^5.0.1",
65 | "react-spinners": "^0.13.8",
66 | "recharts": "^2.10.3",
67 | "superjson": "1.12.2",
68 | "tailwind-merge": "^2.1.0",
69 | "tailwindcss-animate": "^1.0.7",
70 | "zod": "^3.22.4",
71 | "zustand": "^4.4.7"
72 | },
73 | "devDependencies": {
74 | "@types/eslint": "^8.21.3",
75 | "@types/jest": "^29.5.12",
76 | "@types/mocha": "^10.0.6",
77 | "@types/node": "^18.15.5",
78 | "@types/prettier": "^2.7.2",
79 | "@types/react": "^18.0.28",
80 | "@types/react-dom": "^18.0.11",
81 | "@typescript-eslint/eslint-plugin": "^5.56.0",
82 | "@typescript-eslint/parser": "^5.56.0",
83 | "autoprefixer": "^10.4.14",
84 | "eslint": "^8.36.0",
85 | "eslint-config-next": "^13.2.4",
86 | "jest": "^29.7.0",
87 | "mocha": "^10.4.0",
88 | "postcss": "^8.4.21",
89 | "prettier": "^2.8.6",
90 | "prettier-plugin-tailwindcss": "^0.2.6",
91 | "prisma": "^4.11.0",
92 | "tailwindcss": "^3.3.0",
93 | "typescript": "^5.0.2"
94 | },
95 | "ct3aMetadata": {
96 | "initVersion": "7.12.0"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
8 | module.exports = config;
9 |
--------------------------------------------------------------------------------
/prettier.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import("prettier").Config} */
2 | const config = {
3 | plugins: [require.resolve("prettier-plugin-tailwindcss")],
4 | };
5 |
6 | module.exports = config;
7 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | generator client {
5 | provider = "prisma-client-js"
6 | }
7 |
8 | datasource db {
9 | provider = "postgresql"
10 | // NOTE: When using postgresql, mysql or sqlserver, uncomment the @db.Text annotations in model Account below
11 | // Further reading:
12 | // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
13 | // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
14 | url = env("DATABASE_URL")
15 | }
16 |
17 | model Example {
18 | id String @id @default(cuid())
19 | name String
20 | createdAt DateTime @default(now())
21 | updatedAt DateTime @updatedAt
22 | }
23 |
24 | // Necessary for Next auth
25 | model Account {
26 | id String @id @default(cuid())
27 | userId String
28 | type String
29 | provider String
30 | providerAccountId String
31 | refresh_token String? // @db.Text
32 | access_token String? // @db.Text
33 | expires_at Int?
34 | token_type String?
35 | scope String?
36 | id_token String? // @db.Text
37 | session_state String?
38 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
39 |
40 | @@unique([provider, providerAccountId])
41 | }
42 |
43 | model Session {
44 | id String @id @default(cuid())
45 | sessionToken String @unique
46 | userId String
47 | expires DateTime
48 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
49 | }
50 |
51 | model User {
52 | id String @id @default(cuid())
53 | name String?
54 | email String? @unique
55 | emailVerified DateTime?
56 | image String?
57 | accounts Account[]
58 | sessions Session[]
59 | }
60 |
61 | model VerificationToken {
62 | identifier String
63 | token String @unique
64 | expires DateTime
65 |
66 | @@unique([identifier, token])
67 | }
68 |
69 |
70 | model Employee {
71 | id String @id @default(cuid())
72 | firstName String @map("first_name")
73 | lastName String @map("last_name")
74 | gender String
75 | createAt DateTime @default(now()) @map("create_at")
76 | updateAt DateTime @default(now()) @updatedAt @map("update_at")
77 | }
--------------------------------------------------------------------------------
/src/components/common/alert-modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import { Button } from "@/components/ui/button";
5 | import {
6 | Dialog,
7 | DialogHeader,
8 | DialogContent,
9 | DialogTitle,
10 | DialogDescription,
11 | } from "@/components/ui/dialog";
12 |
13 | interface AlertModalProps {
14 | title: string;
15 | description: string;
16 | name?: string;
17 | loading: boolean;
18 | isOpen: boolean;
19 | onClose: () => void;
20 | onConfirm: () => void;
21 | }
22 |
23 | export const AlertModal = ({
24 | title,
25 | description,
26 | name,
27 | isOpen,
28 | onClose,
29 | onConfirm,
30 | loading,
31 | }: AlertModalProps) => {
32 | console.log();
33 | const [isMounted, setIsMounted] = useState(false);
34 | useEffect(() => {
35 | setIsMounted(true);
36 | }, []);
37 |
38 | if (!isMounted) {
39 | return null;
40 | }
41 |
42 | const onChange = (open: boolean) => {
43 | if (!open) {
44 | onClose();
45 | }
46 | };
47 |
48 | return (
49 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/common/heading.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface HeadingProps {
4 | title: string;
5 | description: string;
6 | }
7 |
8 | export const Heading = ({ title, description }: HeadingProps) => {
9 | return (
10 |
11 |
{title}
12 |
{description}
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/common/loading.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { ClipLoader } from "react-spinners"
4 |
5 | export const Loading = () => {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
--------------------------------------------------------------------------------
/src/components/common/modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Dialog,
5 | DialogTitle,
6 | DialogHeader,
7 | DialogContent,
8 | DialogDescription,
9 | } from "@/components/ui/dialog";
10 | import { useModal } from "@/providers/modal-provider";
11 | import {
12 | Sheet,
13 | SheetContent,
14 | SheetDescription,
15 | SheetHeader,
16 | SheetTitle,
17 | } from "@/components/ui/sheet";
18 | import { cn } from "@/lib/utils";
19 |
20 | interface ModalProps {
21 | type: "dialog" | "sheet";
22 | className?: string;
23 | title: string;
24 | description: string;
25 | children?: React.ReactNode;
26 | }
27 |
28 | export const Modal: React.FC = ({
29 | type,
30 | className,
31 | title,
32 | description,
33 | children,
34 | }) => {
35 | const { isOpen, toggleModal } = useModal();
36 |
37 | return (
38 | <>
39 | {type === "dialog" ? (
40 |
49 | ) : type === "sheet" ? (
50 |
51 |
52 |
53 | {title}
54 | {description}
55 |
56 | {children}
57 |
58 |
59 | ) : null}
60 | >
61 | );
62 | };
--------------------------------------------------------------------------------
/src/components/constants/side-nav.tsx:
--------------------------------------------------------------------------------
1 | import { BookOpenCheck, LayoutDashboard } from "lucide-react";
2 | import { type NavItem } from "@/types";
3 |
4 | export const NavItems: NavItem[] = [
5 | {
6 | title: "Dashboard",
7 | icon: LayoutDashboard,
8 | href: "/",
9 | color: "text-sky-500",
10 | },
11 | {
12 | title: "Example",
13 | icon: BookOpenCheck,
14 | href: "/example",
15 | color: "text-orange-500",
16 | isChildren: true,
17 | children: [
18 | {
19 | title: "Example-01",
20 | icon: BookOpenCheck,
21 | color: "text-red-500",
22 | href: "/example/employees",
23 | },
24 | {
25 | title: "Example-02",
26 | icon: BookOpenCheck,
27 | color: "text-red-500",
28 | href: "/example/example-02",
29 | },
30 | {
31 | title: "Example-03",
32 | icon: BookOpenCheck,
33 | color: "text-red-500",
34 | href: "/example/example-03",
35 | },
36 | ],
37 | },
38 | ];
39 |
--------------------------------------------------------------------------------
/src/components/dashboard/date-range-picker.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { CalendarIcon } from "@radix-ui/react-icons";
5 | import { addDays, format } from "date-fns";
6 | import { type DateRange } from "react-day-picker";
7 |
8 | import { cn } from "@/lib/utils";
9 | import { Button } from "@/components/ui/button";
10 | import { Calendar } from "@/components/ui/calendar";
11 | import {
12 | Popover,
13 | PopoverContent,
14 | PopoverTrigger,
15 | } from "@/components/ui/popover";
16 |
17 | export function CalendarDateRangePicker({
18 | className,
19 | }: React.HTMLAttributes) {
20 | const [date, setDate] = React.useState({
21 | from: new Date(2023, 0, 20),
22 | to: addDays(new Date(2023, 0, 20), 20),
23 | });
24 |
25 | return (
26 |
27 |
28 |
29 |
51 |
52 |
53 |
61 |
62 |
63 |
64 | );
65 | }
--------------------------------------------------------------------------------
/src/components/dashboard/overview.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts";
4 |
5 | const data = [
6 | {
7 | name: "Jan",
8 | total: Math.floor(Math.random() * 5000) + 1000,
9 | },
10 | {
11 | name: "Feb",
12 | total: Math.floor(Math.random() * 5000) + 1000,
13 | },
14 | {
15 | name: "Mar",
16 | total: Math.floor(Math.random() * 5000) + 1000,
17 | },
18 | {
19 | name: "Apr",
20 | total: Math.floor(Math.random() * 5000) + 1000,
21 | },
22 | {
23 | name: "May",
24 | total: Math.floor(Math.random() * 5000) + 1000,
25 | },
26 | {
27 | name: "Jun",
28 | total: Math.floor(Math.random() * 5000) + 1000,
29 | },
30 | {
31 | name: "Jul",
32 | total: Math.floor(Math.random() * 5000) + 1000,
33 | },
34 | {
35 | name: "Aug",
36 | total: Math.floor(Math.random() * 5000) + 1000,
37 | },
38 | {
39 | name: "Sep",
40 | total: Math.floor(Math.random() * 5000) + 1000,
41 | },
42 | {
43 | name: "Oct",
44 | total: Math.floor(Math.random() * 5000) + 1000,
45 | },
46 | {
47 | name: "Nov",
48 | total: Math.floor(Math.random() * 5000) + 1000,
49 | },
50 | {
51 | name: "Dec",
52 | total: Math.floor(Math.random() * 5000) + 1000,
53 | },
54 | ];
55 |
56 | export function Overview() {
57 | return (
58 |
59 |
60 |
67 | `$${value}`}
74 | />
75 |
76 |
77 |
78 | );
79 | }
--------------------------------------------------------------------------------
/src/components/dashboard/recent-sales.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
2 |
3 | export function RecentSales() {
4 | return (
5 |
6 |
7 |
8 |
9 | OM
10 |
11 |
12 |
Olivia Martin
13 |
14 | olivia.martin@email.com
15 |
16 |
17 |
+$1,999.00
18 |
19 |
20 |
21 |
22 | JL
23 |
24 |
25 |
Jackson Lee
26 |
jackson.lee@email.com
27 |
28 |
+$39.00
29 |
30 |
31 |
32 |
33 | IN
34 |
35 |
36 |
Isabella Nguyen
37 |
38 | isabella.nguyen@email.com
39 |
40 |
41 |
+$299.00
42 |
43 |
44 |
45 |
46 | WK
47 |
48 |
49 |
William Kim
50 |
will@email.com
51 |
52 |
+$99.00
53 |
54 |
55 |
56 |
57 | SD
58 |
59 |
60 |
Sofia Davis
61 |
sofia.davis@email.com
62 |
63 |
+$39.00
64 |
65 |
66 | );
67 | }
--------------------------------------------------------------------------------
/src/components/layout/header.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { ThemeToggle } from "@/components/layout/theme-toggle";
3 | import { cn } from "@/lib/utils";
4 | import { MobileSidebar } from "@/components/layout/mobile-sidebar";
5 | import Link from "next/link";
6 | import { Boxes } from "lucide-react";
7 | import { UserNav } from "@/components/layout/user-nav";
8 | import { signIn, useSession } from "next-auth/react";
9 | import { Button } from "@/components/ui/button";
10 |
11 | export default function Header() {
12 | const { data: sessionData } = useSession();
13 | return (
14 |
15 |
42 |
43 | );
44 | }
--------------------------------------------------------------------------------
/src/components/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "@/components/layout/header";
3 | import Sidebar from "@/components/layout/sidebar";
4 |
5 | export const Layout = ({ children }: { children: React.ReactNode }) => {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
12 | {children}
13 |
14 |
15 | >
16 | );
17 | };
--------------------------------------------------------------------------------
/src/components/layout/mobile-sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { MenuIcon } from "lucide-react";
3 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
4 | import { SideNav } from "@/components/layout/side-nav";
5 | import { NavItems } from "@/components/constants/side-nav";
6 |
7 | export const MobileSidebar = () => {
8 | const [open, setOpen] = useState(false);
9 | const [isMounted, setIsMounted] = useState(false);
10 |
11 | useEffect(() => {
12 | setIsMounted(true);
13 | }, []);
14 |
15 | if (!isMounted) {
16 | return null;
17 | }
18 |
19 | return (
20 | <>
21 |
22 |
23 |
24 |
25 |
T3 app template
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | >
35 | );
36 | };
--------------------------------------------------------------------------------
/src/components/layout/side-nav.tsx:
--------------------------------------------------------------------------------
1 |
2 | "use client";
3 | import Link from "next/link";
4 |
5 | import { type NavItem } from "@/types";
6 | import { usePathname } from "next/navigation";
7 | import { cn } from "@/lib/utils";
8 | import { useSidebar } from "@/hooks/useSidebar";
9 | import { buttonVariants } from "@/components/ui/button";
10 |
11 | import {
12 | Accordion,
13 | AccordionContent,
14 | AccordionItem,
15 | AccordionTrigger,
16 | } from "@/components/layout/subnav-accordion";
17 | import { useEffect, useState } from "react";
18 | import { ChevronDownIcon } from "@radix-ui/react-icons";
19 |
20 | interface SideNavProps {
21 | items: NavItem[];
22 | setOpen?: (open: boolean) => void;
23 | className?: string;
24 | }
25 |
26 | export function SideNav({ items, setOpen, className }: SideNavProps) {
27 | const path = usePathname();
28 | const { isOpen } = useSidebar();
29 | const [openItem, setOpenItem] = useState("");
30 | const [lastOpenItem, setLastOpenItem] = useState("");
31 |
32 | useEffect(() => {
33 | if (isOpen) {
34 | setOpenItem(lastOpenItem);
35 | } else {
36 | setLastOpenItem(openItem);
37 | setOpenItem("");
38 | }
39 | }, [isOpen]);
40 |
41 | return (
42 |
131 | );
132 | }
133 |
--------------------------------------------------------------------------------
/src/components/layout/sidebar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { SideNav } from "@/components/layout/side-nav";
3 | import { NavItems } from "@/components/constants/side-nav";
4 |
5 | import { cn } from "@/lib/utils";
6 | import { useSidebar } from "@/hooks/useSidebar";
7 | import { BsArrowLeftShort } from "react-icons/bs";
8 |
9 | interface SidebarProps {
10 | className?: string;
11 | }
12 |
13 | export default function Sidebar({ className }: SidebarProps) {
14 | const { isOpen, toggle } = useSidebar();
15 | const [status, setStatus] = useState(false);
16 |
17 | const handleToggle = () => {
18 | setStatus(true);
19 | toggle();
20 | setTimeout(() => setStatus(false), 500);
21 | };
22 | return (
23 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/layout/subnav-accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Accordion = AccordionPrimitive.Root;
9 |
10 | const AccordionItem = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
19 | ));
20 | AccordionItem.displayName = "AccordionItem";
21 |
22 | const AccordionTrigger = React.forwardRef<
23 | React.ElementRef,
24 | React.ComponentPropsWithoutRef
25 | >(({ className, children, ...props }, ref) => (
26 |
27 | svg]:rotate-180",
31 | className
32 | )}
33 | {...props}
34 | >
35 | {children}
36 |
37 |
38 | ));
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
50 | {children}
51 |
52 | ));
53 | AccordionContent.displayName = AccordionPrimitive.Content.displayName;
54 |
55 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
--------------------------------------------------------------------------------
/src/components/layout/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | import { Moon, Sun } from "lucide-react";
2 | import { useTheme } from "next-themes";
3 | import { Button } from "@/components/ui/button";
4 |
5 | export function ThemeToggle() {
6 | const { setTheme, theme } = useTheme();
7 |
8 | return (
9 |
20 | );
21 | }
--------------------------------------------------------------------------------
/src/components/layout/user-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { type User } from "@prisma/client"
2 | import { type AvatarProps } from "@radix-ui/react-avatar"
3 |
4 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
5 |
6 | import { User as UserIcon } from "lucide-react"
7 |
8 | interface UserAvatarProps extends AvatarProps {
9 | user: Pick
10 | }
11 |
12 | export function UserAvatar({ user, ...props }: UserAvatarProps) {
13 | return (
14 |
15 | {user.image ? (
16 |
17 | ) : (
18 |
19 | {user.name}
20 |
21 |
22 | )}
23 |
24 | )
25 | }
--------------------------------------------------------------------------------
/src/components/layout/user-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { signOut } from "next-auth/react";
4 | import type { User } from "next-auth";
5 | import {
6 | DropdownMenu,
7 | DropdownMenuContent,
8 | DropdownMenuItem,
9 | DropdownMenuSeparator,
10 | DropdownMenuTrigger,
11 | } from "@/components/ui/dropdown-menu";
12 | import { Button } from "@/components/ui/button";
13 | import { LogOut } from "lucide-react";
14 | import { UserAvatar } from "@/components/layout/user-avatar";
15 |
16 | type Props = {
17 | user: Pick;
18 | };
19 |
20 | export function UserNav({ user }: Props) {
21 | return (
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 | {user.name &&
{user.name}
}
33 | {user.email && (
34 |
35 | {user.email}
36 |
37 | )}
38 |
39 |
40 |
41 |
42 |
52 |
53 |
54 |
55 | );
56 | }
--------------------------------------------------------------------------------
/src/components/page-component/example/employee/cell-action.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState } from "react";
3 | import { useRouter } from "next/navigation";
4 | import toast from "react-hot-toast";
5 | import { api } from "@/utils/api";
6 | import { Button } from "@/components/ui/button";
7 | import {
8 | Tooltip,
9 | TooltipContent,
10 | TooltipProvider,
11 | TooltipTrigger,
12 | } from "@/components/ui/tooltip";
13 |
14 | import { Pencil, Trash2 } from "lucide-react";
15 | import { AlertModal } from "@/components/common/alert-modal";
16 | import { type EmployeeColumn } from "@/lib/validators";
17 |
18 | interface CellActionProps {
19 | data: EmployeeColumn;
20 | }
21 |
22 | export function CellAction({ data }: CellActionProps) {
23 | const router = useRouter();
24 | const [alertModalOpen, setAlertModalOpen] = useState(false);
25 |
26 | const { refetch } = api.employee.getAll.useQuery(undefined, {
27 | enabled: false,
28 | });
29 |
30 | const { mutate: deleteEmployee, isLoading: deleteEmployeeIsLoading } =
31 | api.employee.delete.useMutation({
32 | onError: (err) => {
33 | toast.error(err.message);
34 | },
35 | onSuccess: async (data) => {
36 | toast.success("Delete Employee success");
37 | await refetch();
38 | },
39 | });
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
56 |
57 |
58 | Update employee
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
76 |
77 |
78 | Delete employee
79 |
80 |
81 |
82 |
83 |
setAlertModalOpen(false)}
89 | onConfirm={() => deleteEmployee(data.id)}
90 | loading={deleteEmployeeIsLoading}
91 | />
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/page-component/example/employee/client.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { Heading } from "@/components/common/heading";
4 | import { useRouter } from "next/navigation";
5 | import { Button } from "@/components/ui/button";
6 | import { Plus } from "lucide-react";
7 | import { Separator } from "@/components/ui/separator";
8 | import { DataTable } from "@/components/ui/data-table/data-table";
9 | import { type EmployeeColumn } from "@/lib/validators";
10 | import { columns } from "./columns";
11 |
12 | interface EmployeeClientProps {
13 | data: EmployeeColumn[];
14 | }
15 | export const EmployeeClient = ({ data }: EmployeeClientProps) => {
16 | const router = useRouter();
17 |
18 | return (
19 | <>
20 |
21 |
25 |
32 |
33 |
34 |
35 |
36 |
37 | >
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/page-component/example/employee/columns.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Checkbox } from "@/components/ui/checkbox";
4 | import { type ColumnDef } from "@tanstack/react-table";
5 | import { type EmployeeColumn } from "@/lib/validators";
6 | import { CellAction } from "./cell-action";
7 |
8 | export const columns: ColumnDef[] = [
9 | {
10 | id: "select",
11 | header: ({ table }) => (
12 | table.toggleAllPageRowsSelected(!!value)}
18 | aria-label="Select all"
19 | className="translate-y-[2px]"
20 | />
21 | ),
22 | cell: ({ row }) => (
23 | row.toggleSelected(!!value)}
26 | aria-label="Select row"
27 | className="translate-y-[2px]"
28 | />
29 | ),
30 | enableSorting: false,
31 | enableHiding: false,
32 | },
33 | {
34 | accessorKey: "firstName",
35 | header: "First Name",
36 | },
37 | {
38 | accessorKey: "lastName",
39 | header: "Last Name",
40 | },
41 | {
42 | accessorKey: "gender",
43 | header: "Gender",
44 | },
45 | {
46 | accessorKey: "createAt",
47 | header: "Create Time",
48 | },
49 | {
50 | accessorKey: "updateAt",
51 | header: "Update Time",
52 | },
53 | {
54 | id: "actions",
55 | enableSorting: false,
56 | cell: ({ row }) => ,
57 | },
58 | ];
59 |
--------------------------------------------------------------------------------
/src/components/page-component/example/employee/employee-form.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-misused-promises */
2 | import React, { useState } from "react";
3 | import { type Employee } from "@prisma/client";
4 | import { useRouter } from "next/navigation";
5 | import { useForm } from "react-hook-form";
6 | import { type EmployeeFromValues, employeeFormSchema } from "@/lib/validators";
7 | import { zodResolver } from "@hookform/resolvers/zod";
8 | import { api } from "@/utils/api";
9 | import toast from "react-hot-toast";
10 | import { Heading } from "@/components/common/heading";
11 | import { Button } from "@/components/ui/button";
12 | import { Trash } from "lucide-react";
13 | import { Separator } from "@/components/ui/separator";
14 | import {
15 | Form,
16 | FormControl,
17 | FormField,
18 | FormItem,
19 | FormLabel,
20 | FormMessage,
21 | } from "@/components/ui/form";
22 | import { Input } from "@/components/ui/input";
23 | import {
24 | Select,
25 | SelectContent,
26 | SelectItem,
27 | SelectTrigger,
28 | SelectValue,
29 | } from "@/components/ui/select";
30 | import { AlertModal } from "@/components/common/alert-modal";
31 |
32 | interface EmployeeFormProps {
33 | initialData: Employee | null | undefined;
34 | }
35 | export const EmployeeForm = ({ initialData }: EmployeeFormProps) => {
36 | const router = useRouter();
37 | const [open, setOpen] = useState(false);
38 | const [loading, setLoading] = useState(false);
39 |
40 | const title = initialData ? "Edit employee" : "Create employee";
41 | const description = initialData ? "Edit a employee" : "Create a new employee";
42 | const toastMessage = initialData
43 | ? "Employee updated successfully"
44 | : "Employee created successfully";
45 | const action = initialData ? "Save Changes" : "Create";
46 |
47 | const form = useForm({
48 | resolver: zodResolver(employeeFormSchema),
49 | defaultValues: initialData || {
50 | firstName: "",
51 | lastName: "",
52 | gender: "",
53 | },
54 | });
55 |
56 | const { mutate: createEmployee } = api.employee.create.useMutation({
57 | onError: (err) => {
58 | toast.error(err.message);
59 | },
60 | onSuccess: (data) => {
61 | toast.success(toastMessage);
62 | router.push(`/example/employees`);
63 | },
64 | });
65 |
66 | const { mutate: updateEmployee } = api.employee.update.useMutation({
67 | onError: (err) => {
68 | toast.error(err.message);
69 | },
70 | onSuccess: (data) => {
71 | toast.success(toastMessage);
72 | router.push(`/example/employees`);
73 | },
74 | });
75 |
76 | const { mutate: deleteEmployee, isLoading: deleteEmployeeIsLoading } =
77 | api.employee.delete.useMutation({
78 | onError: (err) => {
79 | toast.error(err.message);
80 | },
81 | onSuccess: (data) => {
82 | toast.success(toastMessage);
83 | router.push(`/example/employees`);
84 | },
85 | });
86 |
87 | const onSubmit = (values: EmployeeFromValues) => {
88 | setLoading(true);
89 | if (initialData) {
90 | updateEmployee({ ...values, id: initialData.id });
91 | } else {
92 | createEmployee(values);
93 | }
94 | setLoading(false);
95 | };
96 |
97 | const onDelete = () => {
98 | deleteEmployee(initialData?.id as string);
99 | };
100 |
101 | return (
102 | <>
103 |
104 |
105 | {initialData && (
106 |
114 | )}
115 |
116 |
117 |
118 |
202 |
203 | setOpen(false)}
209 | onConfirm={onDelete}
210 | loading={deleteEmployeeIsLoading}
211 | />
212 | >
213 | );
214 | };
215 |
--------------------------------------------------------------------------------
/src/components/page-component/example/example-02/multi-select-input.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { X } from "lucide-react";
5 |
6 | import { Badge } from "@/components/ui/badge";
7 | import { Command, CommandGroup, CommandItem } from "@/components/ui/command";
8 | import { Command as CommandPrimitive } from "cmdk";
9 |
10 | type Framework = Record<"value" | "label", string>;
11 |
12 | const FRAMEWORKS = [
13 | {
14 | value: "next.js",
15 | label: "Next.js",
16 | },
17 | {
18 | value: "sveltekit",
19 | label: "SvelteKit",
20 | },
21 | {
22 | value: "nuxt.js",
23 | label: "Nuxt.js",
24 | },
25 | {
26 | value: "remix",
27 | label: "Remix",
28 | },
29 | {
30 | value: "astro",
31 | label: "Astro",
32 | },
33 | {
34 | value: "wordpress",
35 | label: "WordPress",
36 | },
37 | {
38 | value: "express.js",
39 | label: "Express.js",
40 | },
41 | {
42 | value: "nest.js",
43 | label: "Nest.js",
44 | },
45 | ] satisfies Framework[];
46 |
47 | export function FancyMultiSelect() {
48 | const inputRef = React.useRef(null);
49 | const [open, setOpen] = React.useState(false);
50 | const [selected, setSelected] = React.useState([]);
51 | const [inputValue, setInputValue] = React.useState("");
52 |
53 | const handleUnselect = React.useCallback((framework: Framework) => {
54 | setSelected((prev) => prev.filter((s) => s.value !== framework.value));
55 | }, []);
56 |
57 | const handleKeyDown = React.useCallback(
58 | (e: React.KeyboardEvent) => {
59 | const input = inputRef.current;
60 | if (input) {
61 | if (e.key === "Delete" || e.key === "Backspace") {
62 | if (input.value === "") {
63 | setSelected((prev) => {
64 | const newSelected = [...prev];
65 | newSelected.pop();
66 | return newSelected;
67 | });
68 | }
69 | }
70 | // This is not a default behaviour of the field
71 | if (e.key === "Escape") {
72 | input.blur();
73 | }
74 | }
75 | },
76 | []
77 | );
78 |
79 | const selectables = FRAMEWORKS.filter(
80 | (framework) =>
81 | !selected.some(
82 | (slect_framework) => framework.value === slect_framework.value
83 | )
84 | );
85 |
86 | return (
87 |
91 |
92 |
93 | {selected.map((framework) => {
94 | return (
95 |
96 | {framework.label}
97 |
112 |
113 | );
114 | })}
115 | {/* Avoid having the "Search" Icon */}
116 | setOpen(false)}
121 | onFocus={() => setOpen(true)}
122 | placeholder="Select frameworks..."
123 | className="ml-2 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
124 | />
125 |
126 |
127 |
128 | {open && selectables.length > 0 ? (
129 |
130 |
131 | {selectables.map((framework) => {
132 | return (
133 | {
136 | e.preventDefault();
137 | e.stopPropagation();
138 | }}
139 | onSelect={(value) => {
140 | setInputValue("");
141 | setSelected((prev) => [...prev, framework]);
142 | }}
143 | className={"cursor-pointer"}
144 | >
145 | {framework.label}
146 |
147 | );
148 | })}
149 |
150 |
151 | ) : null}
152 |
153 |
154 | );
155 | }
156 |
--------------------------------------------------------------------------------
/src/components/page-component/example/example-03/email-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useForm } from "react-hook-form";
4 | import { useFormState } from "./form-context";
5 |
6 | type TFormValues = {
7 | email: string;
8 | };
9 | const EmailForm = () => {
10 | const { onHandleNext, onHandleBack, setFormData, formData } = useFormState();
11 | const { register, handleSubmit } = useForm({
12 | defaultValues: formData,
13 | });
14 |
15 | function onHandleFormSubmit(data: TFormValues) {
16 | setFormData((prevFormData) => ({ ...prevFormData, ...data }));
17 | onHandleNext();
18 | }
19 |
20 | return (
21 | // eslint-disable-next-line @typescript-eslint/no-misused-promises
22 |
46 | );
47 | };
48 |
49 | export default EmailForm;
50 |
--------------------------------------------------------------------------------
/src/components/page-component/example/example-03/form-context.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | type Dispatch,
3 | type SetStateAction,
4 | createContext,
5 | useContext,
6 | useState,
7 | } from "react";
8 |
9 | interface TFormData {
10 | username: string;
11 | email: string;
12 | password: string;
13 | }
14 |
15 | interface FormContextProps {
16 | onHandleNext: () => void;
17 | onHandleBack: () => void;
18 | step: number;
19 | formData: TFormData;
20 | setFormData: Dispatch>;
21 | }
22 |
23 | const FormContext = createContext({
24 | // eslint-disable-next-line @typescript-eslint/no-empty-function
25 | onHandleNext: () => {},
26 | // eslint-disable-next-line @typescript-eslint/no-empty-function
27 | onHandleBack: () => {},
28 | step: 1,
29 | formData: {
30 | username: "",
31 | email: "",
32 | password: "",
33 | },
34 | // eslint-disable-next-line @typescript-eslint/no-empty-function
35 | setFormData: () => {},
36 | });
37 |
38 | interface FormProviderProps {
39 | children: React.ReactNode;
40 | }
41 |
42 | export function FormProvider({ children }: FormProviderProps) {
43 | const [step, setStep] = useState(1);
44 | const [formData, setFormData] = useState({
45 | username: "",
46 | email: "",
47 | password: "",
48 | });
49 |
50 | console.log(formData);
51 | function onHandleNext() {
52 | console.log(step);
53 | setStep((prevValue) => prevValue + 1);
54 | }
55 |
56 | function onHandleBack() {
57 | setStep((prevValue) => prevValue - 1);
58 | }
59 |
60 | return (
61 |
64 | {children}
65 |
66 | );
67 | }
68 |
69 | export function useFormState() {
70 | return useContext(FormContext);
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/page-component/example/example-03/form-step.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useFormState } from "./form-context";
3 | import UsernameForm from "./username-form";
4 | import EmailForm from "./email-form";
5 | import PasswordForm from "./password-form";
6 |
7 | const FormStep = () => {
8 | const { step } = useFormState();
9 |
10 | switch (step) {
11 | case 1:
12 | return ;
13 | case 2:
14 | return ;
15 | case 3:
16 | return ;
17 | default:
18 | return null;
19 | }
20 | };
21 |
22 | export default FormStep;
23 |
--------------------------------------------------------------------------------
/src/components/page-component/example/example-03/password-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useForm } from "react-hook-form";
4 | import { useFormState } from "./form-context";
5 | import { useState } from "react";
6 |
7 | type TFormValues = {
8 | password: string;
9 | };
10 | const PasswordForm = () => {
11 | const { onHandleBack, setFormData, formData } = useFormState();
12 | const { register, handleSubmit } = useForm();
13 |
14 | const [isCreated, setCreated] = useState(false);
15 |
16 | function onHandleFormSubmit(data: TFormValues) {
17 | setFormData((prevFormData) => ({ ...prevFormData, ...data }));
18 | setCreated(true);
19 | }
20 |
21 | return isCreated ? (
22 |
23 |
Account created successfully
24 |
{JSON.stringify(formData)}
25 |
26 | ) : (
27 | // eslint-disable-next-line @typescript-eslint/no-misused-promises
28 |
52 | );
53 | };
54 |
55 | export default PasswordForm;
56 |
--------------------------------------------------------------------------------
/src/components/page-component/example/example-03/username-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useForm } from "react-hook-form";
4 | import { useFormState } from "./form-context";
5 |
6 | type TFormValues = {
7 | username: string;
8 | };
9 | const UsernameForm = () => {
10 | const { onHandleNext, setFormData, formData } = useFormState();
11 |
12 | const { register, handleSubmit } = useForm({
13 | defaultValues: formData,
14 | });
15 |
16 | function onHandleFormSubmit(data: TFormValues) {
17 | setFormData((prevFormData) => ({ ...prevFormData, ...data }));
18 | void onHandleNext();
19 | }
20 |
21 | return (
22 | // eslint-disable-next-line @typescript-eslint/no-misused-promises
23 |
40 | );
41 | };
42 |
43 | export default UsernameForm;
44 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
5 | import { ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = "AccordionItem"
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180",
32 | className
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ))
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
52 | {children}
53 |
54 | ))
55 |
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5 |
6 | import { cn } from "@/lib/utils"
7 | import { buttonVariants } from "@/components/ui/button"
8 |
9 | const AlertDialog = AlertDialogPrimitive.Root
10 |
11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12 |
13 | const AlertDialogPortal = AlertDialogPrimitive.Portal
14 |
15 | const AlertDialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29 |
30 | const AlertDialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, ...props }, ref) => (
34 |
35 |
36 |
44 |
45 | ))
46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47 |
48 | const AlertDialogHeader = ({
49 | className,
50 | ...props
51 | }: React.HTMLAttributes) => (
52 |
59 | )
60 | AlertDialogHeader.displayName = "AlertDialogHeader"
61 |
62 | const AlertDialogFooter = ({
63 | className,
64 | ...props
65 | }: React.HTMLAttributes) => (
66 |
73 | )
74 | AlertDialogFooter.displayName = "AlertDialogFooter"
75 |
76 | const AlertDialogTitle = React.forwardRef<
77 | React.ElementRef,
78 | React.ComponentPropsWithoutRef
79 | >(({ className, ...props }, ref) => (
80 |
85 | ))
86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87 |
88 | const AlertDialogDescription = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
97 | ))
98 | AlertDialogDescription.displayName =
99 | AlertDialogPrimitive.Description.displayName
100 |
101 | const AlertDialogAction = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112 |
113 | const AlertDialogCancel = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
126 | ))
127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128 |
129 | export {
130 | AlertDialog,
131 | AlertDialogPortal,
132 | AlertDialogOverlay,
133 | AlertDialogTrigger,
134 | AlertDialogContent,
135 | AlertDialogHeader,
136 | AlertDialogFooter,
137 | AlertDialogTitle,
138 | AlertDialogDescription,
139 | AlertDialogAction,
140 | AlertDialogCancel,
141 | }
142 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ChevronLeft, ChevronRight } from "lucide-react"
5 | import { DayPicker } from "react-day-picker"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { buttonVariants } from "@/components/ui/button"
9 |
10 | export type CalendarProps = React.ComponentProps
11 |
12 | function Calendar({
13 | className,
14 | classNames,
15 | showOutsideDays = true,
16 | ...props
17 | }: CalendarProps) {
18 | return (
19 | ,
58 | IconRight: ({ ...props }) => ,
59 | }}
60 | {...props}
61 | />
62 | )
63 | }
64 | Calendar.displayName = "Calendar"
65 |
66 | export { Calendar }
67 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/src/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { type DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { Search } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 | import { Dialog, DialogContent } from "@/components/ui/dialog"
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
27 | interface CommandDialogProps extends DialogProps { }
28 |
29 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
30 | return (
31 |
38 | )
39 | }
40 |
41 | const CommandInput = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, ...props }, ref) => (
45 |
46 |
47 |
55 |
56 | ))
57 |
58 | CommandInput.displayName = CommandPrimitive.Input.displayName
59 |
60 | const CommandList = React.forwardRef<
61 | React.ElementRef,
62 | React.ComponentPropsWithoutRef
63 | >(({ className, ...props }, ref) => (
64 |
69 | ))
70 |
71 | CommandList.displayName = CommandPrimitive.List.displayName
72 |
73 | const CommandEmpty = React.forwardRef<
74 | React.ElementRef,
75 | React.ComponentPropsWithoutRef
76 | >((props, ref) => (
77 |
82 | ))
83 |
84 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
85 |
86 | const CommandGroup = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, ...props }, ref) => (
90 |
98 | ))
99 |
100 | CommandGroup.displayName = CommandPrimitive.Group.displayName
101 |
102 | const CommandSeparator = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
113 |
114 | const CommandItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, ...props }, ref) => (
118 |
126 | ))
127 |
128 | CommandItem.displayName = CommandPrimitive.Item.displayName
129 |
130 | const CommandShortcut = ({
131 | className,
132 | ...props
133 | }: React.HTMLAttributes) => {
134 | return (
135 |
142 | )
143 | }
144 | CommandShortcut.displayName = "CommandShortcut"
145 |
146 | export {
147 | Command,
148 | CommandDialog,
149 | CommandInput,
150 | CommandList,
151 | CommandEmpty,
152 | CommandGroup,
153 | CommandItem,
154 | CommandShortcut,
155 | CommandSeparator,
156 | }
157 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const ContextMenu = ContextMenuPrimitive.Root
10 |
11 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger
12 |
13 | const ContextMenuGroup = ContextMenuPrimitive.Group
14 |
15 | const ContextMenuPortal = ContextMenuPrimitive.Portal
16 |
17 | const ContextMenuSub = ContextMenuPrimitive.Sub
18 |
19 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
20 |
21 | const ContextMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
41 |
42 | const ContextMenuSubContent = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef
45 | >(({ className, ...props }, ref) => (
46 |
54 | ))
55 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
56 |
57 | const ContextMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
62 |
70 |
71 | ))
72 | ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
73 |
74 | const ContextMenuItem = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef & {
77 | inset?: boolean
78 | }
79 | >(({ className, inset, ...props }, ref) => (
80 |
89 | ))
90 | ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
91 |
92 | const ContextMenuCheckboxItem = React.forwardRef<
93 | React.ElementRef,
94 | React.ComponentPropsWithoutRef
95 | >(({ className, children, checked, ...props }, ref) => (
96 |
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 | ))
113 | ContextMenuCheckboxItem.displayName =
114 | ContextMenuPrimitive.CheckboxItem.displayName
115 |
116 | const ContextMenuRadioItem = React.forwardRef<
117 | React.ElementRef,
118 | React.ComponentPropsWithoutRef
119 | >(({ className, children, ...props }, ref) => (
120 |
128 |
129 |
130 |
131 |
132 |
133 | {children}
134 |
135 | ))
136 | ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
137 |
138 | const ContextMenuLabel = React.forwardRef<
139 | React.ElementRef,
140 | React.ComponentPropsWithoutRef & {
141 | inset?: boolean
142 | }
143 | >(({ className, inset, ...props }, ref) => (
144 |
153 | ))
154 | ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
155 |
156 | const ContextMenuSeparator = React.forwardRef<
157 | React.ElementRef,
158 | React.ComponentPropsWithoutRef
159 | >(({ className, ...props }, ref) => (
160 |
165 | ))
166 | ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
167 |
168 | const ContextMenuShortcut = ({
169 | className,
170 | ...props
171 | }: React.HTMLAttributes) => {
172 | return (
173 |
180 | )
181 | }
182 | ContextMenuShortcut.displayName = "ContextMenuShortcut"
183 |
184 | export {
185 | ContextMenu,
186 | ContextMenuTrigger,
187 | ContextMenuContent,
188 | ContextMenuItem,
189 | ContextMenuCheckboxItem,
190 | ContextMenuRadioItem,
191 | ContextMenuLabel,
192 | ContextMenuSeparator,
193 | ContextMenuShortcut,
194 | ContextMenuGroup,
195 | ContextMenuPortal,
196 | ContextMenuSub,
197 | ContextMenuSubContent,
198 | ContextMenuSubTrigger,
199 | ContextMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/src/components/ui/data-table/data-table-column-header.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ArrowDownIcon,
3 | ArrowUpIcon,
4 | CaretSortIcon,
5 | EyeNoneIcon,
6 | } from "@radix-ui/react-icons"
7 | import { type Column } from "@tanstack/react-table"
8 |
9 | import { cn } from "@/lib/utils"
10 | import { Button } from "@/components/ui/button"
11 | import {
12 | DropdownMenu,
13 | DropdownMenuContent,
14 | DropdownMenuItem,
15 | DropdownMenuSeparator,
16 | DropdownMenuTrigger,
17 | } from "@/components/ui/dropdown-menu"
18 |
19 | interface DataTableColumnHeaderProps
20 | extends React.HTMLAttributes {
21 | column: Column
22 | title: string
23 | }
24 |
25 | export function DataTableColumnHeader({
26 | column,
27 | title,
28 | className,
29 | }: DataTableColumnHeaderProps) {
30 | if (!column.getCanSort()) {
31 | return {title}
32 | }
33 |
34 | return (
35 |
36 |
37 |
38 |
52 |
53 |
54 | column.toggleSorting(false)}>
55 |
56 | Asc
57 |
58 | column.toggleSorting(true)}>
59 |
60 | Desc
61 |
62 |
63 | column.toggleVisibility(false)}>
64 |
65 | Hide
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/ui/data-table/data-table-pagination.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ChevronLeftIcon,
3 | ChevronRightIcon,
4 | DoubleArrowLeftIcon,
5 | DoubleArrowRightIcon,
6 | } from "@radix-ui/react-icons"
7 | import { type Table } from "@tanstack/react-table"
8 |
9 | import { Button } from "@/components/ui/button"
10 | import {
11 | Select,
12 | SelectContent,
13 | SelectItem,
14 | SelectTrigger,
15 | SelectValue,
16 | } from "@/components/ui/select"
17 |
18 | interface DataTablePaginationProps {
19 | table: Table
20 | }
21 |
22 | export function DataTablePagination({
23 | table,
24 | }: DataTablePaginationProps) {
25 | return (
26 |
27 |
28 | {table.getFilteredSelectedRowModel().rows.length} of{" "}
29 | {table.getFilteredRowModel().rows.length} row(s) selected.
30 |
31 |
32 |
33 |
Rows per page
34 |
51 |
52 |
53 | Page {table.getState().pagination.pageIndex + 1} of{" "}
54 | {table.getPageCount()}
55 |
56 |
57 |
66 |
75 |
84 |
93 |
94 |
95 |
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/src/components/ui/data-table/data-table-toolbar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 |
4 | import { type Table } from "@tanstack/react-table"
5 | import { Input } from "@/components/ui/input"
6 | import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"
7 |
8 | interface DataTableToolbarProps {
9 | table: Table
10 | }
11 |
12 | export function DataTableToolbar({
13 | table,
14 | }: DataTableToolbarProps) {
15 |
16 |
17 | return (
18 |
19 |
20 | table.setGlobalFilter(event.target.value)}
24 | className="h-8 w-[150px] lg:w-[250px]"
25 | />
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/ui/data-table/data-table-view-options.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
4 | import { MixerHorizontalIcon } from "@radix-ui/react-icons"
5 | import { type Table } from "@tanstack/react-table"
6 |
7 | import { Button } from "@/components/ui/button"
8 | import {
9 | DropdownMenu,
10 | DropdownMenuCheckboxItem,
11 | DropdownMenuContent,
12 | DropdownMenuLabel,
13 | DropdownMenuSeparator,
14 | } from "@/components/ui/dropdown-menu"
15 |
16 | interface DataTableViewOptionsProps {
17 | table: Table
18 | }
19 |
20 | export function DataTableViewOptions({
21 | table,
22 | }: DataTableViewOptionsProps) {
23 | return (
24 |
25 |
26 |
34 |
35 |
36 | Toggle columns
37 |
38 | {table
39 | .getAllColumns()
40 | .filter(
41 | (column) =>
42 | typeof column.accessorFn !== "undefined" && column.getCanHide()
43 | )
44 | .map((column) => {
45 | return (
46 | column.toggleVisibility(!!value)}
51 | >
52 | {column.id}
53 |
54 | )
55 | })}
56 |
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/ui/data-table/data-table.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | type ColumnDef,
6 | type ColumnFiltersState,
7 | type SortingState,
8 | type VisibilityState,
9 | flexRender,
10 | getCoreRowModel,
11 | getFacetedRowModel,
12 | getFacetedUniqueValues,
13 | getFilteredRowModel,
14 | getPaginationRowModel,
15 | getSortedRowModel,
16 | useReactTable,
17 | } from "@tanstack/react-table"
18 |
19 | import {
20 | Table,
21 | TableBody,
22 | TableCell,
23 | TableHead,
24 | TableHeader,
25 | TableRow,
26 | } from "@/components/ui/table"
27 |
28 | import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"
29 | import { DataTableToolbar } from "@/components/ui/data-table/data-table-toolbar"
30 |
31 | interface DataTableProps {
32 | columns: ColumnDef[]
33 | data: TData[]
34 | }
35 |
36 | export function DataTable({
37 | columns,
38 | data,
39 | }: DataTableProps) {
40 | const [rowSelection, setRowSelection] = React.useState({})
41 | const [columnVisibility, setColumnVisibility] =
42 | React.useState({})
43 | const [columnFilters, setColumnFilters] = React.useState(
44 | []
45 | )
46 | const [sorting, setSorting] = React.useState([])
47 |
48 | const table = useReactTable({
49 | data,
50 | columns,
51 | state: {
52 | sorting,
53 | columnVisibility,
54 | rowSelection,
55 | columnFilters,
56 | },
57 | enableRowSelection: true,
58 | onRowSelectionChange: setRowSelection,
59 | onSortingChange: setSorting,
60 | onColumnFiltersChange: setColumnFilters,
61 | onColumnVisibilityChange: setColumnVisibility,
62 | getCoreRowModel: getCoreRowModel(),
63 | getFilteredRowModel: getFilteredRowModel(),
64 | getPaginationRowModel: getPaginationRowModel(),
65 | getSortedRowModel: getSortedRowModel(),
66 | getFacetedRowModel: getFacetedRowModel(),
67 | getFacetedUniqueValues: getFacetedUniqueValues(),
68 | })
69 |
70 | return (
71 |
72 |
73 |
74 |
75 |
76 | {table.getHeaderGroups().map((headerGroup) => (
77 |
78 | {headerGroup.headers.map((header) => {
79 | return (
80 |
81 | {header.isPlaceholder
82 | ? null
83 | : flexRender(
84 | header.column.columnDef.header,
85 | header.getContext()
86 | )}
87 |
88 | )
89 | })}
90 |
91 | ))}
92 |
93 |
94 | {table.getRowModel().rows?.length ? (
95 | table.getRowModel().rows.map((row) => (
96 |
100 | {row.getVisibleCells().map((cell) => (
101 |
102 | {flexRender(
103 | cell.column.columnDef.cell,
104 | cell.getContext()
105 | )}
106 |
107 | ))}
108 |
109 | ))
110 | ) : (
111 |
112 |
116 | No results.
117 |
118 |
119 | )}
120 |
121 |
122 |
123 |
124 |
125 | )
126 | }
127 |
--------------------------------------------------------------------------------
/src/components/ui/date-picker.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { format } from "date-fns";
3 | import { Calendar as CalendarIcon } from "lucide-react";
4 |
5 | import { cn } from "@/lib/utils";
6 | import { Button } from "@/components/ui/button";
7 | import { Calendar } from "@/components/ui/calendar";
8 | import {
9 | Popover,
10 | PopoverContent,
11 | PopoverTrigger,
12 | } from "@/components/ui/popover";
13 | import { forwardRef } from "react";
14 |
15 | export const DatePicker = forwardRef<
16 | HTMLDivElement,
17 | {
18 | date?: Date;
19 | setDate: (date?: Date) => void;
20 | }
21 | >(function DatePickerCmp({ date, setDate }, ref) {
22 | return (
23 |
24 |
25 |
35 |
36 |
37 |
43 |
44 |
45 | );
46 | });
47 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogClose,
116 | DialogTrigger,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | export interface InputProps
7 | extends React.InputHTMLAttributes { }
8 |
9 | const Input = React.forwardRef(
10 | ({ className, type, ...props }, ref) => {
11 | return (
12 |
21 | )
22 | }
23 | )
24 | Input.displayName = "Input"
25 |
26 | export { Input }
27 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{" "}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5 | import { Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown, ChevronUp } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ))
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ))
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = "popper", ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ))
100 | SelectContent.displayName = SelectPrimitive.Content.displayName
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ))
135 | SelectItem.displayName = SelectPrimitive.Item.displayName
136 |
137 | const SelectSeparator = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef
140 | >(({ className, ...props }, ref) => (
141 |
146 | ))
147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148 |
149 | export {
150 | Select,
151 | SelectGroup,
152 | SelectValue,
153 | SelectTrigger,
154 | SelectContent,
155 | SelectLabel,
156 | SelectItem,
157 | SelectSeparator,
158 | SelectScrollUpButton,
159 | SelectScrollDownButton,
160 | }
161 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42 | right:
43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = "SheetHeader"
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = "SheetFooter"
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = "Table"
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = "TableHeader"
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = "TableBody"
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0",
47 | className
48 | )}
49 | {...props}
50 | />
51 | ))
52 | TableFooter.displayName = "TableFooter"
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ))
67 | TableRow.displayName = "TableRow"
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ))
82 | TableHead.displayName = "TableHead"
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.TdHTMLAttributes
87 | >(({ className, ...props }, ref) => (
88 | |
93 | ))
94 | TableCell.displayName = "TableCell"
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = "TableCaption"
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | export interface TextareaProps
7 | extends React.TextareaHTMLAttributes { }
8 |
9 | const Textarea = React.forwardRef(
10 | ({ className, ...props }, ref) => {
11 | return (
12 |
20 | )
21 | }
22 | )
23 | Textarea.displayName = "Textarea"
24 |
25 | export { Textarea }
26 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitives from "@radix-ui/react-toast"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background text-foreground",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5 | import { VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { toggleVariants } from "@/components/ui/toggle"
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | })
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ))
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext)
41 |
42 | return (
43 |