mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
83 lines
3.0 KiB
TypeScript
83 lines
3.0 KiB
TypeScript
import { Check, ChevronDown } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { TASK_VIEW_GROUPS, taskView, type TaskViewKey } from "@/lib/task-views";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* The single control for Inbox views and task status presets (PAP-670).
|
|
* One menu, grouped by scope, so the
|
|
* nav does not have to grow a row every time a view is added.
|
|
*/
|
|
export function TaskViewsMenu({
|
|
value,
|
|
onChange,
|
|
badgeCount,
|
|
coordinator = false,
|
|
}: {
|
|
value: TaskViewKey;
|
|
onChange: (next: TaskViewKey) => void;
|
|
/** Unread count surfaced next to the My-work group, mirroring the nav badge. */
|
|
badgeCount?: number;
|
|
coordinator?: boolean;
|
|
}) {
|
|
const active = taskView(value);
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-8 gap-1.5"
|
|
aria-label={`Change view — currently ${active.label}`}
|
|
>
|
|
<span className="max-w-(--sz-160px) truncate font-medium">{active.label}</span>
|
|
<ChevronDown aria-hidden="true" className="h-3.5 w-3.5 text-muted-foreground" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start" className="w-(--sz-260px)">
|
|
{TASK_VIEW_GROUPS.map((group, groupIndex) => (
|
|
<div key={group.label}>
|
|
{groupIndex > 0 ? <DropdownMenuSeparator /> : null}
|
|
<DropdownMenuLabel className="flex items-center justify-between gap-2">
|
|
<span>{group.label}</span>
|
|
{groupIndex === 0 && badgeCount != null && badgeCount > 0 ? (
|
|
<span className="rounded-full bg-primary px-1.5 text-(length:--text-nano) leading-tight text-primary-foreground">
|
|
{badgeCount > 99 ? "99+" : badgeCount}
|
|
</span>
|
|
) : null}
|
|
</DropdownMenuLabel>
|
|
{group.views.filter(view => coordinator || !view.coordinatorOnly).map((view) => {
|
|
const selected = view.key === value;
|
|
return (
|
|
<DropdownMenuItem
|
|
key={view.key}
|
|
onSelect={() => onChange(view.key)}
|
|
className="items-start gap-2"
|
|
aria-current={selected ? "true" : undefined}
|
|
>
|
|
<Check
|
|
aria-hidden="true"
|
|
className={cn("mt-0.5 size-3.5 shrink-0", selected ? "opacity-100" : "opacity-0")}
|
|
/>
|
|
<span className="min-w-0">
|
|
<span className={cn("block truncate", selected && "font-medium")}>{view.label}</span>
|
|
<span className="block text-(length:--text-nano) text-muted-foreground">{view.hint}</span>
|
|
</span>
|
|
</DropdownMenuItem>
|
|
);
|
|
})}
|
|
</div>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|