feat: new directory dialog

repalces the new item table row
This commit is contained in:
2025-09-26 23:04:02 +00:00
parent ce2e3c4f26
commit 3dfcdd84cf
6 changed files with 119 additions and 119 deletions

View File

@@ -53,7 +53,7 @@ import {
contextMenuTargeItemAtom,
dragInfoAtom,
itemBeingRenamedAtom,
newItemKindAtom,
newFileTypeAtom,
openedFileAtom,
optimisticDeletedItemsAtom,
} from "./state"
@@ -332,7 +332,6 @@ export function DirectoryContentTableContent() {
) : (
<NoResultsRow />
)}
<NewItemRow />
</TableBody>
</Table>
</div>
@@ -340,10 +339,6 @@ export function DirectoryContentTableContent() {
}
function NoResultsRow() {
const newItemKind = useAtomValue(newItemKindAtom)
if (newItemKind) {
return null
}
return (
<TableRow>
<TableCell colSpan={columns.length} className="text-center">
@@ -353,108 +348,6 @@ function NoResultsRow() {
)
}
function NewItemRow() {
const { directory } = useContext(DirectoryPageContext)
const inputRef = useRef<HTMLInputElement>(null)
const newItemFormId = useId()
const [newItemKind, setNewItemKind] = useAtom(newItemKindAtom)
const { mutate: createDirectory, isPending } = useMutation({
mutationFn: useContextMutation(api.files.createDirectory),
onSuccess: () => {
setNewItemKind(null)
},
onError: withDefaultOnError(() => {
setTimeout(() => {
inputRef.current?.focus()
}, 1)
}),
})
// Auto-focus the input when newItemKind changes to a truthy value
useEffect(() => {
if (newItemKind && inputRef.current) {
// Use requestAnimationFrame to ensure the component is fully rendered
// and the dropdown has completed its close cycle
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.focus()
inputRef.current.select() // Also select the default text for better UX
}
})
}
}, [newItemKind])
if (!newItemKind) {
return null
}
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const itemName = formData.get("itemName") as string
if (itemName) {
createDirectory({ name: itemName, directoryId: directory._id })
} else {
toast.error("Please enter a name.")
}
}
const clearNewItemKind = () => {
// setItemBeingAdded(null)
setNewItemKind(null)
}
return (
<TableRow className={cn("align-middle", { "opacity-50": isPending })}>
<TableCell />
<TableCell className="p-0">
<div className="flex items-center gap-2 px-2 py-1 h-full">
{isPending ? (
<LoadingSpinner className="size-4" />
) : (
<DirectoryIcon className="size-4" />
)}
<form
className="w-full"
id={newItemFormId}
onSubmit={onSubmit}
>
<input
ref={inputRef}
type="text"
name="itemName"
defaultValue={newItemKind}
disabled={isPending}
className="w-full h-8 px-2 bg-transparent border border-input rounded-sm outline-none focus:border-primary focus:ring-1 focus:ring-primary"
/>
</form>
</div>
</TableCell>
<TableCell />
<TableCell align="right" className="space-x-2 p-1">
{!isPending ? (
<>
<Button
type="button"
form={newItemFormId}
variant="ghost"
size="icon"
onClick={clearNewItemKind}
>
<XIcon />
</Button>
<Button type="submit" form={newItemFormId} size="icon">
<CheckIcon />
</Button>
</>
) : null}
</TableCell>
</TableRow>
)
}
function FileItemRow({
table,
row,

View File

@@ -1,7 +1,8 @@
import { api } from "@fileone/convex/_generated/api"
import type {
DirectoryHandle,
PathComponent,
import {
type DirectoryHandle,
FileType,
type PathComponent,
} from "@fileone/convex/model/filesystem"
import { useMutation } from "@tanstack/react-query"
import { Link } from "@tanstack/react-router"
@@ -38,14 +39,17 @@ import {
TooltipContent,
TooltipTrigger,
} from "../../components/ui/tooltip"
import { WithAtom } from "../../components/with-atom"
import { useFileDrop } from "../../files/use-file-drop"
import { cn } from "../../lib/utils"
import { DirectoryPageContext } from "./context"
import { DirectoryContentTable } from "./directory-content-table"
import { NewDirectoryDialog } from "./new-directory-dialog"
import { RenameFileDialog } from "./rename-file-dialog"
import { dragInfoAtom, newItemKindAtom, openedFileAtom } from "./state"
import { dragInfoAtom, newFileTypeAtom, openedFileAtom } from "./state"
export function DirectoryPage() {
const { directory } = useContext(DirectoryPageContext)
return (
<>
<header className="flex py-1 shrink-0 items-center gap-2 border-b px-4 w-full">
@@ -60,6 +64,19 @@ export function DirectoryPage() {
</div>
<RenameFileDialog />
<PreviewDialog />
<WithAtom atom={newFileTypeAtom}>
{(newFileType, setNewFileType) => (
<NewDirectoryDialog
open={newFileType === FileType.Directory}
directoryId={directory._id}
onOpenChange={(open) => {
if (!open) {
setNewFileType(null)
}
}}
/>
)}
</WithAtom>
</>
)
}
@@ -194,16 +211,15 @@ function UploadFileButton() {
}
function NewDirectoryItemDropdown() {
const setNewItemKind = useSetAtom(newItemKindAtom)
const newItemKind = useAtomValue(newItemKindAtom)
const [newFileType, setNewFileType] = useAtom(newFileTypeAtom)
const addNewDirectory = () => {
setNewItemKind("directory")
setNewFileType(FileType.Directory)
}
const handleCloseAutoFocus = (event: Event) => {
// If we just created a new item, prevent the dropdown from restoring focus to the trigger
if (newItemKind) {
if (newFileType) {
event.preventDefault()
}
}
@@ -236,8 +252,6 @@ function PreviewDialog() {
if (!openedFile) return null
console.log("openedFile", openedFile)
switch (openedFile.mimeType) {
case "image/jpeg":
case "image/png":

View File

@@ -0,0 +1,72 @@
import { api } from "@fileone/convex/_generated/api"
import type { Id } from "@fileone/convex/_generated/dataModel"
import { useMutation } from "@tanstack/react-query"
import { useMutation as useContextMutation } from "convex/react"
import { useId } from "react"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
export function NewDirectoryDialog({
open,
onOpenChange,
directoryId,
}: {
open: boolean
onOpenChange: (open: boolean) => void
directoryId: Id<"directories">
}) {
const formId = useId()
const { mutate: createDirectory, isPending: isCreating } = useMutation({
mutationFn: useContextMutation(api.files.createDirectory),
onSuccess: () => {
onOpenChange(false)
toast.success("Directory created successfully")
},
})
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const name = formData.get("directoryName") as string
if (name) {
createDirectory({ name, directoryId })
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>New Directory</DialogTitle>
</DialogHeader>
<form id={formId} onSubmit={onSubmit}>
<Input name="directoryName" />
</form>
<DialogFooter>
<DialogClose asChild>
<Button loading={isCreating} variant="outline">
<span>Cancel</span>
</Button>
</DialogClose>
<Button loading={isCreating} type="submit" form={formId}>
<span>Create</span>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -3,6 +3,7 @@ import type {
DirectoryItem,
DirectoryItemKind,
} from "@fileone/convex/model/directories"
import type { FileType } from "@fileone/convex/model/filesystem"
import type { RowSelectionState } from "@tanstack/react-table"
import { atom } from "jotai"
import type { FileDragInfo } from "../../files/use-file-drop"
@@ -14,7 +15,7 @@ export const optimisticDeletedItemsAtom = atom(
export const selectedFileRowsAtom = atom<RowSelectionState>({})
export const newItemKindAtom = atom<DirectoryItemKind | null>(null)
export const newFileTypeAtom = atom<FileType | null>(null)
export const itemBeingRenamedAtom = atom<{
kind: DirectoryItemKind