diff --git a/app/(content)/(gaming)/games/layout.tsx b/app/(content)/(gaming)/games/layout.tsx deleted file mode 100644 index e825038e820aae5f53f933edfc15181fb31e44a2..0000000000000000000000000000000000000000 --- a/app/(content)/(gaming)/games/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -"use client" - -export default function DashboardLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - <section> - <div> - {children} - </div> - </section> - ); -} \ No newline at end of file diff --git a/app/(content)/(gaming)/games/page.tsx b/app/(content)/(gaming)/games/page.tsx index 13a76b8a43d4ae48818bdef7e0265bd45e87314a..68138b4fb14885b6da2e492f50f3668215524a52 100644 --- a/app/(content)/(gaming)/games/page.tsx +++ b/app/(content)/(gaming)/games/page.tsx @@ -1,10 +1,18 @@ import { InfiniteScrollGames } from "@/components/InfiniteScroll"; +import Search from "@/components/search-games"; +import Sort from "@/components/sort-games"; // renders a list of games infinitely (presumably) export default async function GamesPage() { return ( - <div className="py-5"> - <InfiniteScrollGames /> - </div> + <main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_300px]"> + <div className="grid"> + <Search className="p-3 lg:w-2/3 2xl:w-1/3" /> + <InfiniteScrollGames /> + </div> + <div className="hidden xl:block"> + <Sort /> + </div> + </main> ) } \ No newline at end of file diff --git a/app/(content)/layout.tsx b/app/(content)/layout.tsx index 06b983170168def7ae12c64c1a653550b7bc647e..8660cfe8e2cc881cd80c182bd9d854801e11a65a 100644 --- a/app/(content)/layout.tsx +++ b/app/(content)/layout.tsx @@ -10,8 +10,8 @@ export default async function ContentLayout({ children, }: DashboardLayoutProps) { return ( - <div className="flex min-h-screen flex-col space-y-6"> - <div className="container grid flex-1 gap-12 md:grid-cols-[200px_1fr]"> + <div className="flex min-h-screen flex-col"> + <div className="mx-6 my-6 flex-1 md:grid md:grid-cols-[220px_1fr] md:gap-6 lg:grid-cols-[240px_1fr] lg:gap-10"> <aside className="hidden w-[200px] flex-col md:flex"> <div className="sticky top-0"> <DashboardNav items={dashboardConfig.sidebarNav} /> diff --git a/components/InfiniteScroll.tsx b/components/InfiniteScroll.tsx index 895e83edf60b7576d4365f1e4bb3363f8ec89459..6aa7bbcc1bcc9eac663dc16b655bcae61ac2ea34 100644 --- a/components/InfiniteScroll.tsx +++ b/components/InfiniteScroll.tsx @@ -18,6 +18,7 @@ export function InfiniteScrollGames() { ['infiniteGames'], async ({ pageParam = 1 }) => await fetch(`/api/games/?page=${pageParam}`, + { cache: 'force-cache' } ).then((result) => result.json() as Promise<IGame[]>), { getNextPageParam: (lastPage, pages) => { @@ -27,7 +28,7 @@ export function InfiniteScrollGames() { ) return ( - <Card className="p-5"> + <Card className="p-6"> {status === 'error' ? (<span>Error: {(error as Error).message}</span>) diff --git a/components/nav.tsx b/components/nav.tsx index afc15302cd4858bb4840767275e67135cc5b1695..b7c9d441e084b09527aa4e494524b04fad1e464a 100644 --- a/components/nav.tsx +++ b/components/nav.tsx @@ -25,7 +25,7 @@ export default function DashboardNav({ items }: DashboardNavProps) { return ( <nav className="grid items-start gap-2"> - <div className="flex items-center py-2"> + <div className="flex items-center"> <Link href="/" className={cn("rounded-full p-3 hover:bg-accent")}> <Icons.logo className="h-7 w-7 dark:hidden" /> <Icons.logoWhite className="h-7 w-7 hidden dark:block" /> diff --git a/components/search-games.tsx b/components/search-games.tsx new file mode 100644 index 0000000000000000000000000000000000000000..14551f34905b4ee0326924bb38cbf737da848b54 --- /dev/null +++ b/components/search-games.tsx @@ -0,0 +1,40 @@ +"use client" + +import { cn } from '@/lib/utils'; +import { useState } from 'react'; +import { Button } from './ui/button'; +import { Icons } from './ui/icons'; +import { Input } from './ui/input'; +import { toast } from './ui/use-toast'; + +interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> { } + +export default function Search({ className, ...props }: DocsSearchProps) { + const [searchText, setSearchText] = useState(''); + + function onSubmit(event: React.SyntheticEvent) { + event.preventDefault() + + return toast({ + title: "Not implemented", + description: "We're still working on the search.", + }) + }; + + return ( + <form + onSubmit={onSubmit} + className={cn("relative w-full flex justify-end items-center", className)} + {...props} + > + <Input + type="search" + placeholder="Search..." + className="rounded-full pr-12" + /> + <Button variant="outline" size="lg" className="absolute align-middle h-8 px-2.5 mr-1"> + <Icons.arrowRight className="h-3 w-3" /> + </Button> + </form> + ); +} \ No newline at end of file diff --git a/components/sort-games.tsx b/components/sort-games.tsx new file mode 100644 index 0000000000000000000000000000000000000000..76f380eef5126e30f5009bca260900981755c9ca --- /dev/null +++ b/components/sort-games.tsx @@ -0,0 +1,63 @@ +"use client" + +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useState } from "react"; +import { Card } from "./ui/card"; + +export default function Sort() { + const [select, setSelect] = useState<string>(''); + + const handleChange = (event: any) => { + const selectedOption = event.target.value as string; + setSelect(selectedOption); + }; + + return ( + <Card className="p-6 grid items-start gap-2"> + <Select> + <SelectTrigger className="w-full"> + <SelectValue placeholder="Sort by..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>Sorting</SelectLabel> + <SelectItem value="">Any</SelectItem> + <SelectItem value="name">Name</SelectItem> + <SelectItem value="rating">Rating</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + + <h1 className="pt-3">Filter</h1> + <Select> + <SelectTrigger className="w-full"> + <SelectValue placeholder="By genre..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>Genre</SelectLabel> + <SelectItem value="">Any</SelectItem> + <SelectItem value="action">Action</SelectItem> + <SelectItem value="simulation">Simulation</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + + <Select> + <SelectTrigger className="w-full"> + <SelectValue placeholder="By platform..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>Platform</SelectLabel> + <SelectItem value="">Any</SelectItem> + <SelectItem value="pc">PC</SelectItem> + <SelectItem value="playstation">Playstation</SelectItem> + <SelectItem value="xbox">Xbox</SelectItem> + <SelectItem value="nintendo">Nintendo</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + </Card> + ) +} \ No newline at end of file diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..929e05f50056bb69c128b4ac19fc3b83eed2c646 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes<HTMLInputElement> {} + +const Input = React.forwardRef<HTMLInputElement, InputProps>( + ({ className, type, ...props }, ref) => { + return ( + <input + type={type} + className={cn( + "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + className + )} + ref={ref} + {...props} + /> + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/components/ui/scroll-area.tsx b/components/ui/scroll-area.tsx new file mode 100644 index 0000000000000000000000000000000000000000..54b87cd76571b2891f8f4fc8624733fa817eb841 --- /dev/null +++ b/components/ui/scroll-area.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +const ScrollArea = React.forwardRef< + React.ElementRef<typeof ScrollAreaPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> +>(({ className, children, ...props }, ref) => ( + <ScrollAreaPrimitive.Root + ref={ref} + className={cn("relative overflow-hidden", className)} + {...props} + > + <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> + {children} + </ScrollAreaPrimitive.Viewport> + <ScrollBar /> + <ScrollAreaPrimitive.Corner /> + </ScrollAreaPrimitive.Root> +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, + React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> +>(({ className, orientation = "vertical", ...props }, ref) => ( + <ScrollAreaPrimitive.ScrollAreaScrollbar + ref={ref} + orientation={orientation} + className={cn( + "flex touch-none select-none transition-colors", + orientation === "vertical" && + "h-full w-2.5 border-l border-l-transparent p-[1px]", + orientation === "horizontal" && + "h-2.5 border-t border-t-transparent p-[1px]", + className + )} + {...props} + > + <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> + </ScrollAreaPrimitive.ScrollAreaScrollbar> +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0d23fc492c4283de34ece4b29fa5933dde4abe5d --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,120 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> +>(({ className, children, ...props }, ref) => ( + <SelectPrimitive.Trigger + ref={ref} + className={cn( + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + className + )} + {...props} + > + {children} + <SelectPrimitive.Icon asChild> + <ChevronDown className="h-4 w-4 opacity-50" /> + </SelectPrimitive.Icon> + </SelectPrimitive.Trigger> +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectContent = 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 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80", + position === "popper" && "translate-y-1", + className + )} + position={position} + {...props} + > + <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> + </SelectPrimitive.Content> + </SelectPrimitive.Portal> +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Label>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.Label + ref={ref} + className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} + {...props} + /> +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = 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 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + className + )} + {...props} + > + <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> + <SelectPrimitive.ItemIndicator> + <Check className="h-4 w-4" /> + </SelectPrimitive.ItemIndicator> + </span> + + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> + </SelectPrimitive.Item> +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Separator>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.Separator + ref={ref} + className={cn("-mx-1 my-1 h-px bg-muted", className)} + {...props} + /> +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, +} diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2deb2e9cc94af0be722b6a433a6d309621f12a5f --- /dev/null +++ b/components/ui/toast.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { VariantProps, cva } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Viewport>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Viewport + ref={ref} + className={cn( + "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", + className + )} + {...props} + /> +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full", + { + variants: { + variant: { + default: "bg-background border", + destructive: + "group destructive border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Root>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & + VariantProps<typeof toastVariants> +>(({ className, variant, ...props }, ref) => { + return ( + <ToastPrimitives.Root + ref={ref} + className={cn(toastVariants({ variant }), className)} + {...props} + /> + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Action>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Action + ref={ref} + className={cn( + "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-destructive/30 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", + className + )} + {...props} + /> +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Close>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Close + ref={ref} + className={cn( + "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", + className + )} + toast-close="" + {...props} + > + <X className="h-4 w-4" /> + </ToastPrimitives.Close> +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Title>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Title + ref={ref} + className={cn("text-sm font-semibold", className)} + {...props} + /> +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef<typeof ToastPrimitives.Description>, + React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> +>(({ className, ...props }, ref) => ( + <ToastPrimitives.Description + ref={ref} + className={cn("text-sm opacity-90", className)} + {...props} + /> +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> + +type ToastActionElement = React.ReactElement<typeof ToastAction> + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/components/ui/use-toast.ts b/components/ui/use-toast.ts new file mode 100644 index 0000000000000000000000000000000000000000..c70c0d61949e822e1c5f2530d91aad64ded73d47 --- /dev/null +++ b/components/ui/use-toast.ts @@ -0,0 +1,189 @@ +// Inspired by react-hot-toast library +import * as React from "react" + +import { ToastActionElement, type ToastProps } from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_VALUE + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial<ToasterToast> + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +interface Toast extends Omit<ToasterToast, "id"> {} + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState<State>(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/package-lock.json b/package-lock.json index 8f0f3cb5aebdc29cfd29089caa35f221168e5280..a1ff6ee240459811cdd649e31671ba439e6537b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,10 @@ "@clerk/themes": "^1.7.3", "@prisma/client": "^4.14.1", "@radix-ui/react-dropdown-menu": "^2.0.4", + "@radix-ui/react-scroll-area": "^1.0.3", + "@radix-ui/react-select": "^1.2.1", "@radix-ui/react-slot": "^1.0.1", + "@radix-ui/react-toast": "^1.1.3", "@tanstack/react-query": "^4.29.7", "class-variance-authority": "^0.6.0", "clsx": "^1.2.1", @@ -642,6 +645,14 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c.tgz", "integrity": "sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw==" }, + "node_modules/@radix-ui/number": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz", + "integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", @@ -899,6 +910,60 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.3.tgz", + "integrity": "sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.0", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-direction": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-select": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.1.tgz", + "integrity": "sha512-GULRMITaOHNj79BZvQs3iZO0+f2IgI8g5HDhMi7Bnc13t7IlG86NFtOCfTLme4PNZdEtU+no+oGgcl6IFiphpQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.0", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-collection": "1.0.2", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-direction": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.3", + "@radix-ui/react-focus-guards": "1.0.0", + "@radix-ui/react-focus-scope": "1.0.2", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-popper": "1.1.1", + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-slot": "1.0.1", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0", + "@radix-ui/react-use-previous": "1.0.0", + "@radix-ui/react-visually-hidden": "1.0.2", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", @@ -911,6 +976,30 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.3.tgz", + "integrity": "sha512-yHFgpxi9wjbfPvpSPdYAzivCqw48eA1ofT8m/WqYOVTxKPdmQMuVKRYPlMmj4C1d6tJdFj/LBa1J4iY3fL4OwQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-collection": "1.0.2", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.3", + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0", + "@radix-ui/react-visually-hidden": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", @@ -957,6 +1046,17 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.0.tgz", + "integrity": "sha512-RG2K8z/K7InnOKpq6YLDmT49HGjNmrK+fr82UCVKT2sW0GYfVnYp4wZWBooT/EYfQ5faA9uIjvsuMMhH61rheg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz", @@ -981,6 +1081,19 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.2.tgz", + "integrity": "sha512-qirnJxtYn73HEk1rXL12/mXnu2rwsNHDID10th2JGtdK25T9wX+mxRmGt7iPSahw512GbZOc0syZX1nLQGoEOg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/rect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz", diff --git a/package.json b/package.json index 2a17a6412103f65ed53a74a6cec2e31659246cf3..aee36ccf609ed52b3a2e520a1dddd27d6bb59e1a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "@clerk/themes": "^1.7.3", "@prisma/client": "^4.14.1", "@radix-ui/react-dropdown-menu": "^2.0.4", + "@radix-ui/react-scroll-area": "^1.0.3", + "@radix-ui/react-select": "^1.2.1", "@radix-ui/react-slot": "^1.0.1", + "@radix-ui/react-toast": "^1.1.3", "@tanstack/react-query": "^4.29.7", "class-variance-authority": "^0.6.0", "clsx": "^1.2.1",