mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-31 12:29:32 +02:00
177 lines
4.7 KiB
TypeScript
177 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import type { Table } from "@tanstack/react-table";
|
|
import { AnimatePresence, motion } from "motion/react";
|
|
import * as React from "react";
|
|
import * as ReactDOM from "react-dom";
|
|
import { LuX } from "react-icons/lu";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface DataTableActionBarProps<TData>
|
|
extends React.ComponentProps<typeof motion.div> {
|
|
table: Table<TData>;
|
|
visible?: boolean;
|
|
portalContainer?: Element | DocumentFragment | null;
|
|
}
|
|
|
|
function DataTableActionBar<TData>({
|
|
table,
|
|
visible: visibleProp,
|
|
portalContainer: portalContainerProp,
|
|
children,
|
|
className,
|
|
...props
|
|
}: DataTableActionBarProps<TData>) {
|
|
const [mounted, setMounted] = React.useState(false);
|
|
React.useLayoutEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
function onKeyDown(event: KeyboardEvent) {
|
|
if (event.key === "Escape") {
|
|
table.toggleAllRowsSelected(false);
|
|
}
|
|
}
|
|
window.addEventListener("keydown", onKeyDown);
|
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
}, [table]);
|
|
|
|
const portalContainer =
|
|
portalContainerProp ?? (mounted ? globalThis.document?.body : null);
|
|
|
|
if (!portalContainer) return null;
|
|
|
|
const visible =
|
|
visibleProp ?? table.getFilteredSelectedRowModel().rows.length > 0;
|
|
|
|
return ReactDOM.createPortal(
|
|
<AnimatePresence>
|
|
{visible && (
|
|
<motion.div
|
|
role="toolbar"
|
|
aria-orientation="horizontal"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: 20 }}
|
|
transition={{ duration: 0.2, ease: "easeInOut" }}
|
|
className={cn(
|
|
"fixed inset-x-0 bottom-6 z-50 mx-auto flex w-fit flex-wrap items-center justify-center gap-2 rounded-md border bg-background p-2 text-foreground shadow-sm",
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>,
|
|
portalContainer,
|
|
);
|
|
}
|
|
|
|
interface DataTableActionBarActionProps
|
|
extends React.ComponentProps<typeof Button> {
|
|
tooltip?: string;
|
|
isPending?: boolean;
|
|
}
|
|
|
|
function DataTableActionBarAction({
|
|
size = "sm",
|
|
tooltip,
|
|
isPending,
|
|
disabled,
|
|
className,
|
|
children,
|
|
...props
|
|
}: DataTableActionBarActionProps) {
|
|
const trigger = (
|
|
<Button
|
|
variant="secondary"
|
|
size={size}
|
|
className={cn(
|
|
"gap-1.5 border border-secondary bg-secondary/50 hover:bg-secondary/70 [&>svg]:size-3.5",
|
|
size === "icon" ? "size-7" : "h-7",
|
|
className,
|
|
)}
|
|
disabled={disabled || isPending}
|
|
{...props}
|
|
>
|
|
{isPending ? (
|
|
<div className="w-3.5 h-3.5 rounded-full border border-current animate-spin border-t-transparent" />
|
|
) : (
|
|
children
|
|
)}
|
|
</Button>
|
|
);
|
|
|
|
if (!tooltip) return trigger;
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
|
<TooltipContent
|
|
sideOffset={6}
|
|
className="border bg-accent font-semibold text-foreground dark:bg-zinc-900 [&>span]:hidden"
|
|
>
|
|
<p>{tooltip}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
interface DataTableActionBarSelectionProps<TData> {
|
|
table: Table<TData>;
|
|
}
|
|
|
|
function DataTableActionBarSelection<TData>({
|
|
table,
|
|
}: DataTableActionBarSelectionProps<TData>) {
|
|
const onClearSelection = React.useCallback(() => {
|
|
table.toggleAllRowsSelected(false);
|
|
}, [table]);
|
|
|
|
return (
|
|
<div className="flex h-7 items-center rounded-md border pr-1 pl-2.5">
|
|
<span className="whitespace-nowrap text-xs">
|
|
{table.getFilteredSelectedRowModel().rows.length} selected
|
|
</span>
|
|
<div className="mr-1 ml-2 h-4 w-px bg-border" />
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-5"
|
|
onClick={onClearSelection}
|
|
>
|
|
<LuX className="size-3.5" />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent
|
|
sideOffset={10}
|
|
className="flex items-center gap-2 border bg-accent px-2 py-1 font-semibold text-foreground dark:bg-zinc-900 [&>span]:hidden"
|
|
>
|
|
<p>Clear selection</p>
|
|
<kbd className="select-none rounded border bg-background px-1.5 py-px font-mono font-normal text-[0.7rem] text-foreground shadow-xs">
|
|
<abbr title="Escape" className="no-underline">
|
|
Esc
|
|
</abbr>
|
|
</kbd>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export {
|
|
DataTableActionBar,
|
|
DataTableActionBarAction,
|
|
DataTableActionBarSelection,
|
|
};
|