Search for brains through the dropdown (#507)

This commit is contained in:
Aditya Nandan 2023-07-05 12:54:42 +05:30 committed by GitHub
parent e931d29017
commit 02272ab0ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 253 additions and 147 deletions

View File

@ -15,7 +15,7 @@ export const Header = ({
y: hidden ? "-100%" : "0%",
transition: { ease: "circOut" },
}}
className="sticky top-0 w-full border-b border-b-black/10 dark:border-b-white/25 bg-white dark:bg-black z-[1200]"
className="sticky top-0 w-full border-b border-b-black/10 dark:border-b-white/25 bg-white dark:bg-black z-20"
>
<nav className="max-w-screen-xl mx-auto py-1 flex items-center justify-between gap-8">
{children}

View File

@ -1,146 +0,0 @@
/* eslint-disable */
import { useEffect, useRef, useState } from "react";
import { FaBrain } from "react-icons/fa";
import { IoMdAdd } from "react-icons/io";
import Button from "@/lib/components/ui/Button";
import Field from "@/lib/components/ui/Field";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useEventTracking } from "@/services/analytics/useEventTracking";
import { UUID } from "crypto";
import { AnimatePresence, motion } from "framer-motion";
import { MdCheck, MdDelete } from "react-icons/md";
export const BrainsDropDown = (): JSX.Element => {
const [showDropdown, setShowDropdown] = useState(false);
const [newBrainName, setNewBrainName] = useState("");
const { allBrains, createBrain, setActiveBrain, currentBrain, deleteBrain } =
useBrainContext();
const dropdownRef = useRef<HTMLDivElement | null>(null);
const { track } = useEventTracking();
const toggleDropdown = () => {
setShowDropdown((prevState) => !prevState);
void track("SHOW_BRAINS_DROPDOWN");
};
const handleCreateBrain = () => {
if (newBrainName.trim() === "") {
return;
}
void createBrain(newBrainName);
setNewBrainName(""); // Reset the new brain name input
void track("BRAIN_CREATED");
};
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node | null)
) {
setShowDropdown(false);
}
};
const changeBrains = (value: string) => {
void track("CHANGE_BRAIN");
setNewBrainName(value);
};
const deteleBrains = (id: UUID) => {
void track("DELETE_BRAIN");
deleteBrain(id);
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
return (
<>
{/* Add the brain icon and dropdown */}
<div className="relative ml-auto px-4 py-2" ref={dropdownRef}>
<button
type="button"
className="flex items-center focus:outline-none"
onClick={toggleDropdown}
>
<FaBrain className="w-6 h-6" />
</button>
<AnimatePresence>
{showDropdown && (
<motion.div
initial={{ opacity: 0, y: -32, height: "0" }}
animate={{
opacity: 1,
y: 0,
height: "13rem",
}}
exit={{ opacity: 0, y: -32, height: "0" }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="absolute right-0 mt-2 w-96 flex flex-col bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-lg"
>
{/* Option to create a new brain */}
<form
onSubmit={(e) => {
e.preventDefault();
handleCreateBrain();
}}
className="flex items-center gap-2 p-2"
>
<Field
name="brainname"
placeholder="Add a new brain"
autoFocus
onChange={(e) => changeBrains(e.target.value)}
/>
<Button type="submit" className="px-2 py-2">
<IoMdAdd className="w-5 h-5" />
</Button>
</form>
<div className="overflow-auto scrollbar flex flex-col h-full">
{/* List of brains */}
{allBrains.map((brain) => (
<div
key={brain.id}
className="relative flex group items-center"
>
<button
type="button"
className={`flex flex-1 items-center gap-2 w-full text-left px-4 py-2 text-sm leading-5 text-gray-900 dark:text-gray-300 group-hover:bg-gray-100 dark:group-hover:bg-gray-700 group-focus:bg-gray-100 dark:group-focus:bg-gray-700 group-focus:outline-none transition-colors`}
onClick={() => setActiveBrain({ ...brain })}
>
<span>
<MdCheck
style={{
opacity: currentBrain?.id === brain.id ? 1 : 0,
}}
className="text-xl transition-opacity"
width={32}
height={32}
/>
</span>
<span className="flex-1">{brain.name}</span>
</button>
<Button
className="group-hover:opacity-100 opacity-0 absolute right-0 hover:text-red-500 transition-[colors,opacity]"
onClick={() => deteleBrains(brain.id)}
variant={"tertiary"}
>
<MdDelete className="text-xl" />
</Button>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</>
);
};

View File

@ -0,0 +1,60 @@
import { FormEvent, useState } from "react";
import { MdAdd } from "react-icons/md";
import Button from "@/lib/components/ui/Button";
import Field from "@/lib/components/ui/Field";
import Modal from "@/lib/components/ui/Modal";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
const AddBrainModal = (): JSX.Element => {
const [newBrainName, setNewBrainName] = useState("");
const [isPending, setIsPending] = useState(false);
const { createBrain } = useBrainContext();
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (newBrainName.trim() === "" || isPending) {
return;
}
setIsPending(true);
await createBrain(newBrainName);
setNewBrainName("");
setIsPending(false);
};
return (
<Modal
Trigger={
<Button variant={"secondary"}>
Add New Brain
<MdAdd className="text-xl" />
</Button>
}
title="Add Brain"
desc="Add a new brain"
>
<form
onSubmit={(e) => void handleSubmit(e)}
className="my-10 flex items-center gap-2"
>
<Field
name="brainname"
label="Enter a brain name"
autoFocus
placeholder="E.g. History notes"
autoComplete="off"
value={newBrainName}
onChange={(e) => setNewBrainName(e.currentTarget.value)}
className="flex-1"
/>
<Button isLoading={isPending} className="self-end" type="submit">
Create
<MdAdd className="text-xl" />
</Button>
</form>
</Modal>
);
};
export { AddBrainModal };

View File

@ -0,0 +1,88 @@
import { useState } from "react";
import { FaBrain } from "react-icons/fa";
import { MdCheck, MdDelete } from "react-icons/md";
import Button from "@/lib/components/ui/Button";
import Field from "@/lib/components/ui/Field";
import Popover from "@/lib/components/ui/Popover";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { AddBrainModal } from "./AddBrainModal";
export const BrainsDropDown = (): JSX.Element => {
const [searchQuery, setSearchQuery] = useState("");
const { allBrains, setActiveBrain, currentBrain, deleteBrain } =
useBrainContext();
return (
<>
{/* Add the brain icon and dropdown */}
<div className="relative ml-auto px-4 py-2">
<Popover
Trigger={
<button
type="button"
className="flex items-center focus:outline-none"
>
<FaBrain className="w-6 h-6" />
</button>
}
ActionTrigger={<AddBrainModal />}
CloseTrigger={false}
>
<div>
<Field
name="brainsearch"
placeholder="Search for a brain"
autoFocus
autoComplete="off"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<div className="overflow-auto scrollbar flex flex-col h-48 mt-5">
{/* List of brains */}
{allBrains.map((brain) => {
if (brain.name.includes(searchQuery)) {
return (
<div
key={brain.id}
className="relative flex group items-center"
>
<button
type="button"
className={`flex flex-1 items-center gap-2 w-full text-left px-4 py-2 text-sm leading-5 text-gray-900 dark:text-gray-300 group-hover:bg-gray-100 dark:group-hover:bg-gray-700 group-focus:bg-gray-100 dark:group-focus:bg-gray-700 group-focus:outline-none transition-colors`}
onClick={() => {
setActiveBrain({ ...brain });
setSearchQuery("");
}}
>
<span>
<MdCheck
style={{
opacity: currentBrain?.id === brain.id ? 1 : 0,
}}
className="text-xl transition-opacity"
width={32}
height={32}
/>
</span>
<span className="flex-1">{brain.name}</span>
</button>
<Button
className="group-hover:visible invisible absolute right-0 hover:text-red-500 transition-[colors,opacity]"
onClick={() => void deleteBrain(brain.id)}
variant={"tertiary"}
>
<MdDelete className="text-xl" />
</Button>
</div>
);
}
})}
</div>
</div>
</Popover>
</div>
</>
);
};

View File

@ -0,0 +1,4 @@
import { AddBrainModal } from "./AddBrainModal";
import { BrainsDropDown } from "./BrainsDropDown";
export { BrainsDropDown, AddBrainModal };

View File

@ -0,0 +1,71 @@
"use client";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { AnimatePresence, motion } from "framer-motion";
import { ReactNode, useState } from "react";
import Button from "./Button";
interface PopoverProps {
children?: ReactNode;
Trigger: ReactNode;
ActionTrigger?: ReactNode;
CloseTrigger?: ReactNode;
}
const Popover = ({
children,
Trigger,
ActionTrigger,
CloseTrigger,
}: PopoverProps): JSX.Element => {
const [open, setOpen] = useState(false);
return (
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
<PopoverPrimitive.Trigger asChild>{Trigger}</PopoverPrimitive.Trigger>
<AnimatePresence>
{open && (
<PopoverPrimitive.Portal forceMount>
<PopoverPrimitive.Content forceMount asChild sideOffset={5}>
<motion.div
initial={{ opacity: 0, y: -32 }}
animate={{
opacity: 1,
y: 0,
}}
exit={{ opacity: 0, y: -32 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="relative flex flex-col p-4 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-lg z-40"
>
<div className="flex-1">{children}</div>
<div className="mt-4 self-end flex gap-4">
{ActionTrigger !== undefined && (
<PopoverPrimitive.Close asChild>
{ActionTrigger}
</PopoverPrimitive.Close>
)}
<PopoverPrimitive.Close asChild>
{CloseTrigger === undefined ? (
<Button
variant={"secondary"}
className="px-3 py-2"
aria-label="Close"
>
Close
</Button>
) : (
CloseTrigger
)}
</PopoverPrimitive.Close>
</div>
<PopoverPrimitive.Arrow className="fill-white stroke-gray-300 stroke-2" />
</motion.div>
</PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
)}
</AnimatePresence>
</PopoverPrimitive.Root>
);
};
export default Popover;

View File

@ -10,6 +10,7 @@ import {
getUserDefaultBrainFromBackend,
} from "@/lib/api";
import { useAxios, useToast } from "@/lib/hooks";
import { useEventTracking } from "@/services/analytics/useEventTracking";
import {
getBrainFromLocalStorage,
@ -36,6 +37,8 @@ export const useBrainState = (): BrainStateProps => {
const [currentBrainId, setCurrentBrainId] = useState<null | UUID>(null);
const { axiosInstance } = useAxios();
const { track } = useEventTracking();
const currentBrain = allBrains.find((brain) => brain.id === currentBrainId);
// options: Record<string, string | unknown>;
@ -45,6 +48,7 @@ export const useBrainState = (): BrainStateProps => {
if (createdBrain !== undefined) {
setAllBrains((prevBrains) => [...prevBrains, createdBrain]);
saveBrainInLocalStorage(createdBrain);
void track("BRAIN_CREATED");
return createdBrain.id;
} else {
@ -58,6 +62,7 @@ export const useBrainState = (): BrainStateProps => {
const deleteBrain = async (id: UUID) => {
await deleteBrainFromBE(axiosInstance, id);
setAllBrains((prevBrains) => prevBrains.filter((brain) => brain.id !== id));
void track("DELETE_BRAIN");
};
const getBrainWithId = async (brainId: UUID): Promise<Brain> => {
@ -90,6 +95,7 @@ export const useBrainState = (): BrainStateProps => {
saveBrainInLocalStorage(newActiveBrain);
setCurrentBrainId(id);
console.log("Setting active brain", newActiveBrain);
void track("CHANGE_BRAIN");
},
[]
);

View File

@ -21,6 +21,7 @@
"@growthbook/growthbook-react": "^0.17.0",
"@june-so/analytics-next": "^2.0.0",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-toast": "^1.1.3",
"@radix-ui/react-tooltip": "^1.0.6",
"@sentry/nextjs": "^7.57.0",

View File

@ -840,6 +840,28 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "1.0.1"
"@radix-ui/react-popover@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.6.tgz#19bb81e7450482c625b8cd05bf4dcd1d2cd91a8b"
integrity sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-dismissable-layer" "1.0.4"
"@radix-ui/react-focus-guards" "1.0.1"
"@radix-ui/react-focus-scope" "1.0.3"
"@radix-ui/react-id" "1.0.1"
"@radix-ui/react-popper" "1.1.2"
"@radix-ui/react-portal" "1.0.3"
"@radix-ui/react-presence" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-slot" "1.0.2"
"@radix-ui/react-use-controllable-state" "1.0.1"
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
"@radix-ui/react-popper@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.2.tgz#4c0b96fcd188dc1f334e02dba2d538973ad842e9"