Skip to content

Commit

Permalink
wip(ban-tpl): added dropdown for the web modal
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed May 6, 2024
1 parent 8a8b7ec commit 0a26d17
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 13 deletions.
116 changes: 116 additions & 0 deletions panel/src/components/dropDownSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { ChevronDown, ChevronUp } from "lucide-react"

import { cn } from "@/lib/utils"

const DropDownSelect = SelectPrimitive.Root

const DropDownSelectValue = SelectPrimitive.Value

const DropDownSelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ children }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
asChild
>
{children}
</SelectPrimitive.Trigger>
))
DropDownSelectTrigger.displayName = SelectPrimitive.Trigger.displayName

const DropDownSelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
DropDownSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName

const DropDownSelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
DropDownSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName

const DropDownSelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<DropDownSelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<DropDownSelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
DropDownSelectContent.displayName = SelectPrimitive.Content.displayName


const DropDownSelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
DropDownSelectItem.displayName = SelectPrimitive.Item.displayName

export {
DropDownSelect,
DropDownSelectValue,
DropDownSelectTrigger,
DropDownSelectItem,
DropDownSelectContent,
DropDownSelectScrollUpButton,
DropDownSelectScrollDownButton,
}
127 changes: 114 additions & 13 deletions panel/src/layout/PlayerModal/PlayerBanTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,31 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useAdminPerms } from "@/hooks/auth";
import { PlayerModalRefType, useClosePlayerModal } from "@/hooks/playerModal";
import { Loader2Icon } from "lucide-react";
import { useRef, useState } from "react";
import { ClipboardPasteIcon, ExternalLinkIcon, Loader2Icon } from "lucide-react";
import { useMemo, useRef, useState } from "react";
import { useBackendApi } from "@/hooks/fetch";
import { GenericApiOkResp } from "@shared/genericApiTypes";
import ModalCentralMessage from "@/components/ModalCentralMessage";
import { DropDownSelect, DropDownSelectContent, DropDownSelectItem, DropDownSelectTrigger } from "@/components/dropDownSelect";
import { banDurationToShortString, banDurationToString, cn } from "@/lib/utils";
import { Link, useLocation } from "wouter";
import { DynamicNewItem } from "@/components/DynamicNewBadge";
import type { BanTemplatesDataType } from "@shared/otherTypes";


export default function PlayerBanTab({ playerRef }: { playerRef: PlayerModalRefType }) {
const ADD_NEW_SELECT_OPTION = '!add-new';
const maxReasonSize = 150;
const defaultDurations = ['permanent', '2 hours', '8 hours', '1 day', '2 days', '1 week', '2 weeks'];

type PlayerBanTabProps = {
banTemplates: BanTemplatesDataType[];
playerRef: PlayerModalRefType;
};

export default function PlayerBanTab({ playerRef, banTemplates }: PlayerBanTabProps) {
const reasonRef = useRef<HTMLInputElement>(null);
const customMultiplierRef = useRef<HTMLInputElement>(null);
const setLocation = useLocation()[1];
const [currentDuration, setCurrentDuration] = useState('2 days');
const [customUnits, setCustomUnits] = useState('days');
const [isSaving, setIsSaving] = useState(false);
Expand Down Expand Up @@ -54,20 +69,106 @@ export default function PlayerBanTab({ playerRef }: { playerRef: PlayerModalRefT
});
};

const handleTemplateSelectChange = (value: string) => {
if (value === ADD_NEW_SELECT_OPTION) {
setLocation('/settings/ban-templates');
closeModal();
} else {
const template = banTemplates.find(template => template.id === value);
if (!template) return;

const processedDuration = banDurationToString(template.duration);
if (defaultDurations.includes(processedDuration)) {
setCurrentDuration(processedDuration);
} else if (typeof template.duration === 'object') {
setCurrentDuration('custom');
customMultiplierRef.current!.value = template.duration.value.toString();
setCustomUnits(template.duration.unit);
}

reasonRef.current!.value = template.reason;
setTimeout(() => {
reasonRef.current!.focus();
}, 50);
}
}

//Optimization
const processedTemplates = useMemo(() => {
return banTemplates.map((template, index) => {
const duration = banDurationToShortString(template.duration);
const reason = template.reason.length > maxReasonSize
? template.reason.slice(0, maxReasonSize - 3) + '...'
: template.reason;
return (
<DropDownSelectItem key={index} value={template.id}>
<span
className="inline-block pr-1 font-mono opacity-75 min-w-[4ch]"
>{duration}</span> {reason}
</DropDownSelectItem>
);
});
}, [banTemplates]);

return (
<form onSubmit={handleSubmit} className="grid gap-4 p-1">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="banReason" className="col-span-4 xs:col-auto">
Reason
</Label>
<Input
id="banReason"
placeholder="The reason for the ban, rule violated, etc."
className="col-span-full xs:col-span-3"
ref={reasonRef}
autoFocus
required
/>
<div className="col-span-full xs:col-span-3 flex gap-1">
<Input
id="banReason"
placeholder="The reason for the ban, rule violated, etc."
className="w-full"
ref={reasonRef}
autoFocus
required
/>
<DropDownSelect onValueChange={handleTemplateSelectChange}>
<DropDownSelectTrigger className="tracking-wide">
<button
className={cn(
'relative', //FIXME: REMOVE THIS LINE - DynamicNewItem
'size-10 inline-flex justify-center items-center rounded-md shrink-0',
'ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'border bg-black/20 shadow-sm',
'hover:bg-primary hover:text-primary-foreground hover:border-primary',
)}
>
<ClipboardPasteIcon className="size-5" />
<DynamicNewItem featName="banTemplates" durationDays={7}>
<div className="absolute rounded-full size-2 -top-1 -right-1 bg-accent" />
</DynamicNewItem>
</button>
</DropDownSelectTrigger>
<DropDownSelectContent className="tracking-wide max-w-screen-sm" align="center">
{!banTemplates.length ? (
<div className="text-warning-inline text-center p-4">
You do not have any template configured. <br />
<Link
href="/settings/ban-templates"
className="cursor-pointer underline hover:text-accent"
onClick={() => { closeModal(); }}
>
Add Ban Template
<ExternalLinkIcon className="inline mr-1 h-4" />
</Link>
</div>
) : null}
{processedTemplates}
{banTemplates.length ? (
<DropDownSelectItem
value={ADD_NEW_SELECT_OPTION}
className="font-bold text-warning-inline"
>
Add Ban Template
<ExternalLinkIcon className="inline mr-1 h-4" />
</DropDownSelectItem>
) : null}
</DropDownSelectContent>
</DropDownSelect>
</div>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="durationSelect" className="col-span-4 xs:col-auto">
Expand All @@ -76,7 +177,7 @@ export default function PlayerBanTab({ playerRef }: { playerRef: PlayerModalRefT
<div className="col-span-full xs:col-span-3 space-y-1">
<Select
onValueChange={setCurrentDuration}
defaultValue={currentDuration}
value={currentDuration}
>
<SelectTrigger id="durationSelect" className="tracking-wide">
<SelectValue placeholder="Select Duration" />
Expand All @@ -103,7 +204,7 @@ export default function PlayerBanTab({ playerRef }: { playerRef: PlayerModalRefT
/>
<Select
onValueChange={setCustomUnits}
defaultValue={customUnits}
value={customUnits}
>
<SelectTrigger
className="tracking-wide"
Expand Down

0 comments on commit 0a26d17

Please sign in to comment.