├── .cursorrules ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── setup │ └── action.yml └── workflows │ └── code-check.yml ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── biome.json ├── components.json ├── docker-compose.yml ├── drizzle.config.ts ├── drizzle └── meta │ └── _journal.json ├── next.config.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.cjs ├── public ├── favicon.ico ├── images │ └── screenshot.png ├── r │ ├── data-table-action-bar.json │ ├── data-table-filter-list.json │ ├── data-table-filter-menu.json │ ├── data-table-sort-list.json │ ├── data-table-ts-doc.json │ └── data-table.json └── site.webmanifest ├── registry.json ├── renovate.json ├── reset.d.ts ├── src ├── app │ ├── _components │ │ ├── create-task-sheet.tsx │ │ ├── delete-tasks-dialog.tsx │ │ ├── feature-flags-provider.tsx │ │ ├── task-form.tsx │ │ ├── tasks-table-action-bar.tsx │ │ ├── tasks-table-columns.tsx │ │ ├── tasks-table-toolbar-actions.tsx │ │ ├── tasks-table.tsx │ │ └── update-task-sheet.tsx │ ├── _lib │ │ ├── actions.ts │ │ ├── queries.ts │ │ ├── seeds.ts │ │ ├── utils.ts │ │ └── validations.ts │ ├── icon.png │ ├── layout.tsx │ ├── opengraph-image.png │ ├── page.tsx │ ├── robots.ts │ └── sitemap.ts ├── components │ ├── data-table │ │ ├── data-table-action-bar.tsx │ │ ├── data-table-advanced-toolbar.tsx │ │ ├── data-table-column-header.tsx │ │ ├── data-table-date-filter.tsx │ │ ├── data-table-faceted-filter.tsx │ │ ├── data-table-filter-list.tsx │ │ ├── data-table-filter-menu.tsx │ │ ├── data-table-pagination.tsx │ │ ├── data-table-range-filter.tsx │ │ ├── data-table-skeleton.tsx │ │ ├── data-table-slider-filter.tsx │ │ ├── data-table-sort-list.tsx │ │ ├── data-table-toolbar.tsx │ │ ├── data-table-view-options.tsx │ │ └── data-table.tsx │ ├── dynamic-container.tsx │ ├── icons.tsx │ ├── layouts │ │ ├── mode-toggle.tsx │ │ └── site-header.tsx │ ├── providers.tsx │ ├── shell.tsx │ ├── tailwind-indicator.tsx │ └── ui │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── checkbox.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── faceted.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── sortable.tsx │ │ ├── table.tsx │ │ ├── textarea.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ └── tooltip.tsx ├── config │ ├── data-table.ts │ ├── flag.ts │ └── site.ts ├── db │ ├── index.ts │ ├── migrate.ts │ ├── schema.ts │ ├── seed.ts │ └── utils.ts ├── env.js ├── hooks │ ├── use-callback-ref.ts │ ├── use-data-table.ts │ ├── use-debounced-callback.ts │ └── use-media-query.ts ├── lib │ ├── composition.ts │ ├── constants.ts │ ├── data-table.ts │ ├── export.ts │ ├── filter-columns.ts │ ├── fonts.ts │ ├── format.ts │ ├── handle-error.ts │ ├── id.ts │ ├── parsers.ts │ ├── unstable-cache.ts │ └── utils.ts ├── styles │ └── globals.css └── types │ ├── data-table.ts │ ├── doc.ts │ └── index.ts └── tsconfig.json /.cursorrules: -------------------------------------------------------------------------------- 1 | # Expert Guidelines 2 | 3 | You are an expert in TypeScript, Node.js, Next.js App Router, React, Shadcn UI, Radix UI and Tailwind. 4 | 5 | Code Style and Structure 6 | 7 | - Write concise, technical TypeScript code with accurate examples. 8 | - Use functional and declarative programming patterns; avoid classes. 9 | - Prefer iteration and modularization over code duplication. 10 | - Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). 11 | - Structure files: exported component, subcomponents, helpers, static content, types. 12 | - Use console.log({value}) instead of console.log(value) 13 | - Use onCallback instead of handleCallback 14 | 15 | Naming Conventions 16 | 17 | - Use lowercase with dashes for directories (e.g., components/auth-wizard). 18 | - Favor named exports for components. 19 | 20 | TypeScript Usage 21 | 22 | - Use TypeScript for all code; prefer interfaces over types. 23 | - Avoid enums; use maps instead. 24 | - Use functional components with TypeScript interfaces. 25 | 26 | Syntax and Formatting 27 | 28 | - Use the "function" keyword for pure functions. 29 | - Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements. 30 | - Use declarative JSX. 31 | 32 | UI and Styling 33 | 34 | - Use Shadcn UI, Radix, and Tailwind for components and styling. 35 | - Implement responsive design with Tailwind CSS; use a mobile-first approach. 36 | 37 | Performance Optimization 38 | 39 | - Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC). 40 | - Wrap client components in Suspense with fallback. 41 | - Use dynamic loading for non-critical components. 42 | - Optimize images: use WebP format, include size data, implement lazy loading. 43 | 44 | Key Conventions 45 | 46 | - Optimize Web Vitals (LCP, CLS, FID). 47 | - Limit 'use client': 48 | - Favor server components and Next.js SSR. 49 | - Use only for Web API access in small components. 50 | - Avoid for data fetching or state management. 51 | 52 | Follow Next.js docs for Data Fetching, Rendering, and Routing. 53 | 54 | --- 55 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Database 2 | DATABASE_URL="postgres://YOUR_POSTGRESQL_URL" 3 | 4 | ## Docker compose (optional) 5 | DB_HOST=localhost 6 | DB_USER=shadcntable 7 | DB_PASSWORD=securepassword 8 | DB_NAME=shadcntable 9 | DB_PORT=5432 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # This template is heavily inspired by the acme-corp and shadcn-ui/ui repositories. 2 | # See: https://github.com/juliusmarminge/acme-corp/blob/main/.github/ISSUE_TEMPLATE/bug_report.yml 3 | # See: https://github.com/shadcn-ui/ui/blob/main/.github/ISSUE_TEMPLATE/feature_request.yml 4 | 5 | name: Bug report 6 | description: Create a bug report to help us improve 7 | title: "[bug]: " 8 | labels: ["🐞❔ unconfirmed bug"] 9 | body: 10 | - type: textarea 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of the bug, as well as what you expected to happen when encountering it. 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: How to reproduce 19 | description: A step-by-step description of how to reproduce the bug. 20 | placeholder: | 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. See error 24 | validations: 25 | required: true 26 | - type: input 27 | attributes: 28 | label: Link to reproduction 29 | description: A link to a CodeSandbox or StackBlitz that includes a minimal reproduction of the problem. In rare cases when not applicable, you can link to a GitHub repository that we can easily run to recreate the issue. If a report is vague and does not have a reproduction, it will be closed without warning. 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Additional information 35 | description: Add any other information related to the bug here, screenshots if applicable. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # This template is heavily inspired by the shadcn-ui/ui repository. 2 | # See: https://github.com/shadcn-ui/ui/blob/main/.github/ISSUE_TEMPLATE/config.yml 3 | 4 | blank_issues_enabled: false 5 | contact_links: 6 | - name: General questions 7 | url: https://github.com/sadmann7/shadcn-table/discussions?category=general 8 | about: Please ask and answer questions here 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | # This template is heavily inspired by the shadcn-ui/ui repository. 2 | # See: https://github.com/shadcn-ui/ui/blob/main/.github/ISSUE_TEMPLATE/feature_request.yml 3 | 4 | name: "Feature request" 5 | description: Create a feature request for shadcn-table 6 | title: "[feat]: " 7 | labels: ["✨ enhancement"] 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: | 12 | ### Thanks for suggesting a feature request! Make sure to see if your feature request has already been suggested by searching through the existing issues. If you find a similar request, give it a thumbs up and add any additional context you have in the comments. 13 | 14 | - type: textarea 15 | id: feature-description 16 | attributes: 17 | label: Feature description 18 | description: Tell us about your feature request. 19 | placeholder: "I think this feature would be great because..." 20 | value: "Describe your feature request..." 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context about the feature here. 29 | placeholder: ex. screenshots, Stack Overflow links, forum links, etc. 30 | value: "Additional details here..." 31 | validations: 32 | required: false 33 | 34 | - type: checkboxes 35 | id: terms 36 | attributes: 37 | label: Before submitting 38 | description: By submitting this issue, you agree to follow our [Contributing Guidelines](https://github.com/sadmann7/shadcn-table/blob/main/CONTRIBUTING.md). 39 | options: 40 | - label: I've made research efforts and searched the documentation 41 | required: true 42 | - label: I've searched for existing issues and PRs 43 | required: true 44 | -------------------------------------------------------------------------------- /.github/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Workflow 2 | description: Composite action that sets up pnpm and installs dependencies 3 | runs: 4 | using: "composite" 5 | steps: 6 | - uses: actions/setup-node@v4 7 | with: 8 | node-version: 20.x 9 | 10 | - name: Setup pnpm 11 | uses: pnpm/action-setup@v4 12 | with: 13 | version: 10.9.0 14 | 15 | - run: pnpm install 16 | shell: bash 17 | -------------------------------------------------------------------------------- /.github/workflows/code-check.yml: -------------------------------------------------------------------------------- 1 | name: Code check 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | push: 7 | branches: ["main"] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 12 | 13 | env: 14 | FORCE_COLOR: 3 15 | 16 | jobs: 17 | format: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout repo 21 | uses: actions/checkout@v4 22 | - name: Setup pnpm 23 | uses: pnpm/action-setup@v4 24 | with: 25 | version: 10.9.0 26 | - uses: ./.github/setup 27 | 28 | - name: Check formatting 29 | run: pnpm lint:fix 30 | 31 | lint: 32 | runs-on: ubuntu-latest 33 | name: Lint 34 | steps: 35 | - name: Checkout repo 36 | uses: actions/checkout@v4 37 | - name: Setup pnpm 38 | uses: pnpm/action-setup@v4 39 | with: 40 | version: 10.9.0 41 | - uses: ./.github/setup 42 | 43 | - run: pnpm lint 44 | 45 | typecheck: 46 | runs-on: ubuntu-latest 47 | name: Typecheck 48 | steps: 49 | - name: Checkout repo 50 | uses: actions/checkout@v4 51 | - name: Setup pnpm 52 | uses: pnpm/action-setup@v4 53 | with: 54 | version: 10.9.0 55 | - uses: ./.github/setup 56 | 57 | - run: pnpm typecheck 58 | -------------------------------------------------------------------------------- /.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 | docker-data 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | next-env.d.ts 20 | 21 | # production 22 | /build 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | .pnpm-debug.log* 33 | 34 | # local env files 35 | # 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 36 | .env 37 | .env*.local 38 | 39 | # vercel 40 | .vercel 41 | 42 | # typescript 43 | *.tsbuildinfo 44 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "quickfix.biome": "explicit", 4 | "source.organizeImports.biome": "explicit" 5 | }, 6 | "editor.formatOnSave": true, 7 | "editor.defaultFormatter": "biomejs.biome", 8 | "tailwindCSS.experimental.classRegex": [ 9 | ["clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 10 | ["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"], 11 | ["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Thank you for investing your time in contributing to our project! Any contribution you make will be reflected on [shadcn-table](<[table.sadmn.com](https://github.com/sadmann7/shadcn-table)>). 4 | 5 | In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR. 6 | 7 | ## Getting started 8 | 9 | ### Fork the repository 10 | 11 | Fork the project [on GitHub](https://github.com/sadmann7/shadcn-table) 12 | 13 | ### Clone the project 14 | 15 | Clone your fork locally. Do not clone the original repository unless you plan to become a long-term contributor and have been given permission to do so. 16 | 17 | ```shell 18 | git clone https://github.com/sadmann7/shadcn-table 19 | cd shadcn-table 20 | ``` 21 | 22 | ### Install dependencies 23 | 24 | Install the project dependencies: 25 | 26 | ```shell 27 | pnpm install 28 | ``` 29 | 30 | ### Create a branch 31 | 32 | Create and check out your feature branch: 33 | 34 | ```shell 35 | git checkout -b my-new-feature 36 | ``` 37 | 38 | ### Make changes locally 39 | 40 | Make your changes to the codebase. See the [development guide](contributing/development.md) for more information. 41 | 42 | ### Commit your changes 43 | 44 | Commit your changes: 45 | 46 | ```shell 47 | git commit -m 'Add some feature' 48 | ``` 49 | 50 | ### Push your changes 51 | 52 | Push your changes to your fork: 53 | 54 | ```shell 55 | git push -u origin my-new-feature 56 | ``` 57 | 58 | ### Create a pull request 59 | 60 | When you're finished with the changes, create a pull request, also known as a PR. 61 | 62 | - Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request. 63 | 64 | ### Issues 65 | 66 | #### Create a new issue 67 | 68 | If you spot a problem in the codebase that you believe needs to be fixed, or you have an idea for a new feature, take a look at the [Issues](https://github.com/sadmann7/shadcn-table/issues). 69 | 70 | If you can't find an open issue addressing the problem, [open a new one](https://github.com/sadmann7/shadcn-table/issues/new). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring. 71 | 72 | #### Solve an issue 73 | 74 | Scan through our [existing issues](https://github.com/sadmann7/shadcn-table/issues) to find one that interests you. You can narrow down the search using `labels` and `projects` to find issues that need attention. 75 | 76 | Then, fork the repository, create a branch, and make your changes. 77 | 78 | Finally, open a pull request with the changes. 79 | 80 | ### Your PR is merged 81 | 82 | Congratulations :tada::tada: The GitHub team thanks you :sparkles:. 83 | 84 | Once your PR is merged, your contributions will be publicly visible on the [shadcn-table](https://github.com/sadmann7/shadcn-table). 85 | 86 | ### Credits 87 | 88 | This Contributing Guide is adapted from [GitHub docs contributing guide](https://github.com/github/docs/blob/main/CONTRIBUTING.md?plain=1). 89 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 Sadman Sakib 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 | # [Shadcn Table](https://tablecn.com) 2 | 3 | This is a shadcn table component with server-side sorting, filtering, and pagination. It is bootstrapped with `create-t3-app`. 4 | 5 | [![Shadcn Table](./public/images/screenshot.png)](https://tablecn.com) 6 | 7 | ## Documentation 8 | 9 | See the [documentation](https://diceui.com/docs/components/data-table) to get started. 10 | 11 | ## Tech Stack 12 | 13 | - **Framework:** [Next.js](https://nextjs.org) 14 | - **Styling:** [Tailwind CSS](https://tailwindcss.com) 15 | - **UI Components:** [shadcn/ui](https://ui.shadcn.com) 16 | - **Table package:** [TanStack/react-table](https://tanstack.com/table/latest) 17 | - **Database:** [Neon](https://neon.tech) 18 | - **ORM:** [Drizzle ORM](https://orm.drizzle.team) 19 | - **Validation:** [Zod](https://zod.dev) 20 | 21 | ## Features 22 | 23 | - [x] Server-side pagination, sorting, and filtering 24 | - [x] Customizable columns 25 | - [x] Auto generated filters from column definitions 26 | - [x] Dynamic `Data-Table-Toolbar` with search, filters, and actions 27 | - [x] `Notion/Airtable` like advanced filtering 28 | - [x] `Linear` like filter menu for command palette filtering 29 | - [x] Action bar on row selection 30 | 31 | ## Running Locally 32 | 33 | 1. Clone the repository 34 | 35 | ```bash 36 | git clone https://github.com/sadmann7/shadcn-table 37 | ``` 38 | 39 | 2. Install dependencies using pnpm 40 | 41 | ```bash 42 | pnpm install 43 | ``` 44 | 45 | 3. Copy the `.env.example` to `.env` and update the variables. 46 | 47 | ```bash 48 | cp .env.example .env 49 | ``` 50 | 51 | 4. (Optional) Run database using docker-compose.yml file 52 | 53 | ```bash 54 | docker compose up 55 | ``` 56 | 57 | 5. Push the database schema 58 | 59 | ```bash 60 | pnpm run db:push 61 | ``` 62 | 63 | 6. Seed the database 64 | 65 | ```bash 66 | pnpm run db:seed 67 | ``` 68 | 69 | 7. Start the development server 70 | 71 | ```bash 72 | pnpm run dev 73 | ``` 74 | 75 | ## How do I deploy this? 76 | 77 | Follow the deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. 78 | 79 | ## Credits 80 | 81 | - [shadcn/ui](https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/examples/tasks) - For the initial implementation of the data table. 82 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "formatter": { 7 | "enabled": true, 8 | "indentWidth": 2, 9 | "indentStyle": "space", 10 | "ignore": [ 11 | "**/node_modules", 12 | "**/dist", 13 | "**/build", 14 | "**/public", 15 | "**/.turbo", 16 | "**/.next" 17 | ] 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true, 23 | "a11y": { 24 | "noSvgWithoutTitle": "off", 25 | "useButtonType": "off", 26 | "useAltText": "off", 27 | "useKeyWithClickEvents": "off", 28 | "useSemanticElements": "off", 29 | "noLabelWithoutControl": "off" 30 | }, 31 | "correctness": { 32 | "noUnusedVariables": "warn", 33 | "useExhaustiveDependencies": "warn" 34 | }, 35 | "complexity": { 36 | "noBannedTypes": "off" 37 | }, 38 | "style": { 39 | "useImportType": "warn" 40 | }, 41 | "security": { 42 | "noDangerouslySetInnerHtml": "off" 43 | }, 44 | "suspicious": { 45 | "noAssignInExpressions": "off", 46 | "noArrayIndexKey": "off" 47 | }, 48 | "nursery": { 49 | "useSortedClasses": { 50 | "level": "warn", 51 | "options": { 52 | "attributes": ["classList"], 53 | "functions": ["clsx", "cva", "tw"] 54 | } 55 | } 56 | } 57 | }, 58 | "ignore": [ 59 | "**/node_modules", 60 | "**/dist", 61 | "**/build", 62 | "**/public", 63 | "**/.turbo", 64 | "**/.next" 65 | ] 66 | }, 67 | "overrides": [ 68 | { 69 | "include": ["**/*.test.ts"] 70 | } 71 | ], 72 | "vcs": { 73 | "enabled": true, 74 | "clientKind": "git", 75 | "useIgnoreFile": true 76 | }, 77 | "files": { 78 | "ignore": [ 79 | "**/node_modules", 80 | "**/dist", 81 | "**/build", 82 | "**/public", 83 | "**/.turbo", 84 | "**/.next" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "iconLibrary": "lucide", 5 | "rsc": true, 6 | "tsx": true, 7 | "tailwind": { 8 | "config": "tailwind.config.ts", 9 | "css": "src/styles/globals.css", 10 | "baseColor": "zinc", 11 | "cssVariables": true 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | shadcn-table: 3 | image: postgres:17.4 4 | restart: always 5 | container_name: shadcn-table 6 | ports: 7 | - ${DB_PORT}:5432 8 | environment: 9 | - POSTGRES_PASSWORD=${DB_PASSWORD} 10 | - POSTGRES_USER=${DB_USER} 11 | - POSTGRES_DB=${DB_NAME} 12 | volumes: 13 | - ./docker-data/db:/var/lib/postgresql/data 14 | 15 | volumes: 16 | postgres: 17 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@/env.js"; 2 | import type { Config } from "drizzle-kit"; 3 | 4 | import { databasePrefix } from "@/lib/constants"; 5 | 6 | export default { 7 | schema: "./src/db/schema.ts", 8 | dialect: "postgresql", 9 | out: "./drizzle", 10 | dbCredentials: { 11 | url: env.DATABASE_URL, 12 | }, 13 | tablesFilter: [`${databasePrefix}_*`], 14 | } satisfies Config; 15 | -------------------------------------------------------------------------------- /drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "mysql", 4 | "entries": [] 5 | } 6 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | /** 4 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 5 | * for Docker builds. 6 | */ 7 | // Import environment validation synchronously 8 | import "./src/env.js"; 9 | 10 | const nextConfig: NextConfig = { 11 | // Already doing linting and typechecking as separate tasks in CI 12 | eslint: { ignoreDuringBuilds: true }, 13 | typescript: { ignoreBuildErrors: true }, 14 | }; 15 | 16 | export default nextConfig; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-table", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "clean": "rimraf --glob **/node_modules **/dist **/.next pnpm-lock.yaml **/.tsbuildinfo", 8 | "build": "next build", 9 | "dev": "next dev", 10 | "start": "next start", 11 | "lint": "biome check .", 12 | "lint:fix": "biome check . --write", 13 | "typecheck": "tsc --noEmit", 14 | "check": "pnpm lint && pnpm typecheck", 15 | "shadcn": "pnpm dlx shadcn@latest", 16 | "build:registry": "shadcn build", 17 | "db:generate": "dotenv drizzle-kit generate", 18 | "db:introspect": "dotenv drizzle-kit introspect", 19 | "db:push": "dotenv drizzle-kit push", 20 | "db:migrate": "dotenv tsx src/db/migrate.ts", 21 | "db:drop-migration": "drizzle-kit drop", 22 | "db:seed": "dotenv tsx src/db/seed.ts", 23 | "db:studio": "dotenv drizzle-kit studio" 24 | }, 25 | "dependencies": { 26 | "@dnd-kit/core": "^6.3.1", 27 | "@dnd-kit/modifiers": "^9.0.0", 28 | "@dnd-kit/sortable": "^10.0.0", 29 | "@dnd-kit/utilities": "^3.2.2", 30 | "@hookform/resolvers": "^5.0.1", 31 | "@radix-ui/react-checkbox": "^1.2.3", 32 | "@radix-ui/react-dialog": "^1.1.11", 33 | "@radix-ui/react-dropdown-menu": "^2.1.12", 34 | "@radix-ui/react-label": "^2.1.4", 35 | "@radix-ui/react-popover": "^1.1.11", 36 | "@radix-ui/react-portal": "^1.1.6", 37 | "@radix-ui/react-select": "^2.2.2", 38 | "@radix-ui/react-separator": "^1.1.4", 39 | "@radix-ui/react-slider": "^1.3.2", 40 | "@radix-ui/react-slot": "^1.2.0", 41 | "@radix-ui/react-toggle": "^1.1.6", 42 | "@radix-ui/react-toggle-group": "^1.1.7", 43 | "@radix-ui/react-tooltip": "^1.2.4", 44 | "@t3-oss/env-nextjs": "^0.13.0", 45 | "@tanstack/react-table": "^8.21.3", 46 | "class-variance-authority": "^0.7.1", 47 | "clsx": "^2.1.1", 48 | "cmdk": "^1.1.1", 49 | "date-fns": "^4.1.0", 50 | "drizzle-orm": "^0.43.1", 51 | "export-to-csv": "^1.4.0", 52 | "geist": "^1.3.1", 53 | "lucide-react": "^0.503.0", 54 | "motion": "^12.9.2", 55 | "nanoid": "^5.1.5", 56 | "next": "^15.3.1", 57 | "next-themes": "^0.4.6", 58 | "nuqs": "^2.4.3", 59 | "postgres": "^3.4.5", 60 | "react": "^19.1.0", 61 | "react-day-picker": "8.10.1", 62 | "react-dom": "^19.1.0", 63 | "react-hook-form": "^7.56.1", 64 | "server-only": "^0.0.1", 65 | "shadcn": "2.5.0", 66 | "sonner": "^2.0.3", 67 | "tailwind-merge": "^3.2.0", 68 | "vaul": "^1.1.2", 69 | "zod": "^3.24.3" 70 | }, 71 | "devDependencies": { 72 | "@biomejs/biome": "^1.9.4", 73 | "@faker-js/faker": "^9.7.0", 74 | "@tailwindcss/postcss": "^4.1.4", 75 | "@total-typescript/ts-reset": "^0.6.1", 76 | "@types/node": "^22.15.2", 77 | "@types/react": "^19.1.2", 78 | "@types/react-dom": "^19.1.2", 79 | "dotenv-cli": "^8.0.0", 80 | "drizzle-kit": "^0.31.0", 81 | "pg": "^8.15.5", 82 | "postcss": "^8.5.3", 83 | "rimraf": "^6.0.1", 84 | "tailwindcss": "^4.1.4", 85 | "tsx": "^4.19.3", 86 | "tw-animate-css": "^1.2.8", 87 | "typescript": "^5.8.3" 88 | }, 89 | "ct3aMetadata": { 90 | "initVersion": "7.23.1" 91 | }, 92 | "packageManager": "pnpm@10.9.0" 93 | } 94 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /public/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadmann7/shadcn-table/67bfe74c0454c7f657aa22c3d39a4926d6ebaf37/public/images/screenshot.png -------------------------------------------------------------------------------- /public/r/data-table-action-bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json", 3 | "name": "data-table-action-bar", 4 | "type": "registry:component", 5 | "title": "Data Table Action Bar", 6 | "description": "A action bar component for the data table", 7 | "dependencies": [ 8 | "@tanstack/react-table", 9 | "lucide-react", 10 | "motion" 11 | ], 12 | "registryDependencies": [ 13 | "button", 14 | "tooltip" 15 | ], 16 | "files": [ 17 | { 18 | "path": "src/components/data-table/data-table-action-bar.tsx", 19 | "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport type { Table } from \"@tanstack/react-table\";\nimport { Loader, X } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\n\ninterface DataTableActionBarProps\n extends React.ComponentProps {\n table: Table;\n visible?: boolean;\n container?: Element | DocumentFragment | null;\n}\n\nfunction DataTableActionBar({\n table,\n visible: visibleProp,\n container: containerProp,\n children,\n className,\n ...props\n}: DataTableActionBarProps) {\n const [mounted, setMounted] = React.useState(false);\n\n React.useLayoutEffect(() => {\n setMounted(true);\n }, []);\n\n React.useEffect(() => {\n function onKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n table.toggleAllRowsSelected(false);\n }\n }\n\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [table]);\n\n const container =\n containerProp ?? (mounted ? globalThis.document?.body : null);\n\n if (!container) return null;\n\n const visible =\n visibleProp ?? table.getFilteredSelectedRowModel().rows.length > 0;\n\n return ReactDOM.createPortal(\n \n {visible && (\n \n {children}\n \n )}\n ,\n container,\n );\n}\n\ninterface DataTableActionBarActionProps\n extends React.ComponentProps {\n tooltip?: string;\n isPending?: boolean;\n}\n\nfunction DataTableActionBarAction({\n size = \"sm\",\n tooltip,\n isPending,\n disabled,\n className,\n children,\n ...props\n}: DataTableActionBarActionProps) {\n const trigger = (\n svg]:size-3.5\",\n size === \"icon\" ? \"size-7\" : \"h-7\",\n className,\n )}\n disabled={disabled || isPending}\n {...props}\n >\n {isPending ? : children}\n \n );\n\n if (!tooltip) return trigger;\n\n return (\n \n {trigger}\n span]:hidden\"\n >\n

{tooltip}

\n \n
\n );\n}\n\ninterface DataTableActionBarSelectionProps {\n table: Table;\n}\n\nfunction DataTableActionBarSelection({\n table,\n}: DataTableActionBarSelectionProps) {\n const onClearSelection = React.useCallback(() => {\n table.toggleAllRowsSelected(false);\n }, [table]);\n\n return (\n
\n \n {table.getFilteredSelectedRowModel().rows.length} selected\n \n \n \n \n \n \n \n \n span]:hidden\"\n >\n

Clear selection

\n \n \n Esc\n \n \n \n
\n
\n );\n}\n\nexport {\n DataTableActionBar,\n DataTableActionBarAction,\n DataTableActionBarSelection,\n};\n", 20 | "type": "registry:component", 21 | "target": "src/components/data-table/data-table-action-bar.tsx" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shadcn Table", 3 | "short_name": "Shadcn Table", 4 | "icons": [ 5 | { 6 | "src": "/icon.png", 7 | "sizes": "32x32", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "automerge": true, 5 | "enabled": false 6 | } 7 | -------------------------------------------------------------------------------- /reset.d.ts: -------------------------------------------------------------------------------- 1 | import "@total-typescript/ts-reset"; 2 | -------------------------------------------------------------------------------- /src/app/_components/create-task-sheet.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { zodResolver } from "@hookform/resolvers/zod"; 4 | import { Loader, Plus } from "lucide-react"; 5 | import * as React from "react"; 6 | import { useForm } from "react-hook-form"; 7 | import { toast } from "sonner"; 8 | 9 | import { Button } from "@/components/ui/button"; 10 | import { 11 | Sheet, 12 | SheetClose, 13 | SheetContent, 14 | SheetDescription, 15 | SheetFooter, 16 | SheetHeader, 17 | SheetTitle, 18 | SheetTrigger, 19 | } from "@/components/ui/sheet"; 20 | 21 | import { createTask } from "../_lib/actions"; 22 | import type { CreateTaskSchema } from "../_lib/validations"; 23 | import { createTaskSchema } from "../_lib/validations"; 24 | import { TaskForm } from "./task-form"; 25 | 26 | export function CreateTaskSheet() { 27 | const [open, setOpen] = React.useState(false); 28 | const [isPending, startTransition] = React.useTransition(); 29 | 30 | const form = useForm({ 31 | resolver: zodResolver(createTaskSchema), 32 | }); 33 | 34 | function onSubmit(input: CreateTaskSchema) { 35 | startTransition(async () => { 36 | const { error } = await createTask(input); 37 | 38 | if (error) { 39 | toast.error(error); 40 | return; 41 | } 42 | 43 | form.reset(); 44 | setOpen(false); 45 | toast.success("Task created"); 46 | }); 47 | } 48 | 49 | return ( 50 | 51 | 52 | 56 | 57 | 58 | 59 | Create task 60 | 61 | Fill in the details below to create a new task 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 75 | 76 | 77 | 78 | 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/app/_components/delete-tasks-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { Task } from "@/db/schema"; 4 | import type { Row } from "@tanstack/react-table"; 5 | import { Loader, Trash } from "lucide-react"; 6 | import * as React from "react"; 7 | import { toast } from "sonner"; 8 | 9 | import { Button } from "@/components/ui/button"; 10 | import { 11 | Dialog, 12 | DialogClose, 13 | DialogContent, 14 | DialogDescription, 15 | DialogFooter, 16 | DialogHeader, 17 | DialogTitle, 18 | DialogTrigger, 19 | } from "@/components/ui/dialog"; 20 | import { 21 | Drawer, 22 | DrawerClose, 23 | DrawerContent, 24 | DrawerDescription, 25 | DrawerFooter, 26 | DrawerHeader, 27 | DrawerTitle, 28 | DrawerTrigger, 29 | } from "@/components/ui/drawer"; 30 | import { useMediaQuery } from "@/hooks/use-media-query"; 31 | 32 | import { deleteTasks } from "../_lib/actions"; 33 | 34 | interface DeleteTasksDialogProps 35 | extends React.ComponentPropsWithoutRef { 36 | tasks: Row["original"][]; 37 | showTrigger?: boolean; 38 | onSuccess?: () => void; 39 | } 40 | 41 | export function DeleteTasksDialog({ 42 | tasks, 43 | showTrigger = true, 44 | onSuccess, 45 | ...props 46 | }: DeleteTasksDialogProps) { 47 | const [isDeletePending, startDeleteTransition] = React.useTransition(); 48 | const isDesktop = useMediaQuery("(min-width: 640px)"); 49 | 50 | function onDelete() { 51 | startDeleteTransition(async () => { 52 | const { error } = await deleteTasks({ 53 | ids: tasks.map((task) => task.id), 54 | }); 55 | 56 | if (error) { 57 | toast.error(error); 58 | return; 59 | } 60 | 61 | props.onOpenChange?.(false); 62 | toast.success("Tasks deleted"); 63 | onSuccess?.(); 64 | }); 65 | } 66 | 67 | if (isDesktop) { 68 | return ( 69 | 70 | {showTrigger ? ( 71 | 72 | 76 | 77 | ) : null} 78 | 79 | 80 | Are you absolutely sure? 81 | 82 | This action cannot be undone. This will permanently delete your{" "} 83 | {tasks.length} 84 | {tasks.length === 1 ? " task" : " tasks"} from our servers. 85 | 86 | 87 | 88 | 89 | 90 | 91 | 105 | 106 | 107 | 108 | ); 109 | } 110 | 111 | return ( 112 | 113 | {showTrigger ? ( 114 | 115 | 119 | 120 | ) : null} 121 | 122 | 123 | Are you absolutely sure? 124 | 125 | This action cannot be undone. This will permanently delete your{" "} 126 | {tasks.length} 127 | {tasks.length === 1 ? " task" : " tasks"} from our servers. 128 | 129 | 130 | 131 | 132 | 133 | 134 | 145 | 146 | 147 | 148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /src/app/_components/feature-flags-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useQueryState } from "nuqs"; 4 | import * as React from "react"; 5 | 6 | import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; 7 | import { 8 | Tooltip, 9 | TooltipContent, 10 | TooltipTrigger, 11 | } from "@/components/ui/tooltip"; 12 | import { type FlagConfig, flagConfig } from "@/config/flag"; 13 | 14 | type FilterFlag = FlagConfig["featureFlags"][number]["value"]; 15 | 16 | interface FeatureFlagsContextValue { 17 | filterFlag: FilterFlag; 18 | enableAdvancedFilter: boolean; 19 | } 20 | 21 | const FeatureFlagsContext = 22 | React.createContext(null); 23 | 24 | export function useFeatureFlags() { 25 | const context = React.useContext(FeatureFlagsContext); 26 | if (!context) { 27 | throw new Error( 28 | "useFeatureFlags must be used within a FeatureFlagsProvider", 29 | ); 30 | } 31 | return context; 32 | } 33 | 34 | interface FeatureFlagsProviderProps { 35 | children: React.ReactNode; 36 | } 37 | 38 | export function FeatureFlagsProvider({ children }: FeatureFlagsProviderProps) { 39 | const [filterFlag, setFilterFlag] = useQueryState( 40 | "filterFlag", 41 | { 42 | parse: (value) => { 43 | if (!value) return null; 44 | const validValues = flagConfig.featureFlags.map((flag) => flag.value); 45 | return validValues.includes(value as FilterFlag) 46 | ? (value as FilterFlag) 47 | : null; 48 | }, 49 | serialize: (value) => value ?? "", 50 | defaultValue: null, 51 | clearOnDefault: true, 52 | shallow: false, 53 | eq: (a, b) => (!a && !b) || a === b, 54 | }, 55 | ); 56 | 57 | const onFilterFlagChange = React.useCallback( 58 | (value: FilterFlag) => { 59 | setFilterFlag(value); 60 | }, 61 | [setFilterFlag], 62 | ); 63 | 64 | const contextValue = React.useMemo( 65 | () => ({ 66 | filterFlag, 67 | enableAdvancedFilter: 68 | filterFlag === "advancedFilters" || filterFlag === "commandFilters", 69 | }), 70 | [filterFlag], 71 | ); 72 | 73 | return ( 74 | 75 |
76 | 84 | {flagConfig.featureFlags.map((flag) => ( 85 | 86 | 91 | 92 | 93 | {flag.label} 94 | 95 | 96 | 102 |
{flag.tooltipTitle}
103 |

104 | {flag.tooltipDescription} 105 |

106 |
107 |
108 | ))} 109 |
110 |
111 | {children} 112 |
113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /src/app/_components/task-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { tasks } from "@/db/schema"; 4 | import type * as React from "react"; 5 | import type { FieldPath, FieldValues, UseFormReturn } from "react-hook-form"; 6 | 7 | import { 8 | Form, 9 | FormControl, 10 | FormField, 11 | FormItem, 12 | FormLabel, 13 | FormMessage, 14 | } from "@/components/ui/form"; 15 | import { Input } from "@/components/ui/input"; 16 | import { 17 | Select, 18 | SelectContent, 19 | SelectGroup, 20 | SelectItem, 21 | SelectTrigger, 22 | SelectValue, 23 | } from "@/components/ui/select"; 24 | import { Textarea } from "@/components/ui/textarea"; 25 | 26 | interface TaskFormProps 27 | extends Omit, "onSubmit"> { 28 | children: React.ReactNode; 29 | form: UseFormReturn; 30 | onSubmit: (data: T) => void; 31 | } 32 | 33 | export function TaskForm({ 34 | form, 35 | onSubmit, 36 | children, 37 | }: TaskFormProps) { 38 | return ( 39 |
40 | 44 | } 47 | render={({ field }) => ( 48 | 49 | Title 50 | 51 |