From a466cd3c546e62d3837c882c2fb0d8d752178d85 Mon Sep 17 00:00:00 2001 From: Nwadei Shakirat Date: Sun, 21 Jul 2024 19:08:18 +0100 Subject: [PATCH 1/3] update --- app/components/context/editor-context.tsx | 75 +++++++++++++++++++ .../template/templateEditorBlock.tsx | 54 +++++++++++++ app/components/toolbar/index.tsx | 1 + app/components/toolbar/toolbar.tsx | 49 ++++++++++++ app/routes/_index.tsx | 2 + app/types/custom.d.ts | 1 + package-lock.json | 0 7 files changed, 182 insertions(+) create mode 100644 app/components/context/editor-context.tsx create mode 100644 app/components/template/templateEditorBlock.tsx create mode 100644 app/components/toolbar/index.tsx create mode 100644 app/components/toolbar/toolbar.tsx create mode 100644 app/types/custom.d.ts delete mode 100644 package-lock.json diff --git a/app/components/context/editor-context.tsx b/app/components/context/editor-context.tsx new file mode 100644 index 00000000..c65f7278 --- /dev/null +++ b/app/components/context/editor-context.tsx @@ -0,0 +1,75 @@ +"use client"; + +import React, { createContext, useContext, useState } from "react"; + +interface Block { + id: string; + content: string; +} + +interface EditorContextType { + blocks: Block[]; + moveBlockUp: (id: string) => void; + moveBlockDown: (id: string) => void; + deleteBlock: (id: string) => void; +} + +const EditorContext = createContext(undefined); + +export const EditorProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [blocks, setBlocks] = useState([]); + + const moveBlockUp = (id: string) => { + setBlocks((previousBlocks) => { + const index = previousBlocks.findIndex((block) => block.id === id); + if (index > 0) { + const newBlocks = [...previousBlocks]; + [newBlocks[index - 1], newBlocks[index]] = [ + newBlocks[index], + newBlocks[index - 1], + ]; + return newBlocks; + } + return previousBlocks; + }); + }; + + const moveBlockDown = (id: string) => { + setBlocks((previousBlocks) => { + const index = previousBlocks.findIndex((block) => block.id === id); + if (index < previousBlocks.length - 1) { + const newBlocks = [...previousBlocks]; + [newBlocks[index], newBlocks[index + 1]] = [ + newBlocks[index + 1], + newBlocks[index], + ]; + return newBlocks; + } + return previousBlocks; + }); + }; + + const deleteBlock = (id: string) => { + setBlocks((previousBlocks) => + previousBlocks.filter((block) => block.id !== id), + ); + }; + + return ( + + {children} + + ); +}; + +export const useEditorContext = () => { + const context = useContext(EditorContext); + if (context === undefined) { + throw new Error("useEditorContext must be used within an EditorProvider"); + } + return context; +}; diff --git a/app/components/template/templateEditorBlock.tsx b/app/components/template/templateEditorBlock.tsx new file mode 100644 index 00000000..001f960c --- /dev/null +++ b/app/components/template/templateEditorBlock.tsx @@ -0,0 +1,54 @@ +"use client"; + +import React, { useRef, useState } from "react"; + +import Toolbar from "../toolbar/toolbar"; + +interface BlockProperties { + id: string; + content: string; + children?: React.ReactNode; +} + +const ReusableTemplateEditorBlock: React.FC = ({ + id, + content, + children, +}) => { + const [isActive, setIsActive] = useState(false); + const blockReference = useRef(null); + + const handleClick = () => { + setIsActive(true); + }; + + const handleBlur = (event: React.FocusEvent) => { + if (!blockReference.current?.contains(event.relatedTarget as Node)) { + setIsActive(false); + } + }; + + return ( +
+
+ {children} + {isActive && ( +
+ +
+ )} +
+ ); +}; + +export default ReusableTemplateEditorBlock; diff --git a/app/components/toolbar/index.tsx b/app/components/toolbar/index.tsx new file mode 100644 index 00000000..e5d70820 --- /dev/null +++ b/app/components/toolbar/index.tsx @@ -0,0 +1 @@ +export { default } from "./toolbar"; diff --git a/app/components/toolbar/toolbar.tsx b/app/components/toolbar/toolbar.tsx new file mode 100644 index 00000000..111dbf68 --- /dev/null +++ b/app/components/toolbar/toolbar.tsx @@ -0,0 +1,49 @@ +import * as ToolbarPrimitive from "@radix-ui/react-toolbar"; +import { ArrowDown, ArrowUp, Copy, LucideDelete } from "lucide-react"; +import React from "react"; + +import { useEditorContext } from "~/components/context/editor-context"; + +interface ToolbarProperties { + blockId: string; + content: string; +} + +const Toolbar: React.FC = ({ blockId, content }) => { + const { moveBlockUp, moveBlockDown, deleteBlock } = useEditorContext(); + + const handleCopy = () => { + navigator.clipboard.writeText(content); + }; + + const buttonClass = + "p-2 bg-white text-gray-700 rounded shadow-md transition-all duration-200 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-300"; + + return ( + + moveBlockUp(blockId)} + > + + + moveBlockDown(blockId)} + > + + + + + + deleteBlock(blockId)} + > + + + + ); +}; + +export default Toolbar; diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 51793576..d8622fbd 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,6 +1,7 @@ import type { MetaFunction } from "@remix-run/node"; import { useState } from "react"; +import { ReusableTemplateEditorBlock } from "~/components/template/templateEditorBlock"; import { Button } from "~/components/ui/button"; import CardPlatform from "~/components/ui/card/card-platform"; import OtpAuth from "~/components/ui/otp/OtpAuth"; @@ -67,6 +68,7 @@ export default function Index() { + setOpenModal(!openModal)} diff --git a/app/types/custom.d.ts b/app/types/custom.d.ts new file mode 100644 index 00000000..a2046437 --- /dev/null +++ b/app/types/custom.d.ts @@ -0,0 +1 @@ +declare module "@radix-ui/react-toolbar"; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e69de29b..00000000 From d6906d94efd14c78199ccb53329ec5ca617cac95 Mon Sep 17 00:00:00 2001 From: Nwadei Shakirat Date: Sun, 21 Jul 2024 19:57:40 +0100 Subject: [PATCH 2/3] update --- app/components/template/templateEditorBlock.tsx | 4 ++-- app/routes/_index.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/components/template/templateEditorBlock.tsx b/app/components/template/templateEditorBlock.tsx index 001f960c..68c25cc0 100644 --- a/app/components/template/templateEditorBlock.tsx +++ b/app/components/template/templateEditorBlock.tsx @@ -10,7 +10,7 @@ interface BlockProperties { children?: React.ReactNode; } -const ReusableTemplateEditorBlock: React.FC = ({ +export const ReusableTemplateEditorBlock: React.FC = ({ id, content, children, @@ -51,4 +51,4 @@ const ReusableTemplateEditorBlock: React.FC = ({ ); }; -export default ReusableTemplateEditorBlock; +// export default ReusableTemplateEditorBlock; diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index d8622fbd..ac9d1617 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,7 +1,7 @@ import type { MetaFunction } from "@remix-run/node"; import { useState } from "react"; -import { ReusableTemplateEditorBlock } from "~/components/template/templateEditorBlock"; +// import { ReusableTemplateEditorBlock } from "~/components/template/templateEditorBlock"; import { Button } from "~/components/ui/button"; import CardPlatform from "~/components/ui/card/card-platform"; import OtpAuth from "~/components/ui/otp/OtpAuth"; @@ -68,7 +68,9 @@ export default function Index() { - +
+ {/* */} +
setOpenModal(!openModal)} From 01d7773c22b81c7ea0990a9206395bbfe199e6a5 Mon Sep 17 00:00:00 2001 From: Nwadei Shakirat Date: Mon, 22 Jul 2024 06:08:26 +0100 Subject: [PATCH 3/3] update email editor --- app/Star-1.svg | 3 + app/components/Toolbar.tsx | 36 ++ app/components/context/EditorContext.tsx | 242 +++++++++++ app/components/context/TopNavigation.tsx | 131 ++++++ app/components/context/editor-context.tsx | 75 ---- .../template/templateEditorBlock.tsx | 54 --- app/components/templateEditorBlock.tsx | 55 +++ app/components/toolbar/index.tsx | 1 - app/components/toolbar/toolbar.tsx | 49 --- app/icon-10.svg | 11 + app/icon-11.svg | 11 + app/icon-12.svg | 5 + app/icon-8.svg | 4 + app/icon-9.svg | 4 + app/icon.svg | 5 + app/routes/Editor.tsx | 26 ++ app/routes/_index.tsx | 4 - package.json | 14 +- pnpm-lock.yaml | 397 +++++++++++++++++- 19 files changed, 931 insertions(+), 196 deletions(-) create mode 100644 app/Star-1.svg create mode 100644 app/components/Toolbar.tsx create mode 100644 app/components/context/EditorContext.tsx create mode 100644 app/components/context/TopNavigation.tsx delete mode 100644 app/components/context/editor-context.tsx delete mode 100644 app/components/template/templateEditorBlock.tsx create mode 100644 app/components/templateEditorBlock.tsx delete mode 100644 app/components/toolbar/index.tsx delete mode 100644 app/components/toolbar/toolbar.tsx create mode 100644 app/icon-10.svg create mode 100644 app/icon-11.svg create mode 100644 app/icon-12.svg create mode 100644 app/icon-8.svg create mode 100644 app/icon-9.svg create mode 100644 app/icon.svg create mode 100644 app/routes/Editor.tsx diff --git a/app/Star-1.svg b/app/Star-1.svg new file mode 100644 index 00000000..7460d379 --- /dev/null +++ b/app/Star-1.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/components/Toolbar.tsx b/app/components/Toolbar.tsx new file mode 100644 index 00000000..3d66d5c1 --- /dev/null +++ b/app/components/Toolbar.tsx @@ -0,0 +1,36 @@ +import * as ToolbarPrimitive from "@radix-ui/react-toolbar"; +import { ArrowDown, ArrowUp, Copy, Trash2 } from "lucide-react"; +import React from "react"; + +interface ToolbarProperties { + onMoveUp: () => void; + onMoveDown: () => void; + onCopy: () => void; + onDelete: () => void; +} + +const Toolbar: React.FC = ({ + onMoveUp, + onMoveDown, + onCopy, + onDelete, +}) => { + return ( + + + + + + + + + + + + + + + ); +}; + +export default Toolbar; diff --git a/app/components/context/EditorContext.tsx b/app/components/context/EditorContext.tsx new file mode 100644 index 00000000..ce2a825d --- /dev/null +++ b/app/components/context/EditorContext.tsx @@ -0,0 +1,242 @@ +import React, { createContext, useContext, useState } from "react"; + +interface EditorContextType { + blocks: string[]; + moveBlockUp: (index: number) => void; + moveBlockDown: (index: number) => void; + deleteBlock: (index: number) => void; + copyBlock: (index: number) => void; +} + +const EditorContext = createContext(undefined); + +export const EditorProvider: React.FC<{ + children: React.ReactNode; + className?: string; +}> = ({ children, className = "" }) => { + const [blocks, setBlocks] = useState([ + // "Block1", + // "Block2", + // "Block3", + ]); + + const [logo, setLogo] = useState(null); + const [fileInputKey, setFileInputKey] = useState(0); // Key to force re-render of file input + + // Handle file selection + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setLogo(reader.result as string); // Set the logo state with the file's data URL + }; + reader.readAsDataURL(file); + } + }; + + // Trigger file input click + const handleClick = () => { + setFileInputKey((previousKey) => previousKey + 1); // Change key to reset file input + document.querySelector("#logo-upload-input")?.click(); + }; + + const moveBlockUp = (index: number) => { + if (index === 0) return; + const newBlocks = [...blocks]; + [newBlocks[index - 1], newBlocks[index]] = [ + newBlocks[index], + newBlocks[index - 1], + ]; + setBlocks(newBlocks); + }; + + const moveBlockDown = (index: number) => { + if (index === blocks.length - 1) return; + const newBlocks = [...blocks]; + [newBlocks[index + 1], newBlocks[index]] = [ + newBlocks[index], + newBlocks[index + 1], + ]; + setBlocks(newBlocks); + }; + + const deleteBlock = (index: number) => { + setBlocks(blocks.filter((_, index_) => index_ !== index)); + }; + + const copyBlock = (index: number) => { + const newBlocks = [...blocks]; + newBlocks.splice(index, 0, blocks[index]); + setBlocks(newBlocks); + }; + + return ( + + {children} + +
+
+
+
+ {logo ? ( + Logo + ) : ( +

+ Add your logo +

+ )} + +
+
+
+
+
+ Icon +
+
+ Icon +
+ +
+ Icon +
+
+ Icon +
+
+
+
+
+ Icon +
+
+

Welcome to Boilerplate!

+

Thanks for signing up

+
+
+
+
+ We're thrilled to have you join us. Experience quality and + innovation like never before. Our product is made to fit your + needs and make your life easier. +
+
+
+ Here’s what you can look forward to +
+
+
+ +
+ + Exclusive Offers: + {` `} + + + Enjoy special promotions and discounts available only to + our members. + +
+
+
+
+
+ +
+ + Exclusive Offers: + {` `} + + + Enjoy special promotions and discounts available only to + our members. + +
+
+
+
+
+ +
+ + Exclusive Offers: + {` `} + + + Enjoy special promotions and discounts available only to + our members. + +
+
+
+
+
+
+
+ +
+
+
+ ); +}; + +export const useEditor = () => { + const context = useContext(EditorContext); + if (!context) + throw new Error("useEditor must be used within an EditorProvider"); + return context; +}; diff --git a/app/components/context/TopNavigation.tsx b/app/components/context/TopNavigation.tsx new file mode 100644 index 00000000..8bcd9e28 --- /dev/null +++ b/app/components/context/TopNavigation.tsx @@ -0,0 +1,131 @@ +import { FunctionComponent } from "react"; + +export type TopNavigationType = { + className?: string; + icon1?: string; + icon2?: string; + icon3?: string; + icon4?: string; +}; + +const TopNavigation: FunctionComponent = ({ + className = "", + icon1 = "/icon1.svg", + icon2 = "/icon2.svg", + icon3 = "/icon3.svg", + icon4 = "/icon4.svg", +}) => { + return ( +
+
+
+ Home Icon + +
+ +
+
+
+
+ Search Icon + +
+
+ Notifications + Settings +
+ User Avatar + User Menu +
+
+
+ ); +}; + +export default TopNavigation; diff --git a/app/components/context/editor-context.tsx b/app/components/context/editor-context.tsx deleted file mode 100644 index c65f7278..00000000 --- a/app/components/context/editor-context.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import React, { createContext, useContext, useState } from "react"; - -interface Block { - id: string; - content: string; -} - -interface EditorContextType { - blocks: Block[]; - moveBlockUp: (id: string) => void; - moveBlockDown: (id: string) => void; - deleteBlock: (id: string) => void; -} - -const EditorContext = createContext(undefined); - -export const EditorProvider: React.FC<{ children: React.ReactNode }> = ({ - children, -}) => { - const [blocks, setBlocks] = useState([]); - - const moveBlockUp = (id: string) => { - setBlocks((previousBlocks) => { - const index = previousBlocks.findIndex((block) => block.id === id); - if (index > 0) { - const newBlocks = [...previousBlocks]; - [newBlocks[index - 1], newBlocks[index]] = [ - newBlocks[index], - newBlocks[index - 1], - ]; - return newBlocks; - } - return previousBlocks; - }); - }; - - const moveBlockDown = (id: string) => { - setBlocks((previousBlocks) => { - const index = previousBlocks.findIndex((block) => block.id === id); - if (index < previousBlocks.length - 1) { - const newBlocks = [...previousBlocks]; - [newBlocks[index], newBlocks[index + 1]] = [ - newBlocks[index + 1], - newBlocks[index], - ]; - return newBlocks; - } - return previousBlocks; - }); - }; - - const deleteBlock = (id: string) => { - setBlocks((previousBlocks) => - previousBlocks.filter((block) => block.id !== id), - ); - }; - - return ( - - {children} - - ); -}; - -export const useEditorContext = () => { - const context = useContext(EditorContext); - if (context === undefined) { - throw new Error("useEditorContext must be used within an EditorProvider"); - } - return context; -}; diff --git a/app/components/template/templateEditorBlock.tsx b/app/components/template/templateEditorBlock.tsx deleted file mode 100644 index 68c25cc0..00000000 --- a/app/components/template/templateEditorBlock.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import React, { useRef, useState } from "react"; - -import Toolbar from "../toolbar/toolbar"; - -interface BlockProperties { - id: string; - content: string; - children?: React.ReactNode; -} - -export const ReusableTemplateEditorBlock: React.FC = ({ - id, - content, - children, -}) => { - const [isActive, setIsActive] = useState(false); - const blockReference = useRef(null); - - const handleClick = () => { - setIsActive(true); - }; - - const handleBlur = (event: React.FocusEvent) => { - if (!blockReference.current?.contains(event.relatedTarget as Node)) { - setIsActive(false); - } - }; - - return ( -
-
- {children} - {isActive && ( -
- -
- )} -
- ); -}; - -// export default ReusableTemplateEditorBlock; diff --git a/app/components/templateEditorBlock.tsx b/app/components/templateEditorBlock.tsx new file mode 100644 index 00000000..c440abe7 --- /dev/null +++ b/app/components/templateEditorBlock.tsx @@ -0,0 +1,55 @@ +// src/components/TemplateEditorBlock.tsx +import React, { useEffect, useRef, useState } from "react"; + +import { useEditor } from "../components/context/EditorContext"; +import Toolbar from "./Toolbar"; + +interface TemplateEditorBlockProperties { + children: React.ReactNode; + index: number; +} + +const TemplateEditorBlock: React.FC = ({ + children, + index, +}) => { + const { moveBlockUp, moveBlockDown, deleteBlock, copyBlock } = useEditor(); + const [isActive, setIsActive] = useState(false); + const blockReference = useRef(null); + + const handleClickOutside = (event: MouseEvent) => { + if ( + blockReference.current && + !blockReference.current.contains(event.target as Node) + ) { + setIsActive(false); + } + }; + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
setIsActive(true)} + > + {children} + {isActive && ( + moveBlockUp(index)} + onMoveDown={() => moveBlockDown(index)} + onDelete={() => deleteBlock(index)} + onCopy={() => copyBlock(index)} + /> + )} +
+ ); +}; + +export default TemplateEditorBlock; diff --git a/app/components/toolbar/index.tsx b/app/components/toolbar/index.tsx deleted file mode 100644 index e5d70820..00000000 --- a/app/components/toolbar/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./toolbar"; diff --git a/app/components/toolbar/toolbar.tsx b/app/components/toolbar/toolbar.tsx deleted file mode 100644 index 111dbf68..00000000 --- a/app/components/toolbar/toolbar.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as ToolbarPrimitive from "@radix-ui/react-toolbar"; -import { ArrowDown, ArrowUp, Copy, LucideDelete } from "lucide-react"; -import React from "react"; - -import { useEditorContext } from "~/components/context/editor-context"; - -interface ToolbarProperties { - blockId: string; - content: string; -} - -const Toolbar: React.FC = ({ blockId, content }) => { - const { moveBlockUp, moveBlockDown, deleteBlock } = useEditorContext(); - - const handleCopy = () => { - navigator.clipboard.writeText(content); - }; - - const buttonClass = - "p-2 bg-white text-gray-700 rounded shadow-md transition-all duration-200 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-300"; - - return ( - - moveBlockUp(blockId)} - > - - - moveBlockDown(blockId)} - > - - - - - - deleteBlock(blockId)} - > - - - - ); -}; - -export default Toolbar; diff --git a/app/icon-10.svg b/app/icon-10.svg new file mode 100644 index 00000000..7ccbd599 --- /dev/null +++ b/app/icon-10.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/icon-11.svg b/app/icon-11.svg new file mode 100644 index 00000000..7ccbd599 --- /dev/null +++ b/app/icon-11.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/icon-12.svg b/app/icon-12.svg new file mode 100644 index 00000000..65c26ccc --- /dev/null +++ b/app/icon-12.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/icon-8.svg b/app/icon-8.svg new file mode 100644 index 00000000..f884ff76 --- /dev/null +++ b/app/icon-8.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/icon-9.svg b/app/icon-9.svg new file mode 100644 index 00000000..6691f82b --- /dev/null +++ b/app/icon-9.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/icon.svg b/app/icon.svg new file mode 100644 index 00000000..ff68ab83 --- /dev/null +++ b/app/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/routes/Editor.tsx b/app/routes/Editor.tsx new file mode 100644 index 00000000..b7feff34 --- /dev/null +++ b/app/routes/Editor.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +import { EditorProvider, useEditor } from "../components/context/EditorContext"; +import TemplateEditorBlock from "../components/templateEditorBlock"; + +const Editor: React.FC = () => { + const { blocks } = useEditor(); + + return ( +
+ {blocks.map((block, index) => ( + + {block} + + ))} +
+ ); +}; + +const EditorWithProvider: React.FC = () => ( + + + +); + +export default EditorWithProvider; diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index ac9d1617..51793576 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,7 +1,6 @@ import type { MetaFunction } from "@remix-run/node"; import { useState } from "react"; -// import { ReusableTemplateEditorBlock } from "~/components/template/templateEditorBlock"; import { Button } from "~/components/ui/button"; import CardPlatform from "~/components/ui/card/card-platform"; import OtpAuth from "~/components/ui/otp/OtpAuth"; @@ -68,9 +67,6 @@ export default function Index() { -
- {/* */} -
setOpenModal(!openModal)} diff --git a/package.json b/package.json index 92e29012..ca9445f7 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-toolbar": "^1.1.0", "@react-email/components": "^0.0.21", "@remix-run/css-bundle": "^2.10.3", "@remix-run/node": "^2.10.3", @@ -37,8 +38,8 @@ "clsx": "^2.1.1", "isbot": "^4.4.0", "lucide-react": "^0.408.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-email": "^2.1.5", "react-hook-form": "^7.52.1", "swiper": "^11.1.5", @@ -49,8 +50,8 @@ "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.3.1", "@remix-run/dev": "^2.10.3", - "@types/react": "^18.2.20", - "@types/react-dom": "^18.2.7", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", "autoprefixer": "^10.4.19", @@ -69,9 +70,10 @@ "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.4", - "typescript": "^5.1.6", + "typescript": "^5.5.3", "vite": "^5.1.0", - "vite-tsconfig-paths": "^4.2.1" + "vite-tsconfig-paths": "^4.2.1", + "vitest": "^2.0.3" }, "engines": { "node": ">=20.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a6cd8d8..8dd5e24d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@radix-ui/react-tabs': specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toolbar': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-email/components': specifier: ^0.0.21 version: 0.0.21(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -63,10 +66,10 @@ importers: specifier: ^0.408.0 version: 0.408.0(react@18.3.1) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-email: specifier: ^2.1.5 @@ -94,10 +97,10 @@ importers: specifier: ^2.10.3 version: 2.10.3(@remix-run/react@2.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@remix-run/serve@2.10.3(typescript@5.5.3))(@types/node@20.14.11)(terser@5.31.3)(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)) '@types/react': - specifier: ^18.2.20 + specifier: ^18.3.3 version: 18.3.3 '@types/react-dom': - specifier: ^18.2.7 + specifier: ^18.3.0 version: 18.3.0 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 @@ -154,7 +157,7 @@ importers: specifier: ^3.4.4 version: 3.4.6 typescript: - specifier: ^5.1.6 + specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.1.0 @@ -162,6 +165,9 @@ importers: vite-tsconfig-paths: specifier: ^4.2.1 version: 4.3.2(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)) + vitest: + specifier: ^2.0.3 + version: 2.0.3(@types/node@20.14.11)(terser@5.31.3) packages: @@ -1211,6 +1217,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-separator@1.1.0': + resolution: {integrity: sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==} + 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 + '@radix-ui/react-slot@1.0.2': resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -1281,6 +1300,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toolbar@1.1.0': + resolution: {integrity: sha512-ZUKknxhMTL/4hPh+4DuaTot9aO7UD6Kupj4gqXCsBTayX1pD1L+0C2/2VZKXb4tIifQklZ3pf2hG9T+ns+FclQ==} + 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 + '@radix-ui/react-tooltip@1.1.1': resolution: {integrity: sha512-LLE8nzNE4MzPMw3O2zlVlkLFid3y9hMUs7uCbSHyKSo+tCN4yMCf+ZCCcfrYgsOC0TiHBPQ1mtpJ2liY3ZT3SQ==} peerDependencies: @@ -1901,6 +1933,24 @@ packages: '@vanilla-extract/private@1.0.5': resolution: {integrity: sha512-6YXeOEKYTA3UV+RC8DeAjFk+/okoNz/h88R+McnzA2zpaVqTR/Ep+vszkWYlGBcMNO7vEkqbq5nT/JMMvhi+tw==} + '@vitest/expect@2.0.3': + resolution: {integrity: sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==} + + '@vitest/pretty-format@2.0.3': + resolution: {integrity: sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==} + + '@vitest/runner@2.0.3': + resolution: {integrity: sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==} + + '@vitest/snapshot@2.0.3': + resolution: {integrity: sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==} + + '@vitest/spy@2.0.3': + resolution: {integrity: sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==} + + '@vitest/utils@2.0.3': + resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==} + '@web3-storage/multipart-parser@1.0.0': resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} @@ -2076,6 +2126,10 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -2202,6 +2256,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2222,6 +2280,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2446,6 +2508,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -2871,6 +2937,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -3007,6 +3077,9 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -3023,6 +3096,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -3148,6 +3225,10 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -3340,6 +3421,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -3538,6 +3623,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3553,6 +3641,9 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} engines: {node: '>=0.10.0'} @@ -3733,6 +3824,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -3890,6 +3985,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3949,6 +4048,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -4024,6 +4127,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4041,6 +4148,10 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} @@ -4569,6 +4680,9 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4639,6 +4753,9 @@ packages: resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.10: resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} engines: {node: '>=6'} @@ -4647,6 +4764,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -4718,6 +4838,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -4841,6 +4965,21 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tinybench@2.8.0: + resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + + tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + engines: {node: '>=14.0.0'} + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -5066,6 +5205,11 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + vite-node@2.0.3: + resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-tsconfig-paths@4.3.2: resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: @@ -5102,6 +5246,31 @@ packages: terser: optional: true + vitest@2.0.3: + resolution: {integrity: sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.0.3 + '@vitest/ui': 2.0.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + watchpack@2.4.1: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} @@ -5155,6 +5324,11 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -6316,6 +6490,15 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-separator@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.8 @@ -6384,6 +6567,21 @@ snapshots: '@types/react': 18.2.47 '@types/react-dom': 18.3.0 + '@radix-ui/react-toggle-group@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.0)(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -6395,6 +6593,32 @@ snapshots: '@types/react': 18.2.47 '@types/react-dom': 18.3.0 + '@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-toolbar@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-tooltip@1.1.1(@types/react-dom@18.3.0)(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -7123,6 +7347,39 @@ snapshots: '@vanilla-extract/private@1.0.5': {} + '@vitest/expect@2.0.3': + dependencies: + '@vitest/spy': 2.0.3 + '@vitest/utils': 2.0.3 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.3': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.0.3': + dependencies: + '@vitest/utils': 2.0.3 + pathe: 1.1.2 + + '@vitest/snapshot@2.0.3': + dependencies: + '@vitest/pretty-format': 2.0.3 + magic-string: 0.30.10 + pathe: 1.1.2 + + '@vitest/spy@2.0.3': + dependencies: + tinyspy: 3.0.0 + + '@vitest/utils@2.0.3': + dependencies: + '@vitest/pretty-format': 2.0.3 + estree-walker: 3.0.3 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + '@web3-storage/multipart-parser@1.0.0': {} '@webassemblyjs/ast@1.12.1': @@ -7347,6 +7604,8 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} astring@1.8.6: {} @@ -7492,6 +7751,14 @@ snapshots: ccount@2.0.1: {} + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -7511,6 +7778,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.1: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -7699,6 +7968,8 @@ snapshots: dedent@1.5.3: {} + deep-eql@5.0.2: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -8373,6 +8644,18 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + execa@8.0.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + exit-hook@2.2.1: {} express@4.19.2: @@ -8540,6 +8823,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-func-name@2.0.2: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -8554,6 +8839,8 @@ snapshots: get-stream@6.0.1: {} + get-stream@8.0.1: {} + get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 @@ -8716,6 +9003,8 @@ snapshots: human-signals@2.1.0: {} + human-signals@5.0.0: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -8875,6 +9164,8 @@ snapshots: is-stream@2.0.1: {} + is-stream@3.0.0: {} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -9050,6 +9341,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -9062,6 +9357,10 @@ snapshots: dependencies: react: 18.3.1 + magic-string@0.30.10: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + markdown-extensions@1.1.1: {} marked@7.0.4: {} @@ -9422,6 +9721,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-fn@4.0.0: {} + min-indent@1.0.1: {} minimatch@3.1.2: @@ -9583,6 +9884,10 @@ snapshots: dependencies: path-key: 3.1.1 + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -9646,6 +9951,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -9732,6 +10041,8 @@ snapshots: path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} path-scurry@1.11.1: @@ -9745,6 +10056,8 @@ snapshots: pathe@1.1.2: {} + pathval@2.0.0: {} + peberminta@0.9.0: {} peek-stream@1.1.3: @@ -10365,6 +10678,8 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -10450,12 +10765,16 @@ snapshots: dependencies: minipass: 7.1.2 + stackback@0.0.2: {} + stacktrace-parser@0.1.10: dependencies: type-fest: 0.7.1 statuses@2.0.1: {} + std-env@3.7.0: {} + stop-iteration-iterator@1.0.0: dependencies: internal-slot: 1.0.7 @@ -10549,6 +10868,8 @@ snapshots: strip-final-newline@2.0.0: {} + strip-final-newline@3.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -10687,7 +11008,7 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - terser-webpack-plugin@5.3.10(@swc/core@1.3.101(@swc/helpers@0.5.12))(esbuild@0.19.11)(webpack@5.93.0(@swc/core@1.3.101(@swc/helpers@0.5.12))(esbuild@0.19.11)): + terser-webpack-plugin@5.3.10(@swc/core@1.3.101(@swc/helpers@0.5.12))(esbuild@0.19.11)(webpack@5.93.0(esbuild@0.17.6)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -10721,6 +11042,14 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 + tinybench@2.8.0: {} + + tinypool@1.0.0: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.0: {} + to-fast-properties@2.0.0: {} to-regex-range@5.0.1: @@ -10981,6 +11310,23 @@ snapshots: - supports-color - terser + vite-node@2.0.3(@types/node@20.14.11)(terser@5.31.3): + dependencies: + cac: 6.7.14 + debug: 4.3.5 + pathe: 1.1.2 + tinyrainbow: 1.2.0 + vite: 5.3.4(@types/node@20.14.11)(terser@5.31.3) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)): dependencies: debug: 4.3.5 @@ -11002,6 +11348,38 @@ snapshots: fsevents: 2.3.3 terser: 5.31.3 + vitest@2.0.3(@types/node@20.14.11)(terser@5.31.3): + dependencies: + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.3 + '@vitest/pretty-format': 2.0.3 + '@vitest/runner': 2.0.3 + '@vitest/snapshot': 2.0.3 + '@vitest/spy': 2.0.3 + '@vitest/utils': 2.0.3 + chai: 5.1.1 + debug: 4.3.5 + execa: 8.0.1 + magic-string: 0.30.10 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.8.0 + tinypool: 1.0.0 + tinyrainbow: 1.2.0 + vite: 5.3.4(@types/node@20.14.11)(terser@5.31.3) + vite-node: 2.0.3(@types/node@20.14.11)(terser@5.31.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.14.11 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + watchpack@2.4.1: dependencies: glob-to-regexp: 0.4.1 @@ -11044,7 +11422,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.3.101(@swc/helpers@0.5.12))(esbuild@0.19.11)(webpack@5.93.0(@swc/core@1.3.101(@swc/helpers@0.5.12))(esbuild@0.19.11)) + terser-webpack-plugin: 5.3.10(@swc/core@1.3.101(@swc/helpers@0.5.12))(esbuild@0.19.11)(webpack@5.93.0(esbuild@0.17.6)) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -11098,6 +11476,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: