Skip to content

Commit

Permalink
Add placeholder view
Browse files Browse the repository at this point in the history
  • Loading branch information
abefernan committed Sep 30, 2024
1 parent 55937c7 commit db8d312
Show file tree
Hide file tree
Showing 12 changed files with 1,554 additions and 0 deletions.
121 changes: 121 additions & 0 deletions app/tasks/components/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";

import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { ColumnDef } from "@tanstack/react-table";
import { labels, priorities, statuses } from "../data/data";
import { Task } from "../data/schema";
import { DataTableColumnHeader } from "./data-table-column-header";
import { DataTableRowActions } from "./data-table-row-actions";

export const columns: ColumnDef<Task>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
className="translate-y-[2px]"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
className="translate-y-[2px]"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "id",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Task" />
),
cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "title",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Title" />
),
cell: ({ row }) => {
const label = labels.find((label) => label.value === row.original.label);

return (
<div className="flex space-x-2">
{label && <Badge variant="outline">{label.label}</Badge>}
<span className="max-w-[500px] truncate font-medium">
{row.getValue("title")}
</span>
</div>
);
},
},
{
accessorKey: "status",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Status" />
),
cell: ({ row }) => {
const status = statuses.find(
(status) => status.value === row.getValue("status"),
);

if (!status) {
return null;
}

return (
<div className="flex w-[100px] items-center">
{status.icon && (
<status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{status.label}</span>
</div>
);
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
},
{
accessorKey: "priority",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Priority" />
),
cell: ({ row }) => {
const priority = priorities.find(
(priority) => priority.value === row.getValue("priority"),
);

if (!priority) {
return null;
}

return (
<div className="flex items-center">
{priority.icon && (
<priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{priority.label}</span>
</div>
);
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
},
{
id: "actions",
cell: ({ row }) => <DataTableRowActions row={row} />,
},
];
70 changes: 70 additions & 0 deletions app/tasks/components/data-table-column-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import {
ArrowDownIcon,
ArrowUpIcon,
CaretSortIcon,
EyeNoneIcon,
} from "@radix-ui/react-icons";
import { Column } from "@tanstack/react-table";

interface DataTableColumnHeaderProps<TData, TValue>
extends React.HTMLAttributes<HTMLDivElement> {
column: Column<TData, TValue>;
title: string;
}

export function DataTableColumnHeader<TData, TValue>({
column,
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
if (!column.getCanSort()) {
return <div className={cn(className)}>{title}</div>;
}

return (
<div className={cn("flex items-center space-x-2", className)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="-ml-3 h-8 data-[state=open]:bg-accent"
>
<span>{title}</span>
{column.getIsSorted() === "desc" ? (
<ArrowDownIcon className="ml-2 h-4 w-4" />
) : column.getIsSorted() === "asc" ? (
<ArrowUpIcon className="ml-2 h-4 w-4" />
) : (
<CaretSortIcon className="ml-2 h-4 w-4" />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
<ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Asc
</DropdownMenuItem>
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
<ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Desc
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
<EyeNoneIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Hide
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
146 changes: 146 additions & 0 deletions app/tasks/components/data-table-faceted-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons";
import { Column } from "@tanstack/react-table";
import * as React from "react";

interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>;
title?: string;
options: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}

export function DataTableFacetedFilter<TData, TValue>({
column,
title,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
const facets = column?.getFacetedUniqueValues();
const selectedValues = new Set(column?.getFilterValue() as string[]);

return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 border-dashed">
<PlusCircledIcon className="mr-2 h-4 w-4" />
{title}
{selectedValues?.size > 0 && (
<>
<Separator orientation="vertical" className="mx-2 h-4" />
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal lg:hidden"
>
{selectedValues.size}
</Badge>
<div className="hidden space-x-1 lg:flex">
{selectedValues.size > 2 ? (
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal"
>
{selectedValues.size} selected
</Badge>
) : (
options
.filter((option) => selectedValues.has(option.value))
.map((option) => (
<Badge
variant="secondary"
key={option.value}
className="rounded-sm px-1 font-normal"
>
{option.label}
</Badge>
))
)}
</div>
</>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<Command>
<CommandInput placeholder={title} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
{options.map((option) => {
const isSelected = selectedValues.has(option.value);
return (
<CommandItem
key={option.value}
onSelect={() => {
if (isSelected) {
selectedValues.delete(option.value);
} else {
selectedValues.add(option.value);
}
const filterValues = Array.from(selectedValues);
column?.setFilterValue(
filterValues.length ? filterValues : undefined,
);
}}
>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
isSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible",
)}
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
{option.icon && (
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{option.label}</span>
{facets?.get(option.value) && (
<span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
{facets.get(option.value)}
</span>
)}
</CommandItem>
);
})}
</CommandGroup>
{selectedValues.size > 0 && (
<>
<CommandSeparator />
<CommandGroup>
<CommandItem
onSelect={() => column?.setFilterValue(undefined)}
className="justify-center text-center"
>
Clear filters
</CommandItem>
</CommandGroup>
</>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
Loading

0 comments on commit db8d312

Please sign in to comment.