mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
status tag component and implmentation
This commit is contained in:
@@ -4,8 +4,10 @@ import { useEffect, useState } from "react";
|
|||||||
import { useSession } from "@/components/session-provider";
|
import { useSession } from "@/components/session-provider";
|
||||||
import SmallUserDisplay from "@/components/small-user-display";
|
import SmallUserDisplay from "@/components/small-user-display";
|
||||||
import { StatusSelect } from "@/components/status-select";
|
import { StatusSelect } from "@/components/status-select";
|
||||||
|
import StatusTag from "@/components/status-tag";
|
||||||
import { TimerModal } from "@/components/timer-modal";
|
import { TimerModal } from "@/components/timer-modal";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { SelectTrigger } from "@/components/ui/select";
|
||||||
import { UserSelect } from "@/components/user-select";
|
import { UserSelect } from "@/components/user-select";
|
||||||
import { issue } from "@/lib/server";
|
import { issue } from "@/lib/server";
|
||||||
import { issueID } from "@/lib/utils";
|
import { issueID } from "@/lib/utils";
|
||||||
@@ -84,9 +86,23 @@ export function IssueDetailPane({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col w-full p-2 py-2 gap-2">
|
<div className="flex flex-col w-full p-2 py-2 gap-2">
|
||||||
<div className="flex gap-2 -mt-1 -ml-1">
|
<div className="flex gap-2">
|
||||||
<StatusSelect statuses={statuses} value={status} onChange={handleStatusChange} />
|
<StatusSelect
|
||||||
<div className="flex w-full h-8 border-b items-center min-w-0">
|
statuses={statuses}
|
||||||
|
value={status}
|
||||||
|
onChange={handleStatusChange}
|
||||||
|
trigger={({ isOpen, value }) => (
|
||||||
|
<SelectTrigger
|
||||||
|
className="group w-auto flex items-center"
|
||||||
|
variant="unstyled"
|
||||||
|
chevronClassName="hidden"
|
||||||
|
isOpen={isOpen}
|
||||||
|
>
|
||||||
|
<StatusTag status={value} className="group-hover:bg-foreground/75" />
|
||||||
|
</SelectTrigger>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex w-full items-center min-w-0">
|
||||||
<span className="block w-full truncate">{issueData.Issue.title}</span>
|
<span className="block w-full truncate">{issueData.Issue.title}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { IssueResponse } from "@issue/shared";
|
import type { IssueResponse } from "@issue/shared";
|
||||||
import Avatar from "@/components/avatar";
|
import Avatar from "@/components/avatar";
|
||||||
|
import StatusTag from "@/components/status-tag";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -47,9 +48,7 @@ export function IssuesTable({
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="flex items-center gap-2 max-w-full truncate">
|
<span className="flex items-center gap-2 max-w-full truncate">
|
||||||
{(columns.status == null || columns.status === true) && (
|
{(columns.status == null || columns.status === true) && (
|
||||||
<div className="text-xs px-1 bg-foreground/85 rounded text-background">
|
<StatusTag status={issueData.Issue.status} />
|
||||||
{issueData.Issue.status}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{issueData.Issue.title}
|
{issueData.Issue.title}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,37 +1,41 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import StatusTag from "./status-tag";
|
||||||
|
|
||||||
export function StatusSelect({
|
export function StatusSelect({
|
||||||
statuses,
|
statuses,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = "Select status",
|
placeholder = "Select status",
|
||||||
|
trigger,
|
||||||
}: {
|
}: {
|
||||||
statuses: string[];
|
statuses: string[];
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
trigger?: (args: { isOpen: boolean; value: string }) => ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select value={value} onValueChange={onChange} onOpenChange={setIsOpen}>
|
<Select value={value} onValueChange={onChange} onOpenChange={setIsOpen}>
|
||||||
<SelectTrigger
|
{trigger ? (
|
||||||
className="w-fit px-2 text-xs gap-1"
|
trigger({ isOpen, value })
|
||||||
size="sm"
|
) : (
|
||||||
chevronClassName={"size-3 -mr-1"}
|
<SelectTrigger
|
||||||
isOpen={isOpen}
|
className="w-fit px-2 text-xs gap-1"
|
||||||
>
|
size="sm"
|
||||||
<SelectValue placeholder={placeholder}>{value}</SelectValue>
|
chevronClassName={"size-3 -mr-1"}
|
||||||
</SelectTrigger>
|
isOpen={isOpen}
|
||||||
<SelectContent
|
>
|
||||||
side="bottom"
|
<SelectValue placeholder={placeholder}>{value}</SelectValue>
|
||||||
position="popper"
|
</SelectTrigger>
|
||||||
align="start"
|
)}
|
||||||
>
|
<SelectContent side="bottom" position="popper" align="start">
|
||||||
{statuses.map((status) => (
|
{statuses.map((status) => (
|
||||||
<SelectItem key={status} value={status} textClassName="text-xs">
|
<SelectItem key={status} value={status} textClassName="text-xs">
|
||||||
{status}
|
<StatusTag status={status} className="" />
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
14
packages/frontend/src/components/status-tag.tsx
Normal file
14
packages/frontend/src/components/status-tag.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export default function StatusTag({ status, className }: { status: string; className?: string }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"text-xs px-1 bg-foreground/85 rounded text-background inline-flex whitespace-nowrap",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.V
|
|||||||
function SelectTrigger({
|
function SelectTrigger({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
|
variant = "default",
|
||||||
children,
|
children,
|
||||||
isOpen,
|
isOpen,
|
||||||
label,
|
label,
|
||||||
@@ -29,6 +30,7 @@ function SelectTrigger({
|
|||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
size?: "sm" | "default";
|
size?: "sm" | "default";
|
||||||
|
variant?: "default" | "unstyled";
|
||||||
label?: string;
|
label?: string;
|
||||||
hasValue?: boolean;
|
hasValue?: boolean;
|
||||||
labelPosition?: "top" | "bottom";
|
labelPosition?: "top" | "bottom";
|
||||||
@@ -39,17 +41,21 @@ function SelectTrigger({
|
|||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer border data-[placeholder]:text-muted-foreground",
|
variant === "unstyled"
|
||||||
"[&_svg:not([class*='text-'])]:text-muted-foreground",
|
? "cursor-pointer bg-transparent shadow-none outline-none"
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
: [
|
||||||
"aria-invalid:border-destructive dark:hover:bg-muted/40",
|
"cursor-pointer border data-[placeholder]:text-muted-foreground",
|
||||||
"relative flex w-fit items-center justify-between gap-2 border",
|
"[&_svg:not([class*='text-'])]:text-muted-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",
|
"relative 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",
|
||||||
|
"*: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,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
Reference in New Issue
Block a user