├── .gitignore ├── README.md ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.tsx ├── assets │ └── react.svg ├── components │ ├── columns.tsx │ ├── data-table-column-header.tsx │ ├── data-table-filter.tsx │ ├── data-table-pagination.tsx │ ├── data-table-row-actions.tsx │ ├── data-table-toolbar.tsx │ ├── data-table-view-options.tsx │ ├── data-table.tsx │ └── ui │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── popover.tsx │ │ ├── select.tsx │ │ └── table.tsx ├── data │ ├── data.tsx │ ├── schema.ts │ ├── seed.ts │ └── tasks.ts ├── index.css ├── lib │ └── utils.ts ├── main.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShadCN Filter - Linear Style 2 | 3 | The important code is here: https://github.com/chrisdadev13/shadcn-linear-filter/blob/main/src/components/data-table-filter.tsx 4 | 5 | And here: https://github.com/chrisdadev13/shadcn-linear-filter/blob/main/src/components/columns.tsx 6 | 7 | https://github.com/user-attachments/assets/0a2059ba-1104-44e9-aebe-9d2cd1825f6f 8 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-linear-filter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@faker-js/faker": "^9.2.0", 14 | "@radix-ui/react-checkbox": "^1.1.2", 15 | "@radix-ui/react-dialog": "^1.1.2", 16 | "@radix-ui/react-dropdown-menu": "^2.1.2", 17 | "@radix-ui/react-popover": "^1.1.2", 18 | "@radix-ui/react-select": "^2.1.2", 19 | "@radix-ui/react-slot": "^1.1.0", 20 | "@tanstack/react-table": "^8.20.5", 21 | "class-variance-authority": "^0.7.1", 22 | "clsx": "^2.1.1", 23 | "cmdk": "^1.0.0", 24 | "lucide-react": "^0.462.0", 25 | "react": "^18.3.1", 26 | "react-dom": "^18.3.1", 27 | "tailwind-merge": "^2.5.5", 28 | "tailwindcss-animate": "^1.0.7", 29 | "zod": "^3.23.8" 30 | }, 31 | "devDependencies": { 32 | "@eslint/js": "^9.15.0", 33 | "@types/node": "^22.10.1", 34 | "@types/react": "^18.3.12", 35 | "@types/react-dom": "^18.3.1", 36 | "@vitejs/plugin-react": "^4.3.4", 37 | "autoprefixer": "^10.4.20", 38 | "eslint": "^9.15.0", 39 | "eslint-plugin-react-hooks": "^5.0.0", 40 | "eslint-plugin-react-refresh": "^0.4.14", 41 | "globals": "^15.12.0", 42 | "postcss": "^8.4.49", 43 | "tailwindcss": "^3.4.15", 44 | "typescript": "~5.6.2", 45 | "typescript-eslint": "^8.15.0", 46 | "vite": "^6.0.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { columns } from "./components/columns"; 2 | import { DataTable } from "./components/data-table"; 3 | import { tasks } from "./data/tasks"; 4 | 5 | export default function App() { 6 | return ( 7 | <> 8 |
9 |
10 |
11 |
12 |

Welcome back!

13 |

14 | Here's a list of your tasks for this month! 15 |

16 |
17 |
18 | 19 |
20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/columns.tsx: -------------------------------------------------------------------------------- 1 | import { ColumnDef } from "@tanstack/react-table"; 2 | 3 | import { Badge } from "./ui/badge"; 4 | import { Checkbox } from "./ui/checkbox"; 5 | 6 | import { labels, priorities, statuses } from "../data/data"; 7 | import { Task } from "../data/schema"; 8 | import { DataTableColumnHeader } from "./data-table-column-header"; 9 | import { DataTableRowActions } from "./data-table-row-actions"; 10 | 11 | export const columns: ColumnDef[] = [ 12 | { 13 | id: "select", 14 | header: ({ table }) => ( 15 | table.toggleAllPageRowsSelected(!!value)} 21 | aria-label="Select all" 22 | className="translate-y-[2px]" 23 | /> 24 | ), 25 | cell: ({ row }) => ( 26 | row.toggleSelected(!!value)} 29 | aria-label="Select row" 30 | className="translate-y-[2px]" 31 | /> 32 | ), 33 | enableSorting: false, 34 | enableHiding: false, 35 | }, 36 | { 37 | accessorKey: "id", 38 | header: ({ column }) => ( 39 | 40 | ), 41 | cell: ({ row }) =>
{row.getValue("id")}
, 42 | enableSorting: false, 43 | enableHiding: false, 44 | }, 45 | { 46 | accessorKey: "title", 47 | header: ({ column }) => ( 48 | 49 | ), 50 | cell: ({ row }) => { 51 | const label = labels.find((label) => label.value === row.original.label); 52 | 53 | return ( 54 |
55 | {label && {label.label}} 56 | 57 | {row.getValue("title")} 58 | 59 |
60 | ); 61 | }, 62 | }, 63 | { 64 | accessorKey: "status", 65 | header: ({ column }) => ( 66 | 67 | ), 68 | cell: ({ row }) => { 69 | const status = statuses.find( 70 | (status) => status.value === row.getValue("status"), 71 | ); 72 | 73 | if (!status) { 74 | return null; 75 | } 76 | 77 | return ( 78 |
79 | {status.icon && ( 80 | 81 | )} 82 | {status.label} 83 |
84 | ); 85 | }, 86 | filterFn: (row, columnId, filterValue) => { 87 | if (!filterValue || filterValue.length === 0) return true; 88 | 89 | const rowValue = row.getValue(columnId)?.toString().toLowerCase() ?? ""; 90 | 91 | const isFilters = filterValue.filter( 92 | (f: { condition: string; value: string }) => f.condition === "is", 93 | ); 94 | const isNotFilters = filterValue.filter( 95 | (f: { condition: string; value: string }) => f.condition === "is not", 96 | ); 97 | const containsFilters = filterValue.filter( 98 | (f: { condition: string; value: string }) => f.condition === "contains", 99 | ); 100 | const doesNotContainFilters = filterValue.filter( 101 | (f: { condition: string; value: string }) => 102 | f.condition === "does not contain", 103 | ); 104 | 105 | const passesIsCondition = 106 | isFilters.length === 0 || 107 | isFilters.some( 108 | (filter: { condition: string; value: string }) => 109 | rowValue === filter.value.toLowerCase(), 110 | ); 111 | 112 | const passesIsNotCondition = 113 | isNotFilters.length === 0 || 114 | !isNotFilters.some( 115 | (filter: { condition: string; value: string }) => 116 | rowValue === filter.value.toLowerCase(), 117 | ); 118 | 119 | const passesContainsCondition = 120 | containsFilters.length === 0 || 121 | containsFilters.some((filter: { condition: string; value: string }) => 122 | rowValue.includes(filter.value.toLowerCase()), 123 | ); 124 | 125 | const passesDoesNotContainCondition = 126 | doesNotContainFilters.length === 0 || 127 | !doesNotContainFilters.some( 128 | (filter: { condition: string; value: string }) => 129 | rowValue.includes(filter.value.toLowerCase()), 130 | ); 131 | 132 | return ( 133 | passesIsCondition && 134 | passesIsNotCondition && 135 | passesContainsCondition && 136 | passesDoesNotContainCondition 137 | ); 138 | }, 139 | }, 140 | { 141 | accessorKey: "priority", 142 | header: ({ column }) => ( 143 | 144 | ), 145 | cell: ({ row }) => { 146 | const priority = priorities.find( 147 | (priority) => priority.value === row.getValue("priority"), 148 | ); 149 | 150 | if (!priority) { 151 | return null; 152 | } 153 | 154 | return ( 155 |
156 | {priority.icon && ( 157 | 158 | )} 159 | {priority.label} 160 |
161 | ); 162 | }, 163 | filterFn: (row, columnId, filterValue) => { 164 | if (!filterValue || filterValue.length === 0) return true; 165 | 166 | const rowValue = row.getValue(columnId)?.toString().toLowerCase() ?? ""; 167 | 168 | const isFilters = filterValue.filter( 169 | (f: { condition: string; value: string }) => f.condition === "is", 170 | ); 171 | const isNotFilters = filterValue.filter( 172 | (f: { condition: string; value: string }) => f.condition === "is not", 173 | ); 174 | const containsFilters = filterValue.filter( 175 | (f: { condition: string; value: string }) => f.condition === "contains", 176 | ); 177 | const doesNotContainFilters = filterValue.filter( 178 | (f: { condition: string; value: string }) => 179 | f.condition === "does not contain", 180 | ); 181 | 182 | const passesIsCondition = 183 | isFilters.length === 0 || 184 | isFilters.some( 185 | (filter: { condition: string; value: string }) => 186 | rowValue === filter.value.toLowerCase(), 187 | ); 188 | 189 | const passesIsNotCondition = 190 | isNotFilters.length === 0 || 191 | !isNotFilters.some( 192 | (filter: { condition: string; value: string }) => 193 | rowValue === filter.value.toLowerCase(), 194 | ); 195 | 196 | const passesContainsCondition = 197 | containsFilters.length === 0 || 198 | containsFilters.some((filter: { condition: string; value: string }) => 199 | rowValue.includes(filter.value.toLowerCase()), 200 | ); 201 | 202 | const passesDoesNotContainCondition = 203 | doesNotContainFilters.length === 0 || 204 | !doesNotContainFilters.some( 205 | (filter: { condition: string; value: string }) => 206 | rowValue.includes(filter.value.toLowerCase()), 207 | ); 208 | 209 | return ( 210 | passesIsCondition && 211 | passesIsNotCondition && 212 | passesContainsCondition && 213 | passesDoesNotContainCondition 214 | ); 215 | }, 216 | }, 217 | { 218 | id: "actions", 219 | cell: ({ row }) => , 220 | }, 221 | ]; 222 | -------------------------------------------------------------------------------- /src/components/data-table-column-header.tsx: -------------------------------------------------------------------------------- 1 | import { Column } from "@tanstack/react-table"; 2 | import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"; 3 | 4 | import { cn } from "../lib/utils"; 5 | import { Button } from "./ui/button"; 6 | import { 7 | DropdownMenu, 8 | DropdownMenuContent, 9 | DropdownMenuItem, 10 | DropdownMenuSeparator, 11 | DropdownMenuTrigger, 12 | } from "./ui/dropdown-menu"; 13 | 14 | interface DataTableColumnHeaderProps 15 | extends React.HTMLAttributes { 16 | column: Column; 17 | title: string; 18 | } 19 | 20 | export function DataTableColumnHeader({ 21 | column, 22 | title, 23 | className, 24 | }: DataTableColumnHeaderProps) { 25 | if (!column.getCanSort()) { 26 | return
{title}
; 27 | } 28 | 29 | return ( 30 |
31 | 32 | 33 | 47 | 48 | 49 | column.toggleSorting(false)}> 50 | 51 | Asc 52 | 53 | column.toggleSorting(true)}> 54 | 55 | Desc 56 | 57 | 58 | column.toggleVisibility(false)}> 59 | 60 | Hide 61 | 62 | 63 | 64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/components/data-table-filter.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "./ui/button"; 2 | import { Check, FilterIcon, X } from "lucide-react"; 3 | import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover"; 4 | import { 5 | Command, 6 | CommandEmpty, 7 | CommandGroup, 8 | CommandInput, 9 | CommandItem, 10 | CommandList, 11 | } from "./ui/command"; 12 | import { useMemo, useState } from "react"; 13 | import type { Column } from "@tanstack/react-table"; 14 | import { cn } from "../lib/utils"; 15 | 16 | type FilterCondition = "is" | "is not" | "contains" | "does not contain"; 17 | 18 | interface FilterItem { 19 | value: string; 20 | condition: FilterCondition; 21 | } 22 | 23 | interface DataTableFilterProps { 24 | columnLabel?: string; 25 | column?: Column; 26 | icon?: React.ReactNode; 27 | title?: string; 28 | options: { 29 | label: string; 30 | value: string; 31 | }[]; 32 | } 33 | 34 | interface DataTableFilterComponentProps { 35 | filters: DataTableFilterProps[]; 36 | } 37 | 38 | export function DataTableFilter({ 39 | filters, 40 | }: DataTableFilterComponentProps) { 41 | const [open, setOpen] = useState(false); 42 | const [currentColumnLabel, setCurrentColumnLabel] = useState( 43 | null, 44 | ); 45 | const [currentColumn, setCurrentColumn] = useState | null>(null); 49 | const [currentOptions, setCurrentOptions] = useState< 50 | DataTableFilterProps["options"] | null 51 | >(null); 52 | const [activeFilters, setActiveFilters] = useState<{ 53 | [columnId: string]: FilterItem[]; 54 | }>({}); 55 | 56 | const [currentCondition, setCurrentCondition] = 57 | useState("is"); 58 | 59 | const facets = currentColumn?.getFacetedUniqueValues(); 60 | const currentFilterValue = currentColumn?.getFilterValue() as 61 | | FilterItem[] 62 | | undefined; 63 | 64 | const selectedValues = useMemo( 65 | () => new Set(currentFilterValue?.map((f) => f.value) || []), 66 | [currentFilterValue], 67 | ); 68 | 69 | const FiltersCommand = useMemo(() => { 70 | return ( 71 | 72 | 73 | 74 | No filter found. 75 | 76 | {filters.map((filter) => ( 77 | { 81 | if (filter.column) { 82 | setCurrentColumn(filter.column); 83 | setCurrentOptions(filter.options); 84 | setCurrentColumnLabel(filter.columnLabel ?? null); 85 | setCurrentCondition("is"); 86 | } 87 | }} 88 | > 89 | {filter.icon} 90 | {filter.title} 91 | 92 | ))} 93 | 94 | 95 | 96 | ); 97 | }, [filters]); 98 | 99 | const FilterSelectionCommand = useMemo(() => { 100 | return ( 101 | 102 | 106 | 107 | No options found. 108 | 109 | {currentOptions?.map((option) => { 110 | const isSelected = selectedValues.has(option.value); 111 | return ( 112 | { 116 | setCurrentColumn(null); 117 | setCurrentOptions(null); 118 | setOpen(false); 119 | if (currentColumn) { 120 | const columnId = currentColumn.id; 121 | 122 | if (isSelected) { 123 | setActiveFilters((prev) => { 124 | const updated = { ...prev }; 125 | if (updated[columnId]) { 126 | updated[columnId] = updated[columnId].filter( 127 | (filter) => 128 | filter.value !== option.value || 129 | filter.condition !== currentCondition, 130 | ); 131 | 132 | if (updated[columnId].length === 0) { 133 | delete updated[columnId]; 134 | } 135 | } 136 | return updated; 137 | }); 138 | } else { 139 | setActiveFilters((prev) => ({ 140 | ...prev, 141 | [columnId]: [ 142 | ...(prev[columnId] || []), 143 | { 144 | value: option.value, 145 | condition: currentCondition, 146 | }, 147 | ], 148 | })); 149 | } 150 | 151 | const filterValues: FilterItem[] = 152 | activeFilters[columnId] || []; 153 | const newFilterItem = { 154 | value: option.value, 155 | condition: currentCondition, 156 | }; 157 | const updatedFilterValues = isSelected 158 | ? filterValues.filter( 159 | (f) => 160 | f.value !== option.value || 161 | f.condition !== currentCondition, 162 | ) 163 | : [...filterValues, newFilterItem]; 164 | 165 | currentColumn?.setFilterValue( 166 | updatedFilterValues.length 167 | ? updatedFilterValues 168 | : undefined, 169 | ); 170 | } 171 | }} 172 | > 173 |
181 | 182 |
183 | {option.label} 184 | {facets?.get(option.value) && ( 185 | 186 | {facets.get(option.value)} 187 | 188 | )} 189 |
190 | ); 191 | })} 192 |
193 |
194 |
195 | ); 196 | }, [ 197 | currentColumn, 198 | currentOptions, 199 | selectedValues, 200 | facets, 201 | currentColumnLabel, 202 | currentCondition, 203 | activeFilters, 204 | ]); 205 | 206 | const handleClearFilter = ( 207 | columnId: string, 208 | value?: string, 209 | condition?: FilterCondition, 210 | ) => { 211 | setActiveFilters((prev) => { 212 | const updated = { ...prev }; 213 | if (updated[columnId]) { 214 | if (value && condition) { 215 | updated[columnId] = updated[columnId].filter( 216 | (f) => f.value !== value || f.condition !== condition, 217 | ); 218 | } else { 219 | delete updated[columnId]; 220 | } 221 | } 222 | return updated; 223 | }); 224 | 225 | const column = filters.find((f) => f.column?.id === columnId)?.column; 226 | if (column) { 227 | const currentFilterValue = column.getFilterValue() as 228 | | FilterItem[] 229 | | undefined; 230 | 231 | let newFilterValues: FilterItem[] | undefined; 232 | if (value && condition) { 233 | newFilterValues = currentFilterValue?.filter( 234 | (f) => f.value !== value || f.condition !== condition, 235 | ); 236 | } 237 | 238 | column.setFilterValue( 239 | newFilterValues?.length ? newFilterValues : undefined, 240 | ); 241 | } 242 | }; 243 | 244 | const handleUpdateFilterCondition = ( 245 | columnId: string, 246 | value: string, 247 | oldCondition: FilterCondition, 248 | newCondition: FilterCondition, 249 | ) => { 250 | setActiveFilters((prev) => { 251 | const updated = { ...prev }; 252 | if (updated[columnId]) { 253 | updated[columnId] = updated[columnId].map((f) => 254 | f.value === value && f.condition === oldCondition 255 | ? { ...f, condition: newCondition } 256 | : f, 257 | ); 258 | } 259 | return updated; 260 | }); 261 | 262 | const column = filters.find((f) => f.column?.id === columnId)?.column; 263 | if (column) { 264 | const currentFilterValue = column.getFilterValue() as 265 | | FilterItem[] 266 | | undefined; 267 | if (currentFilterValue) { 268 | const updatedFilterValues = currentFilterValue.map((f) => 269 | f.value === value && f.condition === oldCondition 270 | ? { ...f, condition: newCondition } 271 | : f, 272 | ); 273 | column.setFilterValue(updatedFilterValues); 274 | } 275 | } 276 | }; 277 | 278 | return ( 279 |
280 |
281 | {Object.entries(activeFilters).map(([columnId, filterItems]) => 282 | filterItems.map((filterItem) => { 283 | const filter = filters.find((f) => f.column?.id === columnId); 284 | const option = filter?.options.find( 285 | (opt) => opt.value === filterItem.value, 286 | ); 287 | return option ? ( 288 | 294 | handleClearFilter( 295 | columnId, 296 | filterItem.value, 297 | filterItem.condition, 298 | ) 299 | } 300 | onConditionChange={(newCondition) => 301 | handleUpdateFilterCondition( 302 | columnId, 303 | filterItem.value, 304 | filterItem.condition, 305 | newCondition, 306 | ) 307 | } 308 | /> 309 | ) : null; 310 | }), 311 | )} 312 |
313 | { 316 | setCurrentOptions(null); 317 | setOpen(isOpen); 318 | }} 319 | > 320 | 321 | 329 | 330 | 331 | {currentOptions === null ? FiltersCommand : FilterSelectionCommand} 332 | 333 | 334 |
335 | ); 336 | } 337 | 338 | const FilterIndicator = ({ 339 | field, 340 | label, 341 | condition, 342 | onClear, 343 | onConditionChange, 344 | }: { 345 | field: string; 346 | label: string; 347 | condition: FilterCondition; 348 | onClear: () => void; 349 | onConditionChange: (condition: FilterCondition) => void; 350 | }) => { 351 | const CONDITIONS: FilterCondition[] = [ 352 | "is", 353 | "is not", 354 | "contains", 355 | "does not contain", 356 | ]; 357 | const [open, setOpen] = useState(false); 358 | 359 | return ( 360 |
361 |
362 | {field} 363 |
364 | 365 | 366 | 373 | 374 | 375 | 376 | 377 | 378 | {CONDITIONS.map((cond) => ( 379 | { 383 | onConditionChange(cond); 384 | setOpen(false); 385 | }} 386 | > 387 | {cond} 388 | 389 | ))} 390 | 391 | 392 | 393 | 394 | 395 |
396 | 403 | 412 |
413 |
414 | ); 415 | }; 416 | -------------------------------------------------------------------------------- /src/components/data-table-pagination.tsx: -------------------------------------------------------------------------------- 1 | import { Table } from "@tanstack/react-table"; 2 | import { 3 | ChevronLeft, 4 | ChevronRight, 5 | ChevronsLeft, 6 | ChevronsRight, 7 | } from "lucide-react"; 8 | 9 | import { Button } from "./ui/button"; 10 | import { 11 | Select, 12 | SelectContent, 13 | SelectItem, 14 | SelectTrigger, 15 | SelectValue, 16 | } from "./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/data-table-row-actions.tsx: -------------------------------------------------------------------------------- 1 | import { Row } from "@tanstack/react-table"; 2 | import { MoreHorizontal } from "lucide-react"; 3 | 4 | import { Button } from "./ui/button"; 5 | import { 6 | DropdownMenu, 7 | DropdownMenuContent, 8 | DropdownMenuItem, 9 | DropdownMenuRadioGroup, 10 | DropdownMenuRadioItem, 11 | DropdownMenuSeparator, 12 | DropdownMenuShortcut, 13 | DropdownMenuSub, 14 | DropdownMenuSubContent, 15 | DropdownMenuSubTrigger, 16 | DropdownMenuTrigger, 17 | } from "./ui/dropdown-menu"; 18 | 19 | import { labels } from "../data/data"; 20 | import { taskSchema } from "../data/schema"; 21 | 22 | interface DataTableRowActionsProps { 23 | row: Row; 24 | } 25 | 26 | export function DataTableRowActions({ 27 | row, 28 | }: DataTableRowActionsProps) { 29 | const task = taskSchema.parse(row.original); 30 | 31 | return ( 32 | 33 | 34 | 41 | 42 | 43 | Edit 44 | Make a copy 45 | Favorite 46 | 47 | 48 | Labels 49 | 50 | 51 | {labels.map((label) => ( 52 | 53 | {label.label} 54 | 55 | ))} 56 | 57 | 58 | 59 | 60 | 61 | Delete 62 | ⌘⌫ 63 | 64 | 65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/components/data-table-toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { Table } from "@tanstack/react-table"; 2 | import { 3 | ChartBar, 4 | ChartNoAxesColumnIncreasing, 5 | CircleDashed, 6 | X, 7 | } from "lucide-react"; 8 | 9 | import { Button } from "./ui/button"; 10 | import { Input } from "./ui/input"; 11 | import { DataTableViewOptions } from "./data-table-view-options"; 12 | 13 | import { priorities, statuses } from "../data/data"; 14 | import { DataTableFilter } from "./data-table-filter"; 15 | 16 | interface DataTableToolbarProps { 17 | table: Table; 18 | } 19 | 20 | export function DataTableToolbar({ 21 | table, 22 | }: DataTableToolbarProps) { 23 | const isFiltered = table.getState().columnFilters.length > 0; 24 | 25 | return ( 26 |
27 |
28 | 32 | table.getColumn("title")?.setFilterValue(event.target.value) 33 | } 34 | className="h-8 w-[150px] lg:w-[250px]" 35 | /> 36 | , 43 | options: priorities.map((p) => ({ 44 | value: p.value, 45 | label: p.label, 46 | })), 47 | }, 48 | { 49 | column: table.getColumn("status"), 50 | columnLabel: "Status", 51 | title: "Status", 52 | icon: , 53 | options: statuses.map((s) => ({ 54 | value: s.value, 55 | label: s.label, 56 | })), 57 | }, 58 | ]} 59 | /> 60 |
61 | 62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/components/data-table-view-options.tsx: -------------------------------------------------------------------------------- 1 | import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; 2 | import { Table } from "@tanstack/react-table"; 3 | import { Settings2 } from "lucide-react"; 4 | 5 | import { Button } from "./ui/button"; 6 | import { 7 | DropdownMenu, 8 | DropdownMenuCheckboxItem, 9 | DropdownMenuContent, 10 | DropdownMenuLabel, 11 | DropdownMenuSeparator, 12 | } from "./ui/dropdown-menu"; 13 | 14 | interface DataTableViewOptionsProps { 15 | table: Table; 16 | } 17 | 18 | export function DataTableViewOptions({ 19 | table, 20 | }: DataTableViewOptionsProps) { 21 | return ( 22 | 23 | 24 | 32 | 33 | 34 | Toggle columns 35 | 36 | {table 37 | .getAllColumns() 38 | .filter( 39 | (column) => 40 | typeof column.accessorFn !== "undefined" && column.getCanHide(), 41 | ) 42 | .map((column) => { 43 | return ( 44 | column.toggleVisibility(!!value)} 49 | > 50 | {column.id} 51 | 52 | ); 53 | })} 54 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/data-table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | ColumnDef, 4 | ColumnFiltersState, 5 | SortingState, 6 | VisibilityState, 7 | flexRender, 8 | getCoreRowModel, 9 | getFacetedRowModel, 10 | getFacetedUniqueValues, 11 | getFilteredRowModel, 12 | getPaginationRowModel, 13 | getSortedRowModel, 14 | useReactTable, 15 | } from "@tanstack/react-table"; 16 | 17 | import { 18 | Table, 19 | TableBody, 20 | TableCell, 21 | TableHead, 22 | TableHeader, 23 | TableRow, 24 | } from "./ui/table"; 25 | 26 | import { DataTablePagination } from "./data-table-pagination"; 27 | import { DataTableToolbar } from "./data-table-toolbar"; 28 | 29 | interface DataTableProps { 30 | columns: ColumnDef[]; 31 | data: TData[]; 32 | } 33 | 34 | export function DataTable({ 35 | columns, 36 | data, 37 | }: DataTableProps) { 38 | const [rowSelection, setRowSelection] = React.useState({}); 39 | const [columnVisibility, setColumnVisibility] = 40 | React.useState({}); 41 | const [columnFilters, setColumnFilters] = React.useState( 42 | [], 43 | ); 44 | const [sorting, setSorting] = React.useState([]); 45 | 46 | const table = useReactTable({ 47 | data, 48 | columns, 49 | state: { 50 | sorting, 51 | columnVisibility, 52 | rowSelection, 53 | columnFilters, 54 | }, 55 | enableRowSelection: true, 56 | onRowSelectionChange: setRowSelection, 57 | onSortingChange: setSorting, 58 | onColumnFiltersChange: setColumnFilters, 59 | onColumnVisibilityChange: setColumnVisibility, 60 | getCoreRowModel: getCoreRowModel(), 61 | getFilteredRowModel: getFilteredRowModel(), 62 | getPaginationRowModel: getPaginationRowModel(), 63 | getSortedRowModel: getSortedRowModel(), 64 | getFacetedRowModel: getFacetedRowModel(), 65 | getFacetedUniqueValues: getFacetedUniqueValues(), 66 | }); 67 | 68 | return ( 69 |
70 | 71 |
72 | 73 | 74 | {table.getHeaderGroups().map((headerGroup) => ( 75 | 76 | {headerGroup.headers.map((header) => { 77 | return ( 78 | 79 | {header.isPlaceholder 80 | ? null 81 | : flexRender( 82 | header.column.columnDef.header, 83 | header.getContext(), 84 | )} 85 | 86 | ); 87 | })} 88 | 89 | ))} 90 | 91 | 92 | {table.getRowModel().rows?.length ? ( 93 | table.getRowModel().rows.map((row) => ( 94 | 98 | {row.getVisibleCells().map((cell) => ( 99 | 100 | {flexRender( 101 | cell.column.columnDef.cell, 102 | cell.getContext(), 103 | )} 104 | 105 | ))} 106 | 107 | )) 108 | ) : ( 109 | 110 | 114 | No results. 115 | 116 | 117 | )} 118 | 119 |
120 |
121 | 122 |
123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /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-md 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 shadow 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 shadow 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 gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 3 | import { Check } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | )) 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 27 | 28 | export { Checkbox } 29 | -------------------------------------------------------------------------------- /src/components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { type DialogProps } from "@radix-ui/react-dialog" 3 | import { Command as CommandPrimitive } from "cmdk" 4 | import { Search } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { Dialog, DialogContent } from "@/components/ui/dialog" 8 | 9 | const Command = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | )) 22 | Command.displayName = CommandPrimitive.displayName 23 | 24 | const CommandDialog = ({ children, ...props }: DialogProps) => { 25 | return ( 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | const CommandInput = React.forwardRef< 37 | React.ElementRef, 38 | React.ComponentPropsWithoutRef 39 | >(({ className, ...props }, ref) => ( 40 |
41 | 42 | 50 |
51 | )) 52 | 53 | CommandInput.displayName = CommandPrimitive.Input.displayName 54 | 55 | const CommandList = React.forwardRef< 56 | React.ElementRef, 57 | React.ComponentPropsWithoutRef 58 | >(({ className, ...props }, ref) => ( 59 | 64 | )) 65 | 66 | CommandList.displayName = CommandPrimitive.List.displayName 67 | 68 | const CommandEmpty = React.forwardRef< 69 | React.ElementRef, 70 | React.ComponentPropsWithoutRef 71 | >((props, ref) => ( 72 | 77 | )) 78 | 79 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName 80 | 81 | const CommandGroup = React.forwardRef< 82 | React.ElementRef, 83 | React.ComponentPropsWithoutRef 84 | >(({ className, ...props }, ref) => ( 85 | 93 | )) 94 | 95 | CommandGroup.displayName = CommandPrimitive.Group.displayName 96 | 97 | const CommandSeparator = React.forwardRef< 98 | React.ElementRef, 99 | React.ComponentPropsWithoutRef 100 | >(({ className, ...props }, ref) => ( 101 | 106 | )) 107 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName 108 | 109 | const CommandItem = React.forwardRef< 110 | React.ElementRef, 111 | React.ComponentPropsWithoutRef 112 | >(({ className, ...props }, ref) => ( 113 | 121 | )) 122 | 123 | CommandItem.displayName = CommandPrimitive.Item.displayName 124 | 125 | const CommandShortcut = ({ 126 | className, 127 | ...props 128 | }: React.HTMLAttributes) => { 129 | return ( 130 | 137 | ) 138 | } 139 | CommandShortcut.displayName = "CommandShortcut" 140 | 141 | export { 142 | Command, 143 | CommandDialog, 144 | CommandInput, 145 | CommandList, 146 | CommandEmpty, 147 | CommandGroup, 148 | CommandItem, 149 | CommandShortcut, 150 | CommandSeparator, 151 | } 152 | -------------------------------------------------------------------------------- /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 | DialogTrigger, 116 | DialogClose, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 3 | import { Check, ChevronRight, Circle } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const DropdownMenu = DropdownMenuPrimitive.Root 8 | 9 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 10 | 11 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 12 | 13 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 14 | 15 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 16 | 17 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 18 | 19 | const DropdownMenuSubTrigger = React.forwardRef< 20 | React.ElementRef, 21 | React.ComponentPropsWithoutRef & { 22 | inset?: boolean 23 | } 24 | >(({ className, inset, children, ...props }, ref) => ( 25 | 34 | {children} 35 | 36 | 37 | )) 38 | DropdownMenuSubTrigger.displayName = 39 | DropdownMenuPrimitive.SubTrigger.displayName 40 | 41 | const DropdownMenuSubContent = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef 44 | >(({ className, ...props }, ref) => ( 45 | 53 | )) 54 | DropdownMenuSubContent.displayName = 55 | DropdownMenuPrimitive.SubContent.displayName 56 | 57 | const DropdownMenuContent = React.forwardRef< 58 | React.ElementRef, 59 | React.ComponentPropsWithoutRef 60 | >(({ className, sideOffset = 4, ...props }, ref) => ( 61 | 62 | 72 | 73 | )) 74 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 75 | 76 | const DropdownMenuItem = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef & { 79 | inset?: boolean 80 | } 81 | >(({ className, inset, ...props }, ref) => ( 82 | svg]:size-4 [&>svg]:shrink-0", 86 | inset && "pl-8", 87 | className 88 | )} 89 | {...props} 90 | /> 91 | )) 92 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 93 | 94 | const DropdownMenuCheckboxItem = React.forwardRef< 95 | React.ElementRef, 96 | React.ComponentPropsWithoutRef 97 | >(({ className, children, checked, ...props }, ref) => ( 98 | 107 | 108 | 109 | 110 | 111 | 112 | {children} 113 | 114 | )) 115 | DropdownMenuCheckboxItem.displayName = 116 | DropdownMenuPrimitive.CheckboxItem.displayName 117 | 118 | const DropdownMenuRadioItem = React.forwardRef< 119 | React.ElementRef, 120 | React.ComponentPropsWithoutRef 121 | >(({ className, children, ...props }, ref) => ( 122 | 130 | 131 | 132 | 133 | 134 | 135 | {children} 136 | 137 | )) 138 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 139 | 140 | const DropdownMenuLabel = React.forwardRef< 141 | React.ElementRef, 142 | React.ComponentPropsWithoutRef & { 143 | inset?: boolean 144 | } 145 | >(({ className, inset, ...props }, ref) => ( 146 | 155 | )) 156 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 157 | 158 | const DropdownMenuSeparator = React.forwardRef< 159 | React.ElementRef, 160 | React.ComponentPropsWithoutRef 161 | >(({ className, ...props }, ref) => ( 162 | 167 | )) 168 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 169 | 170 | const DropdownMenuShortcut = ({ 171 | className, 172 | ...props 173 | }: React.HTMLAttributes) => { 174 | return ( 175 | 179 | ) 180 | } 181 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 182 | 183 | export { 184 | DropdownMenu, 185 | DropdownMenuTrigger, 186 | DropdownMenuContent, 187 | DropdownMenuItem, 188 | DropdownMenuCheckboxItem, 189 | DropdownMenuRadioItem, 190 | DropdownMenuLabel, 191 | DropdownMenuSeparator, 192 | DropdownMenuShortcut, 193 | DropdownMenuGroup, 194 | DropdownMenuPortal, 195 | DropdownMenuSub, 196 | DropdownMenuSubContent, 197 | DropdownMenuSubTrigger, 198 | DropdownMenuRadioGroup, 199 | } 200 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as PopoverPrimitive from "@radix-ui/react-popover" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Popover = PopoverPrimitive.Root 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger 9 | 10 | const PopoverAnchor = PopoverPrimitive.Anchor 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, PopoverAnchor } 32 | -------------------------------------------------------------------------------- /src/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SelectPrimitive from "@radix-ui/react-select" 3 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Select = SelectPrimitive.Root 8 | 9 | const SelectGroup = SelectPrimitive.Group 10 | 11 | const SelectValue = SelectPrimitive.Value 12 | 13 | const SelectTrigger = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef 16 | >(({ className, children, ...props }, ref) => ( 17 | span]:line-clamp-1", 21 | className 22 | )} 23 | {...props} 24 | > 25 | {children} 26 | 27 | 28 | 29 | 30 | )) 31 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 32 | 33 | const SelectScrollUpButton = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | 46 | 47 | )) 48 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 49 | 50 | const SelectScrollDownButton = React.forwardRef< 51 | React.ElementRef, 52 | React.ComponentPropsWithoutRef 53 | >(({ className, ...props }, ref) => ( 54 | 62 | 63 | 64 | )) 65 | SelectScrollDownButton.displayName = 66 | SelectPrimitive.ScrollDownButton.displayName 67 | 68 | const SelectContent = React.forwardRef< 69 | React.ElementRef, 70 | React.ComponentPropsWithoutRef 71 | >(({ className, children, position = "popper", ...props }, ref) => ( 72 | 73 | 84 | 85 | 92 | {children} 93 | 94 | 95 | 96 | 97 | )) 98 | SelectContent.displayName = SelectPrimitive.Content.displayName 99 | 100 | const SelectLabel = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 109 | )) 110 | SelectLabel.displayName = SelectPrimitive.Label.displayName 111 | 112 | const SelectItem = React.forwardRef< 113 | React.ElementRef, 114 | React.ComponentPropsWithoutRef 115 | >(({ className, children, ...props }, ref) => ( 116 | 124 | 125 | 126 | 127 | 128 | 129 | {children} 130 | 131 | )) 132 | SelectItem.displayName = SelectPrimitive.Item.displayName 133 | 134 | const SelectSeparator = React.forwardRef< 135 | React.ElementRef, 136 | React.ComponentPropsWithoutRef 137 | >(({ className, ...props }, ref) => ( 138 | 143 | )) 144 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 145 | 146 | export { 147 | Select, 148 | SelectGroup, 149 | SelectValue, 150 | SelectTrigger, 151 | SelectContent, 152 | SelectLabel, 153 | SelectItem, 154 | SelectSeparator, 155 | SelectScrollUpButton, 156 | SelectScrollDownButton, 157 | } 158 | -------------------------------------------------------------------------------- /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 |
10 | 15 | 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 |
[role=checkbox]]:translate-y-[2px]", 77 | className 78 | )} 79 | {...props} 80 | /> 81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | [role=checkbox]]:translate-y-[2px]", 92 | className 93 | )} 94 | {...props} 95 | /> 96 | )) 97 | TableCell.displayName = "TableCell" 98 | 99 | const TableCaption = React.forwardRef< 100 | HTMLTableCaptionElement, 101 | React.HTMLAttributes 102 | >(({ className, ...props }, ref) => ( 103 |
108 | )) 109 | TableCaption.displayName = "TableCaption" 110 | 111 | export { 112 | Table, 113 | TableHeader, 114 | TableBody, 115 | TableFooter, 116 | TableHead, 117 | TableRow, 118 | TableCell, 119 | TableCaption, 120 | } 121 | -------------------------------------------------------------------------------- /src/data/data.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ArrowDown, 3 | ArrowRight, 4 | ArrowUp, 5 | CheckCircle, 6 | Circle, 7 | CircleOff, 8 | HelpCircle, 9 | Timer, 10 | } from "lucide-react"; 11 | 12 | export const labels = [ 13 | { 14 | value: "bug", 15 | label: "Bug", 16 | }, 17 | { 18 | value: "feature", 19 | label: "Feature", 20 | }, 21 | { 22 | value: "documentation", 23 | label: "Documentation", 24 | }, 25 | ]; 26 | 27 | export const statuses = [ 28 | { 29 | value: "backlog", 30 | label: "Backlog", 31 | icon: HelpCircle, 32 | }, 33 | { 34 | value: "todo", 35 | label: "Todo", 36 | icon: Circle, 37 | }, 38 | { 39 | value: "in progress", 40 | label: "In Progress", 41 | icon: Timer, 42 | }, 43 | { 44 | value: "done", 45 | label: "Done", 46 | icon: CheckCircle, 47 | }, 48 | { 49 | value: "canceled", 50 | label: "Canceled", 51 | icon: CircleOff, 52 | }, 53 | ]; 54 | 55 | export const priorities = [ 56 | { 57 | label: "Low", 58 | value: "low", 59 | icon: ArrowDown, 60 | }, 61 | { 62 | label: "Medium", 63 | value: "medium", 64 | icon: ArrowRight, 65 | }, 66 | { 67 | label: "High", 68 | value: "high", 69 | icon: ArrowUp, 70 | }, 71 | ]; 72 | -------------------------------------------------------------------------------- /src/data/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const taskSchema = z.object({ 4 | id: z.string(), 5 | title: z.string(), 6 | status: z.string(), 7 | label: z.string(), 8 | priority: z.string(), 9 | }); 10 | 11 | export type Task = z.infer; 12 | -------------------------------------------------------------------------------- /src/data/seed.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { faker } from "@faker-js/faker"; 4 | 5 | import { labels, priorities, statuses } from "./data"; 6 | 7 | const tasks = Array.from({ length: 100 }, () => ({ 8 | id: `TASK-${faker.number.int({ min: 1000, max: 9999 })}`, 9 | title: faker.hacker.phrase().replace(/^./, (letter) => letter.toUpperCase()), 10 | status: faker.helpers.arrayElement(statuses).value, 11 | label: faker.helpers.arrayElement(labels).value, 12 | priority: faker.helpers.arrayElement(priorities).value, 13 | })); 14 | 15 | fs.writeFileSync( 16 | path.join(__dirname, "tasks.json"), 17 | JSON.stringify(tasks, null, 2), 18 | ); 19 | 20 | console.log("✅ Tasks data generated."); 21 | -------------------------------------------------------------------------------- /src/data/tasks.ts: -------------------------------------------------------------------------------- 1 | export const tasks = [ 2 | { 3 | id: "TASK-8782", 4 | title: 5 | "You can't compress the program without quantifying the open-source SSD pixel!", 6 | status: "in progress", 7 | label: "documentation", 8 | priority: "medium", 9 | }, 10 | { 11 | id: "TASK-7878", 12 | title: 13 | "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!", 14 | status: "backlog", 15 | label: "documentation", 16 | priority: "medium", 17 | }, 18 | { 19 | id: "TASK-7839", 20 | title: "We need to bypass the neural TCP card!", 21 | status: "todo", 22 | label: "bug", 23 | priority: "high", 24 | }, 25 | { 26 | id: "TASK-5562", 27 | title: 28 | "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!", 29 | status: "backlog", 30 | label: "feature", 31 | priority: "medium", 32 | }, 33 | { 34 | id: "TASK-8686", 35 | title: 36 | "I'll parse the wireless SSL protocol, that should driver the API panel!", 37 | status: "canceled", 38 | label: "feature", 39 | priority: "medium", 40 | }, 41 | { 42 | id: "TASK-1280", 43 | title: 44 | "Use the digital TLS panel, then you can transmit the haptic system!", 45 | status: "done", 46 | label: "bug", 47 | priority: "high", 48 | }, 49 | { 50 | id: "TASK-7262", 51 | title: 52 | "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!", 53 | status: "done", 54 | label: "feature", 55 | priority: "high", 56 | }, 57 | { 58 | id: "TASK-1138", 59 | title: 60 | "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!", 61 | status: "in progress", 62 | label: "feature", 63 | priority: "medium", 64 | }, 65 | { 66 | id: "TASK-7184", 67 | title: "We need to program the back-end THX pixel!", 68 | status: "todo", 69 | label: "feature", 70 | priority: "low", 71 | }, 72 | { 73 | id: "TASK-5160", 74 | title: 75 | "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!", 76 | status: "in progress", 77 | label: "documentation", 78 | priority: "high", 79 | }, 80 | { 81 | id: "TASK-5618", 82 | title: 83 | "Generating the driver won't do anything, we need to index the online SSL application!", 84 | status: "done", 85 | label: "documentation", 86 | priority: "medium", 87 | }, 88 | { 89 | id: "TASK-6699", 90 | title: 91 | "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!", 92 | status: "backlog", 93 | label: "documentation", 94 | priority: "medium", 95 | }, 96 | { 97 | id: "TASK-2858", 98 | title: "We need to override the online UDP bus!", 99 | status: "backlog", 100 | label: "bug", 101 | priority: "medium", 102 | }, 103 | { 104 | id: "TASK-9864", 105 | title: 106 | "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!", 107 | status: "done", 108 | label: "bug", 109 | priority: "high", 110 | }, 111 | { 112 | id: "TASK-8404", 113 | title: "We need to generate the virtual HEX alarm!", 114 | status: "in progress", 115 | label: "bug", 116 | priority: "low", 117 | }, 118 | { 119 | id: "TASK-5365", 120 | title: 121 | "Backing up the pixel won't do anything, we need to transmit the primary IB array!", 122 | status: "in progress", 123 | label: "documentation", 124 | priority: "low", 125 | }, 126 | { 127 | id: "TASK-1780", 128 | title: 129 | "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!", 130 | status: "todo", 131 | label: "documentation", 132 | priority: "high", 133 | }, 134 | { 135 | id: "TASK-6938", 136 | title: 137 | "Use the redundant SCSI application, then you can hack the optical alarm!", 138 | status: "todo", 139 | label: "documentation", 140 | priority: "high", 141 | }, 142 | { 143 | id: "TASK-9885", 144 | title: "We need to compress the auxiliary VGA driver!", 145 | status: "backlog", 146 | label: "bug", 147 | priority: "high", 148 | }, 149 | { 150 | id: "TASK-3216", 151 | title: 152 | "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!", 153 | status: "backlog", 154 | label: "documentation", 155 | priority: "medium", 156 | }, 157 | { 158 | id: "TASK-9285", 159 | title: 160 | "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!", 161 | status: "todo", 162 | label: "bug", 163 | priority: "high", 164 | }, 165 | { 166 | id: "TASK-1024", 167 | title: 168 | "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!", 169 | status: "in progress", 170 | label: "documentation", 171 | priority: "low", 172 | }, 173 | { 174 | id: "TASK-7068", 175 | title: 176 | "You can't generate the capacitor without indexing the wireless HEX pixel!", 177 | status: "canceled", 178 | label: "bug", 179 | priority: "low", 180 | }, 181 | { 182 | id: "TASK-6502", 183 | title: 184 | "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!", 185 | status: "todo", 186 | label: "bug", 187 | priority: "high", 188 | }, 189 | { 190 | id: "TASK-5326", 191 | title: "We need to hack the redundant UTF8 transmitter!", 192 | status: "todo", 193 | label: "bug", 194 | priority: "low", 195 | }, 196 | { 197 | id: "TASK-6274", 198 | title: 199 | "Use the virtual PCI circuit, then you can parse the bluetooth alarm!", 200 | status: "canceled", 201 | label: "documentation", 202 | priority: "low", 203 | }, 204 | { 205 | id: "TASK-1571", 206 | title: 207 | "I'll input the neural DRAM circuit, that should protocol the SMTP interface!", 208 | status: "in progress", 209 | label: "feature", 210 | priority: "medium", 211 | }, 212 | { 213 | id: "TASK-9518", 214 | title: 215 | "Compressing the interface won't do anything, we need to compress the online SDD matrix!", 216 | status: "canceled", 217 | label: "documentation", 218 | priority: "medium", 219 | }, 220 | { 221 | id: "TASK-5581", 222 | title: 223 | "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!", 224 | status: "backlog", 225 | label: "documentation", 226 | priority: "high", 227 | }, 228 | { 229 | id: "TASK-2197", 230 | title: 231 | "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!", 232 | status: "todo", 233 | label: "documentation", 234 | priority: "low", 235 | }, 236 | { 237 | id: "TASK-8484", 238 | title: "We need to parse the solid state UDP firewall!", 239 | status: "in progress", 240 | label: "bug", 241 | priority: "low", 242 | }, 243 | { 244 | id: "TASK-9892", 245 | title: 246 | "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!", 247 | status: "done", 248 | label: "documentation", 249 | priority: "high", 250 | }, 251 | { 252 | id: "TASK-9616", 253 | title: "We need to synthesize the cross-platform ASCII pixel!", 254 | status: "in progress", 255 | label: "feature", 256 | priority: "medium", 257 | }, 258 | { 259 | id: "TASK-9744", 260 | title: 261 | "Use the back-end IP card, then you can input the solid state hard drive!", 262 | status: "done", 263 | label: "documentation", 264 | priority: "low", 265 | }, 266 | { 267 | id: "TASK-1376", 268 | title: 269 | "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!", 270 | status: "backlog", 271 | label: "documentation", 272 | priority: "low", 273 | }, 274 | { 275 | id: "TASK-7382", 276 | title: 277 | "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!", 278 | status: "todo", 279 | label: "feature", 280 | priority: "low", 281 | }, 282 | { 283 | id: "TASK-2290", 284 | title: 285 | "I'll compress the virtual JSON panel, that should application the UTF8 bus!", 286 | status: "canceled", 287 | label: "documentation", 288 | priority: "high", 289 | }, 290 | { 291 | id: "TASK-1533", 292 | title: 293 | "You can't input the firewall without overriding the wireless TCP firewall!", 294 | status: "done", 295 | label: "bug", 296 | priority: "high", 297 | }, 298 | { 299 | id: "TASK-4920", 300 | title: 301 | "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!", 302 | status: "in progress", 303 | label: "bug", 304 | priority: "high", 305 | }, 306 | { 307 | id: "TASK-5168", 308 | title: 309 | "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!", 310 | status: "in progress", 311 | label: "feature", 312 | priority: "low", 313 | }, 314 | { 315 | id: "TASK-7103", 316 | title: "We need to parse the multi-byte EXE bandwidth!", 317 | status: "canceled", 318 | label: "feature", 319 | priority: "low", 320 | }, 321 | { 322 | id: "TASK-4314", 323 | title: 324 | "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!", 325 | status: "in progress", 326 | label: "bug", 327 | priority: "high", 328 | }, 329 | { 330 | id: "TASK-3415", 331 | title: 332 | "Use the cross-platform XML application, then you can quantify the solid state feed!", 333 | status: "todo", 334 | label: "feature", 335 | priority: "high", 336 | }, 337 | { 338 | id: "TASK-8339", 339 | title: 340 | "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!", 341 | status: "in progress", 342 | label: "feature", 343 | priority: "low", 344 | }, 345 | { 346 | id: "TASK-6995", 347 | title: 348 | "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!", 349 | status: "todo", 350 | label: "feature", 351 | priority: "high", 352 | }, 353 | { 354 | id: "TASK-8053", 355 | title: 356 | "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!", 357 | status: "todo", 358 | label: "feature", 359 | priority: "medium", 360 | }, 361 | { 362 | id: "TASK-4336", 363 | title: 364 | "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!", 365 | status: "todo", 366 | label: "documentation", 367 | priority: "low", 368 | }, 369 | { 370 | id: "TASK-8790", 371 | title: 372 | "I'll back up the optical COM alarm, that should alarm the RSS capacitor!", 373 | status: "done", 374 | label: "bug", 375 | priority: "medium", 376 | }, 377 | { 378 | id: "TASK-8980", 379 | title: 380 | "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!", 381 | status: "canceled", 382 | label: "bug", 383 | priority: "low", 384 | }, 385 | { 386 | id: "TASK-7342", 387 | title: "Use the neural CLI card, then you can parse the online port!", 388 | status: "backlog", 389 | label: "documentation", 390 | priority: "low", 391 | }, 392 | { 393 | id: "TASK-5608", 394 | title: 395 | "I'll hack the haptic SSL program, that should bus the UDP transmitter!", 396 | status: "canceled", 397 | label: "documentation", 398 | priority: "low", 399 | }, 400 | { 401 | id: "TASK-1606", 402 | title: 403 | "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!", 404 | status: "done", 405 | label: "feature", 406 | priority: "medium", 407 | }, 408 | { 409 | id: "TASK-7872", 410 | title: 411 | "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!", 412 | status: "canceled", 413 | label: "feature", 414 | priority: "medium", 415 | }, 416 | { 417 | id: "TASK-4167", 418 | title: 419 | "Use the cross-platform SMS circuit, then you can synthesize the optical feed!", 420 | status: "canceled", 421 | label: "bug", 422 | priority: "medium", 423 | }, 424 | { 425 | id: "TASK-9581", 426 | title: 427 | "You can't index the port without hacking the cross-platform XSS monitor!", 428 | status: "backlog", 429 | label: "documentation", 430 | priority: "low", 431 | }, 432 | { 433 | id: "TASK-8806", 434 | title: "We need to bypass the back-end SSL panel!", 435 | status: "done", 436 | label: "bug", 437 | priority: "medium", 438 | }, 439 | { 440 | id: "TASK-6542", 441 | title: 442 | "Try to quantify the RSS firewall, maybe it will quantify the open-source system!", 443 | status: "done", 444 | label: "feature", 445 | priority: "low", 446 | }, 447 | { 448 | id: "TASK-6806", 449 | title: 450 | "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!", 451 | status: "canceled", 452 | label: "documentation", 453 | priority: "low", 454 | }, 455 | { 456 | id: "TASK-9549", 457 | title: "You can't bypass the bus without connecting the neural JBOD bus!", 458 | status: "todo", 459 | label: "feature", 460 | priority: "high", 461 | }, 462 | { 463 | id: "TASK-1075", 464 | title: 465 | "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!", 466 | status: "done", 467 | label: "feature", 468 | priority: "medium", 469 | }, 470 | { 471 | id: "TASK-1427", 472 | title: 473 | "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!", 474 | status: "done", 475 | label: "documentation", 476 | priority: "high", 477 | }, 478 | { 479 | id: "TASK-1907", 480 | title: 481 | "Hacking the circuit won't do anything, we need to back up the online DRAM system!", 482 | status: "todo", 483 | label: "documentation", 484 | priority: "high", 485 | }, 486 | { 487 | id: "TASK-4309", 488 | title: 489 | "If we generate the system, we can get to the TCP sensor through the optical GB pixel!", 490 | status: "backlog", 491 | label: "bug", 492 | priority: "medium", 493 | }, 494 | { 495 | id: "TASK-3973", 496 | title: 497 | "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!", 498 | status: "todo", 499 | label: "feature", 500 | priority: "medium", 501 | }, 502 | { 503 | id: "TASK-7962", 504 | title: 505 | "Use the wireless RAM program, then you can hack the cross-platform feed!", 506 | status: "canceled", 507 | label: "bug", 508 | priority: "low", 509 | }, 510 | { 511 | id: "TASK-3360", 512 | title: 513 | "You can't quantify the program without synthesizing the neural OCR interface!", 514 | status: "done", 515 | label: "feature", 516 | priority: "medium", 517 | }, 518 | { 519 | id: "TASK-9887", 520 | title: 521 | "Use the auxiliary ASCII sensor, then you can connect the solid state port!", 522 | status: "backlog", 523 | label: "bug", 524 | priority: "medium", 525 | }, 526 | { 527 | id: "TASK-3649", 528 | title: 529 | "I'll input the virtual USB system, that should circuit the DNS monitor!", 530 | status: "in progress", 531 | label: "feature", 532 | priority: "medium", 533 | }, 534 | { 535 | id: "TASK-3586", 536 | title: 537 | "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!", 538 | status: "in progress", 539 | label: "bug", 540 | priority: "low", 541 | }, 542 | { 543 | id: "TASK-5150", 544 | title: 545 | "I'll hack the wireless XSS port, that should transmitter the IP interface!", 546 | status: "canceled", 547 | label: "feature", 548 | priority: "medium", 549 | }, 550 | { 551 | id: "TASK-3652", 552 | title: 553 | "The SQL interface is down, override the optical bus so we can program the ASCII interface!", 554 | status: "backlog", 555 | label: "feature", 556 | priority: "low", 557 | }, 558 | { 559 | id: "TASK-6884", 560 | title: 561 | "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!", 562 | status: "canceled", 563 | label: "feature", 564 | priority: "high", 565 | }, 566 | { 567 | id: "TASK-1591", 568 | title: "We need to connect the mobile XSS driver!", 569 | status: "in progress", 570 | label: "feature", 571 | priority: "high", 572 | }, 573 | { 574 | id: "TASK-3802", 575 | title: 576 | "Try to override the ASCII protocol, maybe it will parse the virtual matrix!", 577 | status: "in progress", 578 | label: "feature", 579 | priority: "low", 580 | }, 581 | { 582 | id: "TASK-7253", 583 | title: 584 | "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!", 585 | status: "backlog", 586 | label: "bug", 587 | priority: "high", 588 | }, 589 | { 590 | id: "TASK-9739", 591 | title: "We need to hack the multi-byte HDD bus!", 592 | status: "done", 593 | label: "documentation", 594 | priority: "medium", 595 | }, 596 | { 597 | id: "TASK-4424", 598 | title: 599 | "Try to hack the HEX alarm, maybe it will connect the optical pixel!", 600 | status: "in progress", 601 | label: "documentation", 602 | priority: "medium", 603 | }, 604 | { 605 | id: "TASK-3922", 606 | title: 607 | "You can't back up the capacitor without generating the wireless PCI program!", 608 | status: "backlog", 609 | label: "bug", 610 | priority: "low", 611 | }, 612 | { 613 | id: "TASK-4921", 614 | title: 615 | "I'll index the open-source IP feed, that should system the GB application!", 616 | status: "canceled", 617 | label: "bug", 618 | priority: "low", 619 | }, 620 | { 621 | id: "TASK-5814", 622 | title: "We need to calculate the 1080p AGP feed!", 623 | status: "backlog", 624 | label: "bug", 625 | priority: "high", 626 | }, 627 | { 628 | id: "TASK-2645", 629 | title: 630 | "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!", 631 | status: "todo", 632 | label: "documentation", 633 | priority: "medium", 634 | }, 635 | { 636 | id: "TASK-4535", 637 | title: 638 | "Try to copy the JSON circuit, maybe it will connect the wireless feed!", 639 | status: "in progress", 640 | label: "feature", 641 | priority: "low", 642 | }, 643 | { 644 | id: "TASK-4463", 645 | title: "We need to copy the solid state AGP monitor!", 646 | status: "done", 647 | label: "documentation", 648 | priority: "low", 649 | }, 650 | { 651 | id: "TASK-9745", 652 | title: 653 | "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!", 654 | status: "canceled", 655 | label: "feature", 656 | priority: "high", 657 | }, 658 | { 659 | id: "TASK-2080", 660 | title: 661 | "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!", 662 | status: "todo", 663 | label: "bug", 664 | priority: "medium", 665 | }, 666 | { 667 | id: "TASK-3838", 668 | title: 669 | "I'll bypass the online TCP application, that should panel the AGP system!", 670 | status: "backlog", 671 | label: "bug", 672 | priority: "high", 673 | }, 674 | { 675 | id: "TASK-1340", 676 | title: "We need to navigate the virtual PNG circuit!", 677 | status: "todo", 678 | label: "bug", 679 | priority: "medium", 680 | }, 681 | { 682 | id: "TASK-6665", 683 | title: 684 | "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!", 685 | status: "canceled", 686 | label: "feature", 687 | priority: "low", 688 | }, 689 | { 690 | id: "TASK-7585", 691 | title: 692 | "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!", 693 | status: "backlog", 694 | label: "feature", 695 | priority: "low", 696 | }, 697 | { 698 | id: "TASK-6319", 699 | title: "We need to copy the multi-byte SCSI program!", 700 | status: "backlog", 701 | label: "bug", 702 | priority: "high", 703 | }, 704 | { 705 | id: "TASK-4369", 706 | title: "Try to input the SCSI bus, maybe it will generate the 1080p pixel!", 707 | status: "backlog", 708 | label: "bug", 709 | priority: "high", 710 | }, 711 | { 712 | id: "TASK-9035", 713 | title: "We need to override the solid state PNG array!", 714 | status: "canceled", 715 | label: "documentation", 716 | priority: "low", 717 | }, 718 | { 719 | id: "TASK-3970", 720 | title: 721 | "You can't index the transmitter without quantifying the haptic ASCII card!", 722 | status: "todo", 723 | label: "documentation", 724 | priority: "medium", 725 | }, 726 | { 727 | id: "TASK-4473", 728 | title: 729 | "You can't bypass the protocol without overriding the neural RSS program!", 730 | status: "todo", 731 | label: "documentation", 732 | priority: "low", 733 | }, 734 | { 735 | id: "TASK-4136", 736 | title: 737 | "You can't hack the hard drive without hacking the primary JSON program!", 738 | status: "canceled", 739 | label: "bug", 740 | priority: "medium", 741 | }, 742 | { 743 | id: "TASK-3939", 744 | title: 745 | "Use the back-end SQL firewall, then you can connect the neural hard drive!", 746 | status: "done", 747 | label: "feature", 748 | priority: "low", 749 | }, 750 | { 751 | id: "TASK-2007", 752 | title: 753 | "I'll input the back-end USB protocol, that should bandwidth the PCI system!", 754 | status: "backlog", 755 | label: "bug", 756 | priority: "high", 757 | }, 758 | { 759 | id: "TASK-7516", 760 | title: 761 | "Use the primary SQL program, then you can generate the auxiliary transmitter!", 762 | status: "done", 763 | label: "documentation", 764 | priority: "medium", 765 | }, 766 | { 767 | id: "TASK-6906", 768 | title: 769 | "Try to back up the DRAM system, maybe it will reboot the online transmitter!", 770 | status: "done", 771 | label: "feature", 772 | priority: "high", 773 | }, 774 | { 775 | id: "TASK-5207", 776 | title: 777 | "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!", 778 | status: "in progress", 779 | label: "bug", 780 | priority: "low", 781 | }, 782 | ]; 783 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 5.9% 10%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 10% 3.9%; 26 | --chart-1: 12 76% 61%; 27 | --chart-2: 173 58% 39%; 28 | --chart-3: 197 37% 24%; 29 | --chart-4: 43 74% 66%; 30 | --chart-5: 27 87% 67%; 31 | --radius: 0.5rem; 32 | } 33 | .dark { 34 | --background: 240 10% 3.9%; 35 | --foreground: 0 0% 98%; 36 | --card: 240 10% 3.9%; 37 | --card-foreground: 0 0% 98%; 38 | --popover: 240 10% 3.9%; 39 | --popover-foreground: 0 0% 98%; 40 | --primary: 0 0% 98%; 41 | --primary-foreground: 240 5.9% 10%; 42 | --secondary: 240 3.7% 15.9%; 43 | --secondary-foreground: 0 0% 98%; 44 | --muted: 240 3.7% 15.9%; 45 | --muted-foreground: 240 5% 64.9%; 46 | --accent: 240 3.7% 15.9%; 47 | --accent-foreground: 0 0% 98%; 48 | --destructive: 0 62.8% 30.6%; 49 | --destructive-foreground: 0 0% 98%; 50 | --border: 240 3.7% 15.9%; 51 | --input: 240 3.7% 15.9%; 52 | --ring: 240 4.9% 83.9%; 53 | --chart-1: 220 70% 50%; 54 | --chart-2: 160 60% 45%; 55 | --chart-3: 30 80% 55%; 56 | --chart-4: 280 65% 60%; 57 | --chart-5: 340 75% 55%; 58 | } 59 | } 60 | 61 | @layer base { 62 | * { 63 | @apply border-border; 64 | } 65 | body { 66 | @apply bg-background text-foreground; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.tsx' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], 5 | theme: { 6 | extend: { 7 | borderRadius: { 8 | lg: 'var(--radius)', 9 | md: 'calc(var(--radius) - 2px)', 10 | sm: 'calc(var(--radius) - 4px)' 11 | }, 12 | colors: { 13 | background: 'hsl(var(--background))', 14 | foreground: 'hsl(var(--foreground))', 15 | card: { 16 | DEFAULT: 'hsl(var(--card))', 17 | foreground: 'hsl(var(--card-foreground))' 18 | }, 19 | popover: { 20 | DEFAULT: 'hsl(var(--popover))', 21 | foreground: 'hsl(var(--popover-foreground))' 22 | }, 23 | primary: { 24 | DEFAULT: 'hsl(var(--primary))', 25 | foreground: 'hsl(var(--primary-foreground))' 26 | }, 27 | secondary: { 28 | DEFAULT: 'hsl(var(--secondary))', 29 | foreground: 'hsl(var(--secondary-foreground))' 30 | }, 31 | muted: { 32 | DEFAULT: 'hsl(var(--muted))', 33 | foreground: 'hsl(var(--muted-foreground))' 34 | }, 35 | accent: { 36 | DEFAULT: 'hsl(var(--accent))', 37 | foreground: 'hsl(var(--accent-foreground))' 38 | }, 39 | destructive: { 40 | DEFAULT: 'hsl(var(--destructive))', 41 | foreground: 'hsl(var(--destructive-foreground))' 42 | }, 43 | border: 'hsl(var(--border))', 44 | input: 'hsl(var(--input))', 45 | ring: 'hsl(var(--ring))', 46 | chart: { 47 | '1': 'hsl(var(--chart-1))', 48 | '2': 'hsl(var(--chart-2))', 49 | '3': 'hsl(var(--chart-3))', 50 | '4': 'hsl(var(--chart-4))', 51 | '5': 'hsl(var(--chart-5))' 52 | } 53 | } 54 | } 55 | }, 56 | plugins: [require("tailwindcss-animate")], 57 | }; 58 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ], 11 | "compilerOptions": { 12 | "baseUrl": ".", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import react from "@vitejs/plugin-react"; 3 | import { defineConfig } from "vite"; 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }); 13 | --------------------------------------------------------------------------------