Skip to content

Commit

Permalink
♻️ Use tailwind-variants
Browse files Browse the repository at this point in the history
  • Loading branch information
vadolasi committed Sep 2, 2024
1 parent 76b75a5 commit b359e16
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 47 deletions.
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
"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",
"react-hot-toast": "^2.4.1",
"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",
Expand Down
43 changes: 32 additions & 11 deletions apps/web/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLButtonElement> & {
loading?: boolean;
}
React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof variants>
>(({ className, loading = false, children, disabled, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
"text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800",
disabled &&
"bg-blue-400 dark:bg-blue-500 cursor-not-allowed hover:bg-blue-400 dark:hover:bg-blue-500",
loading &&
"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",
className,
)}
className={cn(variants({ loading, disabled, ...props }), className)}
disabled={disabled || loading}
{...props}
>
Expand Down
85 changes: 85 additions & 0 deletions apps/web/src/components/FileTree/NewFolderDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { valibotResolver } from "@hookform/resolvers/valibot";
import * as Dialog from "@radix-ui/react-dialog";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import * as v from "valibot";
import type useShowHide from "../../utils/useShowHide";
import Button from "../Button";
import Input from "../Input";

const fileNameSchema = v.object({
name: v.pipe(
v.string(),
v.minLength(1, "File name must not be empty"),
v.maxLength(255, "File name must not exceed 255 characters"),
v.regex(/^[^/\\]+$/, "File name must not contain / or \\"),
v.transform((value) => value.trim()),
),
});
type FileName = v.InferOutput<typeof fileNameSchema>;

// million-ignore
const NewFolderDialog: React.FC<{
handler: ReturnType<typeof useShowHide>;
onFileCreate: (name: string) => unknown;
}> = ({ handler, onFileCreate }) => {
const {
register,
handleSubmit,
setFocus,
formState: { errors, isValid },
} = useForm<FileName>({
resolver: valibotResolver(fileNameSchema),
mode: "onChange",
defaultValues: {
name: "",
},
});

useEffect(() => {
if (handler.visible) {
setFocus("name");
}
}, [handler.visible]);

const onSubmit = (data: FileName) => {
onFileCreate(data.name);
handler.hide();
};

return (
<Dialog.Root open={handler.visible} onOpenChange={handler.toggle}>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-50" />
<Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white p-4 rounded-md shadow-md w-96 dark:bg-gray-700">
<Dialog.Title className="text-lg font-bold">New File</Dialog.Title>
<Dialog.Description className="text-sm text-gray-500">
Enter the name of the new file
</Dialog.Description>
<form className="mt-4" onSubmit={handleSubmit(onSubmit)}>
<Input type="text" label="Name" {...register("name")} />
{errors.name && (
<p className="text-sm text-red-500 mt-1">{errors.name.message}</p>
)}
<div className="flex justify-end mt-4">
<Button
className="px-4 py-2 text-sm text-white bg-blue-500 rounded-md"
onClick={handler.hide}
type="button"
>
Cancel
</Button>
<Button
className="px-4 py-2 ml-2 text-sm text-white bg-blue-500 rounded-md"
type="submit"
disabled={!isValid}
>
Create
</Button>
</div>
</form>
</Dialog.Content>
</Dialog.Root>
);
};

export default NewFolderDialog;
Empty file.
50 changes: 44 additions & 6 deletions apps/web/src/components/FileTree/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 (
<ContextMenu.Root>
<NewFileDialog handler={newFileDialog} onFileCreate={(name) => {}} />
<NewFileDialog
handler={newFileDialog}
onFileCreate={handleNewFileDialog}
/>
<h2 className="font-bold">Files</h2>
<ContextMenu.Trigger asChild>
<div className="h-full">
Expand Down Expand Up @@ -60,13 +94,17 @@ const FileTree: React.FC<{
<>
<ContextMenu.Item
className="select-none cursor-pointer"
onSelect={() => newFileDialog.show()}
onSelect={() =>
openNewFileDialog(false, item.item.index)
}
>
New file
</ContextMenu.Item>
<ContextMenu.Item
className="select-none cursor-pointer"
onSelect={() => newFileDialog.show()}
onSelect={() =>
openNewFileDialog(true, item.item.index)
}
>
New folder
</ContextMenu.Item>
Expand Down Expand Up @@ -108,13 +146,13 @@ const FileTree: React.FC<{
<ContextMenu.Content className="bg-slate-100 p-2 rounded-md w-48">
<ContextMenu.Item
className="select-none cursor-pointer"
onSelect={() => newFileDialog.show()}
onSelect={() => openNewFileDialog(false, rootId)}
>
New file
</ContextMenu.Item>
<ContextMenu.Item
className="select-none cursor-pointer"
onSelect={() => newFileDialog.show()}
onSelect={() => openNewFileDialog(true, rootId)}
>
New folder
</ContextMenu.Item>
Expand Down
67 changes: 39 additions & 28 deletions apps/web/src/components/FileTree/newFileDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,27 @@ type FileName = v.InferOutput<typeof fileNameSchema>;
// million-ignore
const NewFileDialog: React.FC<{
handler: ReturnType<typeof useShowHide>;
onFileCreate: (name: string) => void;
onFileCreate: (name: string) => unknown;
}> = ({ handler, onFileCreate }) => {
const {
register,
handleSubmit,
setFocus,
formState: { errors, isValid },
reset,
} = useForm<FileName>({
resolver: valibotResolver(fileNameSchema),
mode: "onChange",
defaultValues: {
name: "",
},
});

useEffect(() => {
if (handler.visible) {
setFocus("name");
} else {
reset();
}
}, [handler.visible]);

Expand All @@ -47,33 +53,38 @@ const NewFileDialog: React.FC<{
return (
<Dialog.Root open={handler.visible} onOpenChange={handler.toggle}>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-50" />
<Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white p-4 rounded-md shadow-md w-96 dark:bg-gray-700">
<Dialog.Title className="text-lg font-bold">New File</Dialog.Title>
<Dialog.Description className="text-sm text-gray-500">
Enter the name of the new file
</Dialog.Description>
<form className="mt-4" onSubmit={handleSubmit(onSubmit)}>
<Input type="text" label="Name" {...register("name")} />
{errors.name && (
<p className="text-sm text-red-500 mt-1">{errors.name.message}</p>
)}
<div className="flex justify-end mt-4">
<Button
className="px-4 py-2 text-sm text-white bg-blue-500 rounded-md"
onClick={handler.hide}
type="button"
>
Cancel
</Button>
<Button
className="px-4 py-2 ml-2 text-sm text-white bg-blue-500 rounded-md"
type="submit"
disabled={!isValid}
>
Create
</Button>
</div>
</form>
<Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-md border shadow-md dark:bg-gray-700 dark:border-gray-700 w-full max-w-lg max-h-full">
<div className="flex items-center justify-between p-4 md:p-5 border-b dark:border-gray-600">
<Dialog.Title className="text-xl font-semibold text-gray-900 dark:text-white">
New File
</Dialog.Title>
</div>
<div className="p-4 md:p-5 space-y-4">
<form className="mt-4" onSubmit={handleSubmit(onSubmit)}>
<Input
type="text"
label="Name"
error={errors.name?.message}
{...register("name")}
/>
</form>
</div>
<div className="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600">
<Button
className="px-4 py-2 text-sm text-white bg-blue-500 rounded-md"
onClick={handler.hide}
type="button"
>
Cancel
</Button>
<Button
className="px-4 py-2 ml-2 text-sm text-white bg-blue-500 rounded-md"
type="submit"
disabled={!isValid}
>
Create
</Button>
</div>
</Dialog.Content>
</Dialog.Root>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const Input = forwardRef<
<input
ref={ref}
className={cn(
"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
error &&
"bg-red-50 border-red-500 text-red-900 placeholder-red-700 focus:ring-red-500 focus:border-red-500 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500",
className,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/auth/confirm-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const ConfirmEmailPage: React.FC = () => {
}
}, [isValid]);

if (!state?.email) {
if (!history.state?.email) {
toast.error("Invalid email");
return <Redirect to="/auth/login" />;
}
Expand Down
Binary file modified bun.lockb
Binary file not shown.

0 comments on commit b359e16

Please sign in to comment.