diff --git a/apps/web/package.json b/apps/web/package.json index 9cbf6b3..74912a3 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -34,6 +34,7 @@ "msgpackr": "^1.10.2", "randomcolor": "^0.6.2", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-complex-tree": "^2.4.4", "react-dom": "^18.2.0", "react-hook-form": "^7.51.4", @@ -41,6 +42,7 @@ "react-resizable-panels": "^2.0.19", "server": "workspace:*", "tailwind-merge": "^2.3.0", + "tailwind-variants": "^0.2.1", "tailwindcss": "^3.4.10", "tseep": "^1.2.2", "valibot": "^0.40.0", diff --git a/apps/web/src/components/Button.tsx b/apps/web/src/components/Button.tsx index 29c47ac..d00a469 100644 --- a/apps/web/src/components/Button.tsx +++ b/apps/web/src/components/Button.tsx @@ -1,23 +1,44 @@ import { forwardRef } from "react"; +import { type VariantProps, tv } from "tailwind-variants"; import cn from "../utils/cn"; +const variants = tv({ + base: "focus:ring-4 font-medium rounded-lg me-2 mb-2 focus:outline-none", + variants: { + size: { + sm: "px-3 py-2 text-sm", + md: "px-5 py-2.5 text-sm", + lg: "px-5 py-3 text-base", + }, + color: { + primary: + "text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800", + secondary: + "text-gray-900 bg-white border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700", + danger: + "text-white bg-red-700 hover:bg-red-800 focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900", + }, + disabled: { + true: "bg-blue-400 dark:bg-blue-500 cursor-not-allowed hover:bg-blue-400 dark:hover:bg-blue-500", + }, + loading: { + true: "border text-gray-900 bg-white border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700", + }, + }, + defaultVariants: { + size: "md", + color: "primary", + }, +}); + const Button = forwardRef< HTMLButtonElement, - React.ButtonHTMLAttributes & { - loading?: boolean; - } + React.ButtonHTMLAttributes & VariantProps >(({ className, loading = false, children, disabled, ...props }, ref) => { return ( + + + + + + ); +}; + +export default NewFolderDialog; diff --git a/apps/web/src/components/FileTree/EditDialog.tsx b/apps/web/src/components/FileTree/RenameFileDialog.tsx similarity index 100% rename from apps/web/src/components/FileTree/EditDialog.tsx rename to apps/web/src/components/FileTree/RenameFileDialog.tsx diff --git a/apps/web/src/components/FileTree/RenameFolderDialog.tsx b/apps/web/src/components/FileTree/RenameFolderDialog.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/components/FileTree/index.tsx b/apps/web/src/components/FileTree/index.tsx index 762e690..87fbfc0 100644 --- a/apps/web/src/components/FileTree/index.tsx +++ b/apps/web/src/components/FileTree/index.tsx @@ -1,5 +1,10 @@ import * as ContextMenu from "@radix-ui/react-context-menu"; -import { Tree, UncontrolledTreeEnvironment } from "react-complex-tree"; +import { useState } from "react"; +import { + Tree, + type TreeItemIndex, + UncontrolledTreeEnvironment, +} from "react-complex-tree"; import useShowHide from "../../utils/useShowHide"; import type LoroDataProviderImplementation from "./dataProvider"; import NewFileDialog from "./newFileDialog"; @@ -10,12 +15,41 @@ const FileTree: React.FC<{ onChangeTab: (tabId: string) => void; }> = ({ treeProvider, rootId, onChangeTab }) => { const newFileDialog = useShowHide(); + const newFolderDialog = useShowHide(); const deleteFileDialog = useShowHide(); const renameFileDialog = useShowHide(); + const renameFolderDialog = useShowHide(); + + const [data, setData] = useState<{ + isFolder: boolean; + parentItemId: string; + } | null>(null); + + const openNewFileDialog = ( + isFolder: boolean, + parentItemId: TreeItemIndex, + ) => { + setData({ isFolder, parentItemId: String(parentItemId) }); + newFileDialog.show(); + }; + + const handleNewFileDialog = (name: string) => { + setData(null); + newFileDialog.hide(); + if (data) { + treeProvider.insertItem(data.parentItemId, { + data: name, + isFolder: data.isFolder, + }); + } + }; return ( - {}} /> +

Files

@@ -60,13 +94,17 @@ const FileTree: React.FC<{ <> newFileDialog.show()} + onSelect={() => + openNewFileDialog(false, item.item.index) + } > New file newFileDialog.show()} + onSelect={() => + openNewFileDialog(true, item.item.index) + } > New folder @@ -108,13 +146,13 @@ const FileTree: React.FC<{ newFileDialog.show()} + onSelect={() => openNewFileDialog(false, rootId)} > New file newFileDialog.show()} + onSelect={() => openNewFileDialog(true, rootId)} > New folder diff --git a/apps/web/src/components/FileTree/newFileDialog.tsx b/apps/web/src/components/FileTree/newFileDialog.tsx index 51452d6..b6c499b 100644 --- a/apps/web/src/components/FileTree/newFileDialog.tsx +++ b/apps/web/src/components/FileTree/newFileDialog.tsx @@ -21,21 +21,27 @@ type FileName = v.InferOutput; // million-ignore const NewFileDialog: React.FC<{ handler: ReturnType; - onFileCreate: (name: string) => void; + onFileCreate: (name: string) => unknown; }> = ({ handler, onFileCreate }) => { const { register, handleSubmit, setFocus, formState: { errors, isValid }, + reset, } = useForm({ resolver: valibotResolver(fileNameSchema), mode: "onChange", + defaultValues: { + name: "", + }, }); useEffect(() => { if (handler.visible) { setFocus("name"); + } else { + reset(); } }, [handler.visible]); @@ -47,33 +53,38 @@ const NewFileDialog: React.FC<{ return ( - - New File - - Enter the name of the new file - -
- - {errors.name && ( -

{errors.name.message}

- )} -
- - -
-
+ +
+ + New File + +
+
+
+ +
+
+
+ + +
); diff --git a/apps/web/src/components/Input.tsx b/apps/web/src/components/Input.tsx index 018ae1f..573a42f 100644 --- a/apps/web/src/components/Input.tsx +++ b/apps/web/src/components/Input.tsx @@ -22,7 +22,7 @@ const Input = forwardRef< { } }, [isValid]); - if (!state?.email) { + if (!history.state?.email) { toast.error("Invalid email"); return ; } diff --git a/bun.lockb b/bun.lockb index ccf5661..f360f29 100755 Binary files a/bun.lockb and b/bun.lockb differ