mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 10:33:01 +00:00
put status options in a dropdown
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
|||||||
type OrganisationMemberResponse,
|
type OrganisationMemberResponse,
|
||||||
type OrganisationResponse,
|
type OrganisationResponse,
|
||||||
} from "@issue/shared";
|
} 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 type { ReactNode } from "react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { AddMemberDialog } from "@/components/add-member-dialog";
|
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 ColourPicker from "@/components/ui/colour-picker";
|
||||||
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/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 { Input } from "@/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
@@ -207,6 +213,7 @@ function OrganisationsDialog({
|
|||||||
newStatuses[trimmed] = newStatusColour;
|
newStatuses[trimmed] = newStatusColour;
|
||||||
await updateStatuses(newStatuses);
|
await updateStatuses(newStatuses);
|
||||||
setNewStatusName("");
|
setNewStatusName("");
|
||||||
|
setNewStatusColour(DEFAULT_STATUS_COLOUR);
|
||||||
setIsCreatingStatus(false);
|
setIsCreatingStatus(false);
|
||||||
setStatusError(null);
|
setStatusError(null);
|
||||||
};
|
};
|
||||||
@@ -421,42 +428,58 @@ function OrganisationsDialog({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<div className="flex items-center gap-2">
|
<DropdownMenu>
|
||||||
<Button
|
<DropdownMenuTrigger
|
||||||
variant="dummy"
|
asChild
|
||||||
size="none"
|
size={"sm"}
|
||||||
disabled={index === 0}
|
noStyle
|
||||||
onClick={() => void moveStatus(status, "up")}
|
className="hover:opacity-80 cursor-pointer"
|
||||||
aria-label="Move status up"
|
|
||||||
>
|
>
|
||||||
<ChevronUp className="size-5 text-muted-foreground" />
|
<EllipsisVertical className="size-4 text-foreground" />
|
||||||
</Button>
|
</DropdownMenuTrigger>
|
||||||
<Button
|
<DropdownMenuContent
|
||||||
variant="dummy"
|
align="end"
|
||||||
size="none"
|
sideOffset={4}
|
||||||
disabled={
|
className="bg-background"
|
||||||
index === Object.keys(statuses).length - 1
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
void moveStatus(status, "down")
|
|
||||||
}
|
|
||||||
aria-label="Move status down"
|
|
||||||
>
|
>
|
||||||
<ChevronDown className="size-5 text-muted-foreground" />
|
<DropdownMenuItem
|
||||||
</Button>
|
disabled={index === 0}
|
||||||
{Object.keys(statuses).length > 1 && (
|
onSelect={() =>
|
||||||
<Button
|
void moveStatus(status, "up")
|
||||||
variant="dummy"
|
}
|
||||||
size="none"
|
className="hover:bg-primary-foreground"
|
||||||
onClick={() =>
|
>
|
||||||
|
<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)
|
handleRemoveStatusClick(status)
|
||||||
}
|
}
|
||||||
aria-label="Remove status"
|
className="hover:bg-destructive/10"
|
||||||
>
|
>
|
||||||
<X className="size-5 text-destructive" />
|
<X className="size-4" />
|
||||||
</Button>
|
Remove
|
||||||
)}
|
</DropdownMenuItem>
|
||||||
</div>
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -564,19 +587,22 @@ function OrganisationsDialog({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Remove Status</DialogTitle>
|
<DialogTitle>Remove Status</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Are you sure you want to remove the "{statusToRemove}" status? Which status
|
Are you sure you want to remove the{" "}
|
||||||
would you like issues with this status to be set to?
|
{statusToRemove ? (
|
||||||
|
<StatusTag status={statusToRemove} colour={statuses[statusToRemove]} />
|
||||||
|
) : null}{" "}
|
||||||
|
status? Which status would you like issues with this status to be set to?
|
||||||
</p>
|
</p>
|
||||||
<Select value={reassignToStatus} onValueChange={setReassignToStatus}>
|
<Select value={reassignToStatus} onValueChange={setReassignToStatus}>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-min">
|
||||||
<SelectValue placeholder="Select status" />
|
<SelectValue placeholder="Select status" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent side={"bottom"} position="popper" align="start">
|
||||||
{Object.keys(statuses)
|
{Object.keys(statuses)
|
||||||
.filter((s) => s !== statusToRemove)
|
.filter((s) => s !== statusToRemove)
|
||||||
.map((status) => (
|
.map((status) => (
|
||||||
|
|||||||
@@ -14,28 +14,34 @@ function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMe
|
|||||||
function DropdownMenuTrigger({
|
function DropdownMenuTrigger({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
|
noStyle = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger> & {
|
||||||
size?: "sm" | "default";
|
size?: "sm" | "default";
|
||||||
|
noStyle?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Trigger
|
<DropdownMenuPrimitive.Trigger
|
||||||
data-slot="dropdown-menu-trigger"
|
data-slot="dropdown-menu-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={
|
||||||
"cursor-pointer border data-[placeholder]:text-muted-foreground",
|
noStyle
|
||||||
"[&_svg:not([class*='text-'])]:text-foreground",
|
? cn(className)
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
: cn(
|
||||||
"aria-invalid:border-destructive dark:hover:bg-muted/40",
|
"cursor-pointer border data-[placeholder]:text-muted-foreground",
|
||||||
"flex w-fit items-center justify-between gap-2 border",
|
"[&_svg:not([class*='text-'])]:text-foreground",
|
||||||
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
||||||
"shadow-xs outline-none disabled:cursor-not-allowed",
|
"aria-invalid:border-destructive dark:hover:bg-muted/40",
|
||||||
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
|
"flex w-fit items-center justify-between gap-2 border",
|
||||||
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
|
"bg-transparent px-3 py-2 text-sm whitespace-nowrap",
|
||||||
"*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
|
"shadow-xs outline-none disabled:cursor-not-allowed",
|
||||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8",
|
||||||
className,
|
"*: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}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user