feat: Integrate accordion component for sidebar and update autodraft settings to sync with props
This commit is contained in:
parent
2cd4096e70
commit
32980428f2
8 changed files with 162 additions and 117 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Switch } from "~/components/ui/switch";
|
import { Switch } from "~/components/ui/switch";
|
||||||
import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
|
|
@ -24,6 +24,15 @@ export function AutodraftSettings({
|
||||||
const [localMode, setLocalMode] = useState<"next_pick" | "while_on">(mode);
|
const [localMode, setLocalMode] = useState<"next_pick" | "while_on">(mode);
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
|
||||||
|
// Sync local state with props when they change (from socket events)
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalEnabled(isEnabled);
|
||||||
|
}, [isEnabled]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalMode(mode);
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
const handleToggle = async (checked: boolean) => {
|
const handleToggle = async (checked: boolean) => {
|
||||||
setLocalEnabled(checked);
|
setLocalEnabled(checked);
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { ChevronLeft, ChevronRight, ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "~/components/ui/accordion";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
interface DraftSidebarProps {
|
interface DraftSidebarProps {
|
||||||
|
|
@ -19,59 +24,6 @@ export function DraftSidebar({
|
||||||
participantsSection,
|
participantsSection,
|
||||||
className,
|
className,
|
||||||
}: DraftSidebarProps) {
|
}: DraftSidebarProps) {
|
||||||
// Track which sections are expanded
|
|
||||||
// At least one must be expanded at all times
|
|
||||||
const [queueExpanded, setQueueExpanded] = useState(() => {
|
|
||||||
if (typeof window === "undefined") return true;
|
|
||||||
const stored = localStorage.getItem("draftSidebarQueueExpanded");
|
|
||||||
return stored !== null ? JSON.parse(stored) : true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [participantsExpanded, setParticipantsExpanded] = useState(() => {
|
|
||||||
if (typeof window === "undefined") return true;
|
|
||||||
const stored = localStorage.getItem("draftSidebarParticipantsExpanded");
|
|
||||||
return stored !== null ? JSON.parse(stored) : true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Persist section states to localStorage
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
localStorage.setItem(
|
|
||||||
"draftSidebarQueueExpanded",
|
|
||||||
JSON.stringify(queueExpanded)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [queueExpanded]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
localStorage.setItem(
|
|
||||||
"draftSidebarParticipantsExpanded",
|
|
||||||
JSON.stringify(participantsExpanded)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [participantsExpanded]);
|
|
||||||
|
|
||||||
const toggleQueueSection = () => {
|
|
||||||
// Prevent collapsing if it's the only expanded section
|
|
||||||
if (queueExpanded && !participantsExpanded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setQueueExpanded(!queueExpanded);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleParticipantsSection = () => {
|
|
||||||
// Prevent collapsing if it's the only expanded section
|
|
||||||
if (participantsExpanded && !queueExpanded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setParticipantsExpanded(!participantsExpanded);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate flex sizes for sections
|
|
||||||
const queueFlexSize = queueExpanded && participantsExpanded ? "1" : queueExpanded ? "1" : "0";
|
|
||||||
const participantsFlexSize = queueExpanded && participantsExpanded ? "1" : participantsExpanded ? "1" : "0";
|
|
||||||
|
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -112,62 +64,48 @@ export function DraftSidebar({
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Collapse button */}
|
<Accordion
|
||||||
<div className="absolute top-4 right-2 z-20">
|
type="multiple"
|
||||||
<Button
|
defaultValue={["queue", "participants"]}
|
||||||
variant="ghost"
|
className="flex-1 overflow-hidden"
|
||||||
size="icon"
|
|
||||||
onClick={() => onCollapsedChange(true)}
|
|
||||||
aria-label="Collapse sidebar"
|
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
{/* Queue Section */}
|
||||||
</Button>
|
<AccordionItem value="queue" className="border-b">
|
||||||
</div>
|
<AccordionTrigger className="px-4 py-3 hover:no-underline bg-muted/50 hover:bg-muted">
|
||||||
|
<h2 className="font-semibold text-sm">My Queue</h2>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="max-h-[40vh] overflow-y-auto pb-0">
|
||||||
|
{queueSection}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
{/* Queue Section */}
|
{/* Available Participants Section */}
|
||||||
<div
|
<AccordionItem value="participants" className="border-0">
|
||||||
className="flex flex-col border-b border-border overflow-hidden"
|
<AccordionTrigger className="px-4 py-3 hover:no-underline bg-muted/50 hover:bg-muted">
|
||||||
style={{ flex: queueFlexSize }}
|
<h2 className="font-semibold text-sm">Available Participants</h2>
|
||||||
>
|
</AccordionTrigger>
|
||||||
<button
|
<AccordionContent className="pb-0">
|
||||||
onClick={toggleQueueSection}
|
<div className="h-[calc(100vh-400px)] overflow-y-auto">
|
||||||
className="flex items-center justify-between px-4 py-3 pr-12 bg-muted/50 hover:bg-muted transition-colors border-b border-border"
|
{participantsSection}
|
||||||
disabled={queueExpanded && !participantsExpanded}
|
</div>
|
||||||
>
|
</AccordionContent>
|
||||||
<h2 className="font-semibold text-sm">My Queue</h2>
|
</AccordionItem>
|
||||||
{queueExpanded ? (
|
</Accordion>
|
||||||
<ChevronUp className="h-4 w-4" />
|
|
||||||
) : (
|
|
||||||
<ChevronDown className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
{queueExpanded && (
|
|
||||||
<div className="flex-1 overflow-y-auto">{queueSection}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Available Participants Section */}
|
{/* Collapse button at bottom */}
|
||||||
<div
|
<div className="border-t border-border p-2 flex-shrink-0">
|
||||||
className="flex flex-col overflow-hidden"
|
<Button
|
||||||
style={{ flex: participantsFlexSize }}
|
variant="ghost"
|
||||||
>
|
size="sm"
|
||||||
<button
|
onClick={() => onCollapsedChange(true)}
|
||||||
onClick={toggleParticipantsSection}
|
className="w-full flex items-center justify-center gap-2"
|
||||||
className="flex items-center justify-between px-4 py-3 bg-muted/50 hover:bg-muted transition-colors border-b border-border"
|
aria-label="Collapse sidebar"
|
||||||
disabled={participantsExpanded && !queueExpanded}
|
>
|
||||||
>
|
<ChevronLeft className="h-4 w-4" />
|
||||||
<h2 className="font-semibold text-sm">Available Participants</h2>
|
<span className="text-sm">Hide Sidebar</span>
|
||||||
{participantsExpanded ? (
|
</Button>
|
||||||
<ChevronUp className="h-4 w-4" />
|
</div>
|
||||||
) : (
|
|
||||||
<ChevronDown className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
{participantsExpanded && (
|
|
||||||
<div className="flex-1 overflow-y-auto">{participantsSection}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,8 @@ export function AvailableParticipantsSection({
|
||||||
onRemoveFromQueue,
|
onRemoveFromQueue,
|
||||||
}: AvailableParticipantsSectionProps) {
|
}: AvailableParticipantsSectionProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col">
|
||||||
<div className="mb-4 flex-shrink-0">
|
<div className="px-4 pt-4 pb-2 flex-shrink-0">
|
||||||
<h3 className="text-lg font-semibold mb-3">Available Participants</h3>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{/* Search Input */}
|
{/* Search Input */}
|
||||||
<input
|
<input
|
||||||
|
|
@ -93,9 +91,10 @@ export function AvailableParticipantsSection({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table - This will scroll independently */}
|
||||||
<div className="border rounded-lg overflow-hidden flex-1 flex flex-col min-h-0">
|
<div className="px-4 pb-4">
|
||||||
<div className="overflow-y-auto flex-1">
|
<div className="border rounded-lg overflow-hidden">
|
||||||
|
<div className="overflow-y-auto max-h-[60vh]">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="bg-muted sticky top-0">
|
<thead className="bg-muted sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -270,6 +269,7 @@ export function AvailableParticipantsSection({
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export function QueueSection({
|
||||||
onAutodraftUpdate,
|
onAutodraftUpdate,
|
||||||
}: QueueSectionProps) {
|
}: QueueSectionProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="p-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold">Queue ({queue.length})</h3>
|
<h3 className="text-lg font-semibold">Queue ({queue.length})</h3>
|
||||||
{queue.length > 0 && (
|
{queue.length > 0 && (
|
||||||
|
|
@ -159,6 +159,6 @@ export function QueueSection({
|
||||||
isMyTurn={isMyTurn}
|
isMyTurn={isMyTurn}
|
||||||
onUpdate={onAutodraftUpdate}
|
onUpdate={onAutodraftUpdate}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
app/components/ui/accordion.tsx
Normal file
64
app/components/ui/accordion.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
|
import { ChevronDownIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "app/lib/utils"
|
||||||
|
|
||||||
|
function Accordion({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
||||||
|
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccordionItem({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Item
|
||||||
|
data-slot="accordion-item"
|
||||||
|
className={cn("border-b last:border-b-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccordionTrigger({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Header className="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
data-slot="accordion-trigger"
|
||||||
|
className={cn(
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccordionContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
data-slot="accordion-content"
|
||||||
|
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||||
|
|
@ -26,6 +26,7 @@ export default [
|
||||||
route("api/draft/resume", "routes/api/draft.resume.ts"),
|
route("api/draft/resume", "routes/api/draft.resume.ts"),
|
||||||
route("api/draft/force-autopick", "routes/api/draft.force-autopick.ts"),
|
route("api/draft/force-autopick", "routes/api/draft.force-autopick.ts"),
|
||||||
route("api/draft/force-manual-pick", "routes/api/draft.force-manual-pick.ts"),
|
route("api/draft/force-manual-pick", "routes/api/draft.force-manual-pick.ts"),
|
||||||
|
route("api/autodraft/update", "routes/api/autodraft.update.ts"),
|
||||||
route("user-profile", "routes/user-profile.tsx"),
|
route("user-profile", "routes/user-profile.tsx"),
|
||||||
route("how-to-play", "routes/how-to-play.tsx"),
|
route("how-to-play", "routes/how-to-play.tsx"),
|
||||||
route("test-socket", "routes/test-socket.tsx"),
|
route("test-socket", "routes/test-socket.tsx"),
|
||||||
|
|
|
||||||
32
package-lock.json
generated
32
package-lock.json
generated
|
|
@ -7,6 +7,7 @@
|
||||||
"name": "brackt.com",
|
"name": "brackt.com",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clerk/react-router": "^2.1.0",
|
"@clerk/react-router": "^2.1.0",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
|
|
@ -2600,6 +2601,37 @@
|
||||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-accordion": {
|
||||||
|
"version": "1.2.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
|
||||||
|
"integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-collapsible": "1.1.12",
|
||||||
|
"@radix-ui/react-collection": "1.1.7",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-direction": "1.1.1",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-alert-dialog": {
|
"node_modules/@radix-ui/react-alert-dialog": {
|
||||||
"version": "1.1.15",
|
"version": "1.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clerk/react-router": "^2.1.0",
|
"@clerk/react-router": "^2.1.0",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue