put status options in a dropdown

This commit is contained in:
Oliver Bryan
2026-01-10 23:53:07 +00:00
parent 6ada790581
commit 69c8ac7bd0
2 changed files with 83 additions and 51 deletions

View File

@@ -4,7 +4,7 @@ import {
type OrganisationMemberResponse,
type OrganisationResponse,
} from "@issue/shared";
import { ChevronDown, ChevronUp, Plus, X } from "lucide-react";
import { ChevronDown, ChevronUp, EllipsisVertical, Plus, X } from "lucide-react";
import type { ReactNode } from "react";
import { useCallback, useEffect, useState } from "react";
import { AddMemberDialog } from "@/components/add-member-dialog";
@@ -16,6 +16,12 @@ import { Button } from "@/components/ui/button";
import ColourPicker from "@/components/ui/colour-picker";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -207,6 +213,7 @@ function OrganisationsDialog({
newStatuses[trimmed] = newStatusColour;
await updateStatuses(newStatuses);
setNewStatusName("");
setNewStatusColour(DEFAULT_STATUS_COLOUR);
setIsCreatingStatus(false);
setStatusError(null);
};
@@ -421,42 +428,58 @@ function OrganisationsDialog({
/>
</div>
{isAdmin && (
<div className="flex items-center gap-2">
<Button
variant="dummy"
size="none"
disabled={index === 0}
onClick={() => void moveStatus(status, "up")}
aria-label="Move status up"
<DropdownMenu>
<DropdownMenuTrigger
asChild
size={"sm"}
noStyle
className="hover:opacity-80 cursor-pointer"
>
<ChevronUp className="size-5 text-muted-foreground" />
</Button>
<Button
variant="dummy"
size="none"
disabled={
index === Object.keys(statuses).length - 1
}
onClick={() =>
void moveStatus(status, "down")
}
aria-label="Move status down"
<EllipsisVertical className="size-4 text-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
sideOffset={4}
className="bg-background"
>
<ChevronDown className="size-5 text-muted-foreground" />
</Button>
{Object.keys(statuses).length > 1 && (
<Button
variant="dummy"
size="none"
onClick={() =>
<DropdownMenuItem
disabled={index === 0}
onSelect={() =>
void moveStatus(status, "up")
}
className="hover:bg-primary-foreground"
>
<ChevronUp className="size-4 text-muted-foreground" />
Move up
</DropdownMenuItem>
<DropdownMenuItem
disabled={
index ===
Object.keys(statuses).length - 1
}
onSelect={() =>
void moveStatus(status, "down")
}
className="hover:bg-primary-foreground"
>
<ChevronDown className="size-4 text-muted-foreground" />
Move down
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
disabled={
Object.keys(statuses).length <= 1
}
onSelect={() =>
handleRemoveStatusClick(status)
}
aria-label="Remove status"
className="hover:bg-destructive/10"
>
<X className="size-5 text-destructive" />
</Button>
)}
</div>
<X className="size-4" />
Remove
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
))}
@@ -564,19 +587,22 @@ function OrganisationsDialog({
}
}}
>
<DialogContent>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Remove Status</DialogTitle>
</DialogHeader>
<p className="text-sm text-muted-foreground">
Are you sure you want to remove the "{statusToRemove}" status? Which status
would you like issues with this status to be set to?
Are you sure you want to remove the{" "}
{statusToRemove ? (
<StatusTag status={statusToRemove} colour={statuses[statusToRemove]} />
) : null}{" "}
status? Which status would you like issues with this status to be set to?
</p>
<Select value={reassignToStatus} onValueChange={setReassignToStatus}>
<SelectTrigger className="w-full">
<SelectTrigger className="w-min">
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
<SelectContent side={"bottom"} position="popper" align="start">
{Object.keys(statuses)
.filter((s) => s !== statusToRemove)
.map((status) => (

View File

@@ -14,28 +14,34 @@ function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMe
function DropdownMenuTrigger({
className,
size = "default",
noStyle = false,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger> & {
size?: "sm" | "default";
noStyle?: boolean;
}) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
data-size={size}
className={cn(
"cursor-pointer border data-[placeholder]:text-muted-foreground",
"[&_svg:not([class*='text-'])]:text-foreground",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"aria-invalid:border-destructive dark:hover:bg-muted/40",
"flex w-fit items-center justify-between gap-2 border",
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
"shadow-xs outline-none disabled:cursor-not-allowed",
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
className={
noStyle
? cn(className)
: cn(
"cursor-pointer border data-[placeholder]:text-muted-foreground",
"[&_svg:not([class*='text-'])]:text-foreground",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"aria-invalid:border-destructive dark:hover:bg-muted/40",
"flex w-fit items-center justify-between gap-2 border",
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
"shadow-xs outline-none disabled:cursor-not-allowed",
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)
}
{...props}
/>
);