aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/(dash)/dynamicisland.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/app/(dash)/dynamicisland.tsx')
-rw-r--r--apps/web/app/(dash)/dynamicisland.tsx373
1 files changed, 373 insertions, 0 deletions
diff --git a/apps/web/app/(dash)/dynamicisland.tsx b/apps/web/app/(dash)/dynamicisland.tsx
new file mode 100644
index 00000000..6fa56fae
--- /dev/null
+++ b/apps/web/app/(dash)/dynamicisland.tsx
@@ -0,0 +1,373 @@
+"use client";
+
+import { AddIcon } from "@repo/ui/icons";
+import Image from "next/image";
+
+import { AnimatePresence, useMotionValueEvent, useScroll } from "framer-motion";
+import { useActionState, useEffect, useRef, useState } from "react";
+import { motion } from "framer-motion";
+import { Label } from "@repo/ui/shadcn/label";
+import { Input } from "@repo/ui/shadcn/input";
+import { Textarea } from "@repo/ui/shadcn/textarea";
+import { createMemory, createSpace } from "../actions/doers";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@repo/ui/shadcn/select";
+import { Space } from "../actions/types";
+import { getSpaces } from "../actions/fetchers";
+import { toast } from "sonner";
+import { useFormStatus } from "react-dom";
+
+export function DynamicIsland() {
+ const { scrollYProgress } = useScroll();
+ const [visible, setVisible] = useState(true);
+
+ useMotionValueEvent(scrollYProgress, "change", (current) => {
+ if (typeof current === "number") {
+ let direction = current! - scrollYProgress.getPrevious()!;
+
+ if (direction < 0 || direction === 1) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ }
+ });
+
+ return (
+ <div className="fixed z-40 left-1/2 -translate-x-1/2 top-12">
+ <AnimatePresence mode="wait">
+ <motion.div
+ initial={{
+ opacity: 1,
+ y: -150,
+ }}
+ animate={{
+ y: visible ? 0 : -150,
+ opacity: visible ? 1 : 0,
+ }}
+ transition={{
+ duration: 0.2,
+ }}
+ className="flex flex-col items-center"
+ >
+ <DynamicIslandContent />
+ </motion.div>
+ </AnimatePresence>
+ </div>
+ );
+}
+
+export default DynamicIsland;
+
+function DynamicIslandContent() {
+ const [show, setshow] = useState(true);
+ function cancelfn() {
+ setshow(true);
+ }
+
+ const lastBtn = useRef<string>();
+ useEffect(() => {
+ console.log(show);
+ }, [show]);
+
+ useEffect(() => {
+ document.addEventListener("keydown", (e) => {
+ if (e.key === "Escape") {
+ setshow(true);
+ }
+ console.log(e.key, lastBtn.current);
+ if (e.key === "a" && lastBtn.current === "Alt") {
+ setshow(false);
+ }
+ lastBtn.current = e.key;
+ });
+ }, []);
+ return (
+ <>
+ {show ? (
+ <div
+ onClick={() => setshow(!show)}
+ className="bg-secondary px-3 w-[2.23rem] overflow-hidden hover:w-[9.2rem] whitespace-nowrap py-2 rounded-3xl transition-[width] cursor-pointer"
+ >
+ <div className="flex gap-4 items-center">
+ <Image src={AddIcon} alt="Add icon" />
+ Add Content
+ </div>
+ </div>
+ ) : (
+ <div>
+ <ToolBar cancelfn={cancelfn} />
+ </div>
+ )}
+ </>
+ );
+}
+
+const fakeitems = ["spaces", "page", "note"];
+
+function ToolBar({ cancelfn }: { cancelfn: () => void }) {
+ const [spaces, setSpaces] = useState<Space[]>([]);
+
+ const [index, setIndex] = useState(0);
+
+ useEffect(() => {
+ (async () => {
+ let spaces = await getSpaces();
+
+ if (!spaces.success || !spaces.data) {
+ toast.warning("Unable to get spaces", {
+ richColors: true,
+ });
+ setSpaces([]);
+ return;
+ }
+ setSpaces(spaces.data);
+ })();
+ }, []);
+
+ return (
+ <AnimatePresence mode="wait">
+ <motion.div
+ initial={{
+ opacity: 0,
+ y: 20,
+ }}
+ animate={{
+ y: 0,
+ opacity: 1,
+ }}
+ exit={{
+ opacity: 0,
+ y: 20,
+ }}
+ transition={{
+ duration: 0.2,
+ }}
+ className="flex flex-col items-center"
+ >
+ <div className="bg-secondary py-[.35rem] px-[.6rem] rounded-2xl">
+ <HoverEffect
+ items={fakeitems}
+ index={index}
+ indexFn={(i) => setIndex(i)}
+ />
+ </div>
+ {index === 0 ? (
+ <SpaceForm cancelfn={cancelfn} />
+ ) : index === 1 ? (
+ <PageForm cancelfn={cancelfn} spaces={spaces} />
+ ) : (
+ <NoteForm cancelfn={cancelfn} spaces={spaces} />
+ )}
+ </motion.div>
+ </AnimatePresence>
+ );
+}
+
+export const HoverEffect = ({
+ items,
+ index,
+ indexFn,
+}: {
+ items: string[];
+ index: number;
+ indexFn: (i: number) => void;
+}) => {
+ return (
+ <div className={"flex"}>
+ {items.map((item, idx) => (
+ <button
+ key={idx}
+ className="relative block h-full w-full px-2 py-1"
+ onClick={() => indexFn(idx)}
+ >
+ <AnimatePresence>
+ {index === idx && (
+ <motion.span
+ className="absolute inset-0 block h-full w-full rounded-xl bg-[#2B3237]"
+ layoutId="hoverBackground"
+ initial={{ opacity: 0 }}
+ animate={{
+ opacity: 1,
+ transition: { duration: 0.15 },
+ }}
+ exit={{
+ opacity: 0,
+ transition: { duration: 0.15, delay: 0.2 },
+ }}
+ />
+ )}
+ </AnimatePresence>
+ <h3 className="text-[#858B92] z-50 relative">{item}</h3>
+ </button>
+ ))}
+ </div>
+ );
+};
+
+function SpaceForm({ cancelfn }: { cancelfn: () => void }) {
+ return (
+ <form
+ action={createSpace}
+ className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3"
+ >
+ <div>
+ <Label className="text-[#858B92]" htmlFor="name">
+ Name
+ </Label>
+ <Input
+ className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
+ id="name"
+ name="name"
+ />
+ </div>
+ <div className="flex justify-between">
+ <a className="text-blue-500" href="">
+ pull from store
+ </a>
+ {/* <div
+ onClick={cancelfn}
+ className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
+ >
+ cancel
+ </div> */}
+ <button
+ type="submit"
+ className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
+ >
+ Submit
+ </button>
+ </div>
+ </form>
+ );
+}
+
+function PageForm({
+ cancelfn,
+ spaces,
+}: {
+ cancelfn: () => void;
+ spaces: Space[];
+}) {
+ const [loading, setLoading] = useState(false);
+
+ const { pending } = useFormStatus();
+ return (
+ <form
+ action={async (e: FormData) => {
+ const content = e.get("content")?.toString();
+ const space = e.get("space")?.toString();
+ if (!content) {
+ toast.error("Content is required");
+ return;
+ }
+ setLoading(true);
+ const cont = await createMemory({
+ content: content,
+ spaces: space ? [space] : undefined,
+ });
+
+ console.log(cont);
+ setLoading(false);
+ if (cont.success) {
+ toast.success("Memory created");
+ } else {
+ toast.error("Memory creation failed");
+ }
+ }}
+ className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3"
+ >
+ <div>
+ <Label className="text-[#858B92]" htmlFor="space">
+ Space
+ </Label>
+ <Select name="space">
+ <SelectTrigger>
+ <SelectValue placeholder="Space" />
+ </SelectTrigger>
+ <SelectContent className="bg-secondary text-white">
+ {spaces.map((space) => (
+ <SelectItem key={space.id} value={space.id.toString()}>
+ {space.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ <div key={`${loading}-${pending}`}>
+ {loading ? <div>Loading...</div> : "not loading"}
+ </div>
+ <div>
+ <Label className="text-[#858B92]" htmlFor="name">
+ Page Url
+ </Label>
+ <Input
+ className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
+ id="input"
+ name="content"
+ />
+ </div>
+ <div className="flex justify-end">
+ <button
+ type="submit"
+ className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
+ >
+ Submit
+ </button>
+ </div>
+ </form>
+ );
+}
+
+function NoteForm({
+ cancelfn,
+ spaces,
+}: {
+ cancelfn: () => void;
+ spaces: Space[];
+}) {
+ return (
+ <div className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
+ <div>
+ <Label className="text-[#858B92]" htmlFor="name">
+ Space
+ </Label>
+ <Select>
+ <SelectTrigger>
+ <SelectValue placeholder="Space" />
+ </SelectTrigger>
+ <SelectContent className="bg-secondary text-white">
+ {spaces.map((space) => (
+ <SelectItem key={space.id} value={space.id.toString()}>
+ {space.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ <div>
+ <Label className="text-[#858B92]" htmlFor="name">
+ Note
+ </Label>
+ <Textarea
+ cols={4}
+ className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0 resize-none"
+ id="name"
+ />
+ </div>
+ <div className="flex justify-end">
+ <div
+ onClick={cancelfn}
+ className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
+ >
+ cancel
+ </div>
+ </div>
+ </div>
+ );
+}