From eabc9fa2f632cf3a156cf5e6e63114acc11ea242 Mon Sep 17 00:00:00 2001 From: Hamzat Victor Date: Sun, 13 Oct 2024 23:34:27 +0100 Subject: [PATCH] feat: update create new project dialog --- frontend/app/globals.css | 21 ++ frontend/components/dashboard/newProject.tsx | 212 ++++++++++++++++--- frontend/package-lock.json | 49 +++++ frontend/package.json | 2 + 4 files changed, 260 insertions(+), 24 deletions(-) diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 762a30d..9392370 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -152,3 +152,24 @@ .tab-scroll::-webkit-scrollbar { display: none; } + +.fade-r { + --mask-gradient: linear-gradient( + to right, + white 0%, + white calc(100% - var(--fade-size)), + transparent + ); + -webkit-mask-image: var(--mask-gradient); + mask-image: var(--mask-gradient); +} +.fade-l { + --mask-gradient: linear-gradient( + to left, + white var(--fade-size), + white 100%, + transparent + ); + -webkit-mask-image: var(--mask-gradient); + mask-image: var(--mask-gradient); +} diff --git a/frontend/components/dashboard/newProject.tsx b/frontend/components/dashboard/newProject.tsx index 334d232..228fa59 100644 --- a/frontend/components/dashboard/newProject.tsx +++ b/frontend/components/dashboard/newProject.tsx @@ -9,7 +9,7 @@ import { DialogTrigger, } from "@/components/ui/dialog" import Image from "next/image" -import { useState } from "react" +import { useState, useCallback, useEffect, useMemo } from "react" import { set, z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" @@ -34,10 +34,20 @@ import { import { useUser } from "@clerk/nextjs" import { createSandbox } from "@/lib/actions" import { useRouter } from "next/navigation" -import { Loader2 } from "lucide-react" +import { + Loader2, + ChevronRight, + ChevronLeft, + Search, + SlashSquare, +} from "lucide-react" import { Button } from "../ui/button" import { projectTemplates } from "@/lib/data" +import useEmblaCarousel from "embla-carousel-react" +import type { EmblaCarouselType } from "embla-carousel" +import { WheelGesturesPlugin } from "embla-carousel-wheel-gestures" +import { cn } from "@/lib/utils" const formSchema = z.object({ name: z .string() @@ -57,11 +67,20 @@ export default function NewProjectModal({ open: boolean setOpen: (open: boolean) => void }) { + const router = useRouter() + const user = useUser() const [selected, setSelected] = useState("reactjs") const [loading, setLoading] = useState(false) - const router = useRouter() - - const user = useUser() + const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false }, [ + WheelGesturesPlugin(), + ]) + const { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick, + } = usePrevNextButtons(emblaApi) + const [search, setSearch] = useState("") const form = useForm>({ resolver: zodResolver(formSchema), @@ -71,6 +90,26 @@ export default function NewProjectModal({ }, }) + const handleTemplateClick = useCallback( + ({ id, index }: { id: string; index: number }) => { + setSelected(id) + emblaApi?.scrollTo(index) + }, + [emblaApi] + ) + const filteredTemplates = useMemo( + () => + projectTemplates.filter( + (item) => + item.name.toLowerCase().includes(search.toLowerCase()) || + item.description.toLowerCase().includes(search.toLowerCase()) + ), + [search, projectTemplates] + ) + const emptyTemplates = useMemo( + () => filteredTemplates.length === 0, + [filteredTemplates] + ) async function onSubmit(values: z.infer) { if (!user.isSignedIn) return @@ -80,7 +119,6 @@ export default function NewProjectModal({ const id = await createSandbox(sandboxData) router.push(`/code/${id}`) } - return ( Create A Sandbox -
- {projectTemplates.map((item) => ( - - ))} + {filteredTemplates.map((item, i) => ( + + ))} + {emptyTemplates && ( +
+

No templates found

+ +
+ )} +
+
+ +
+
+ +
+
@@ -178,3 +277,68 @@ export default function NewProjectModal({
) } + +function SearchInput({ + value, + onValueChange, +}: { + value?: string + onValueChange?: (value: string) => void +}) { + const onSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault() + console.log("searching") + }, []) + return ( + + + + ) +} +const usePrevNextButtons = (emblaApi: EmblaCarouselType | undefined) => { + const [prevBtnDisabled, setPrevBtnDisabled] = useState(true) + const [nextBtnDisabled, setNextBtnDisabled] = useState(true) + + const onPrevButtonClick = useCallback(() => { + if (!emblaApi) return + emblaApi.scrollPrev() + }, [emblaApi]) + + const onNextButtonClick = useCallback(() => { + if (!emblaApi) return + emblaApi.scrollNext() + }, [emblaApi]) + + const onSelect = useCallback((emblaApi: EmblaCarouselType) => { + setPrevBtnDisabled(!emblaApi.canScrollPrev()) + setNextBtnDisabled(!emblaApi.canScrollNext()) + }, []) + + useEffect(() => { + if (!emblaApi) return + + onSelect(emblaApi) + emblaApi.on("reInit", onSelect).on("select", onSelect) + }, [emblaApi, onSelect]) + + return { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick, + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6f3df42..5f6d175 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -34,6 +34,8 @@ "@xterm/xterm": "^5.5.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "embla-carousel-react": "^8.3.0", + "embla-carousel-wheel-gestures": "^8.0.1", "framer-motion": "^11.2.3", "fs": "^0.0.1-security", "geist": "^1.3.0", @@ -2690,6 +2692,45 @@ "integrity": "sha512-w+9yAVHoHhysCa+gln7AzbO9CdjFcL/wN/5dd+XW/Msl2d/4+WisEaCF1nty0xbAKaxdaJfgLB2296U7zZB7BA==", "dev": true }, + "node_modules/embla-carousel": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.3.0.tgz", + "integrity": "sha512-Ve8dhI4w28qBqR8J+aMtv7rLK89r1ZA5HocwFz6uMB/i5EiC7bGI7y+AM80yAVUJw3qqaZYK7clmZMUR8kM3UA==" + }, + "node_modules/embla-carousel-react": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.3.0.tgz", + "integrity": "sha512-P1FlinFDcIvggcErRjNuVqnUR8anyo8vLMIH8Rthgofw7Nj8qTguCa2QjFAbzxAUTQTPNNjNL7yt0BGGinVdFw==", + "dependencies": { + "embla-carousel": "8.3.0", + "embla-carousel-reactive-utils": "8.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.3.0.tgz", + "integrity": "sha512-EYdhhJ302SC4Lmkx8GRsp0sjUhEN4WyFXPOk0kGu9OXZSRMmcBlRgTvHcq8eKJE1bXWBsOi1T83B+BSSVZSmwQ==", + "peerDependencies": { + "embla-carousel": "8.3.0" + } + }, + "node_modules/embla-carousel-wheel-gestures": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/embla-carousel-wheel-gestures/-/embla-carousel-wheel-gestures-8.0.1.tgz", + "integrity": "sha512-LMAnruDqDmsjL6UoQD65aLotpmfO49Fsr3H0bMi7I+BH6jbv9OJiE61kN56daKsVtCQEt0SU1MrJslbhtgF3yQ==", + "dependencies": { + "wheel-gestures": "^2.2.5" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "embla-carousel": "^8.0.0 || ~8.0.0-rc03" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -4553,6 +4594,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/wheel-gestures": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/wheel-gestures/-/wheel-gestures-2.2.48.tgz", + "integrity": "sha512-f+Gy33Oa5Z14XY9679Zze+7VFhbsQfBFXodnU2x589l4kxGM9L5Y8zETTmcMR5pWOPQyRv4Z0lNax6xCO0NSlA==", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index e82a344..34bdbfe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,8 @@ "@xterm/xterm": "^5.5.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "embla-carousel-react": "^8.3.0", + "embla-carousel-wheel-gestures": "^8.0.1", "framer-motion": "^11.2.3", "fs": "^0.0.1-security", "geist": "^1.3.0",