From 044480f7ebfe8860984a98abd22cf05e5f31f428 Mon Sep 17 00:00:00 2001 From: Precious Oritsedere Date: Fri, 2 Oct 2026 15:40:15 +0100 Subject: [PATCH 1/6] add shadcn item --- components/ui/item.tsx | 201 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 components/ui/item.tsx diff --git a/components/ui/item.tsx b/components/ui/item.tsx new file mode 100644 index 0000000..0370c66 --- /dev/null +++ b/components/ui/item.tsx @@ -0,0 +1,201 @@ +import * as React from "react" +import { mergeProps } from "@base-ui/react/merge-props" +import { useRender } from "@base-ui/react/use-render" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +import { Separator } from "@/components/ui/separator" + +function ItemGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +const itemVariants = cva( + "group/item flex w-full flex-wrap items-center rounded-2xl border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted", + { + variants: { + variant: { + default: "border-transparent", + outline: "border-border", + muted: "border-transparent bg-muted/50", + }, + size: { + default: "gap-3.5 px-4 py-3.5", + sm: "gap-3.5 px-3.5 py-3", + xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Item({ + className, + variant = "default", + size = "default", + render, + ...props +}: useRender.ComponentProps<"div"> & VariantProps) { + return useRender({ + defaultTagName: "div", + props: mergeProps<"div">( + { + className: cn(itemVariants({ variant, size, className })), + }, + props + ), + render, + state: { + slot: "item", + variant, + size, + }, + }) +} + +const itemMediaVariants = cva( + "flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none", + { + variants: { + variant: { + default: "bg-transparent", + icon: "[&_svg:not([class*='size-'])]:size-4", + image: + "size-10 overflow-hidden rounded-xl group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 group-data-[size=xs]/item:rounded-lg [&_img]:size-full [&_img]:object-cover", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function ItemMedia({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function ItemContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +

a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", + className + )} + {...props} + /> + ) +} + +function ItemActions({ className, ...props }: React.ComponentProps<"div">) { + return ( +

+ ) +} + +function ItemHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Item, + ItemMedia, + ItemContent, + ItemActions, + ItemGroup, + ItemSeparator, + ItemTitle, + ItemDescription, + ItemHeader, + ItemFooter, +} From 8ba6083e7d101e683a3ca63262754576169b2987 Mon Sep 17 00:00:00 2001 From: Precious Oritsedere Date: Fri, 2 Oct 2026 15:42:33 +0100 Subject: [PATCH 2/6] Wrap list view in shadcn ItemGroup --- app/components/FileList.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/components/FileList.tsx b/app/components/FileList.tsx index 8c58d55..fbc0a4e 100644 --- a/app/components/FileList.tsx +++ b/app/components/FileList.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import FileItem, { FileItemData } from "./FileItem"; import Toolbar from "./shared/Toolbar"; import EmptyState from "./shared/EmptyState"; +import { ItemGroup } from "@/components/ui/item"; interface FileListProps { files: FileItemData[]; @@ -81,7 +82,7 @@ export default function FileList({ ))}
) : ( -
+ {files.map((file) => ( ))} -
+ )} From 3e41a6258ee4270a4e10fc1342a49e0349c08b6c Mon Sep 17 00:00:00 2001 From: Precious Oritsedere Date: Fri, 2 Oct 2026 15:47:09 +0100 Subject: [PATCH 3/6] Restyle list FileItem with shadcn Item --- app/components/FileItem.tsx | 86 ++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/app/components/FileItem.tsx b/app/components/FileItem.tsx index a72418d..afab8e1 100644 --- a/app/components/FileItem.tsx +++ b/app/components/FileItem.tsx @@ -3,6 +3,13 @@ import { useState, useRef } from "react"; import { getFileIcon, formatFileSize, formatDate, type FileType } from "../lib/helpers"; import FileItemMenu from "./FileItemMenu"; +import { + Item, + ItemActions, + ItemContent, + ItemMedia, + ItemTitle, +} from "@/components/ui/item"; export type { FileType }; @@ -61,7 +68,7 @@ export default function FileItem({ } clickCountRef.current += 1; - + if (clickCountRef.current === 1) { clickTimeoutRef.current = setTimeout(() => { if (clickCountRef.current === 1) { @@ -88,13 +95,13 @@ export default function FileItem({ setTimeout(() => { touchHandledRef.current = false; }, 400); - + const currentTime = new Date().getTime(); const tapLength = currentTime - lastTapRef.current; - + if (tapLength < 300 && tapLength > 0) { // Double tap detected - + if (clickTimeoutRef.current) { clearTimeout(clickTimeoutRef.current); clickTimeoutRef.current = null; @@ -112,7 +119,7 @@ export default function FileItem({ clickTimeoutRef.current = null; }, 300); } - + lastTapRef.current = currentTime; }; @@ -120,8 +127,8 @@ export default function FileItem({ return (
setIsHovered(true)} @@ -162,10 +169,11 @@ export default function FileItem({ // List view return ( -
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={handleClick} @@ -179,32 +187,42 @@ export default function FileItem({ onContextMenu?.(file, event); }} > -
+ {getFileIcon(file.type, file.mimeType)} -
-
-

{file.name}

-
-
- {file.lastModified && formatDate(file.lastModified)} -
-
- {file.size && formatFileSize(file.size)} -
+ + + + {file.name} + + + + + {file.lastModified ? formatDate(file.lastModified) : ""} + + + + + + {file.size ? formatFileSize(file.size) : ""} + + + {isHovered && ( - + + + )} -
+ ); } From d0426cdab347069b9b7bc86b942708fe59310a6c Mon Sep 17 00:00:00 2001 From: Precious Oritsedere Date: Fri, 2 Oct 2026 15:49:32 +0100 Subject: [PATCH 4/6] Restyle grid FileItem with shadcn Item --- app/components/FileItem.tsx | 49 ++++++++++++++++++++----------------- app/components/FileList.tsx | 4 +-- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/app/components/FileItem.tsx b/app/components/FileItem.tsx index afab8e1..c9469d4 100644 --- a/app/components/FileItem.tsx +++ b/app/components/FileItem.tsx @@ -125,12 +125,11 @@ export default function FileItem({ if (view === "grid") { return ( -
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={handleClick} @@ -145,25 +144,29 @@ export default function FileItem({ }} > {isHovered && ( - + + + )} -
+ {getFileIcon(file.type, file.mimeType)} -
-

- {file.name} -

-
+ + + + {file.name} + + + ); } diff --git a/app/components/FileList.tsx b/app/components/FileList.tsx index fbc0a4e..c33c6f2 100644 --- a/app/components/FileList.tsx +++ b/app/components/FileList.tsx @@ -61,7 +61,7 @@ export default function FileList({ {files.length === 0 ? ( ) : view === "grid" ? ( -
+ {files.map((file) => ( ))} -
+ ) : ( {files.map((file) => ( From 815aaa6fe3898cedae9f186d945a73cfe7535272 Mon Sep 17 00:00:00 2001 From: Precious Oritsedere Date: Fri, 2 Oct 2026 15:53:16 +0100 Subject: [PATCH 5/6] Polish file list and grid Item styling --- app/components/FileItem.tsx | 42 +++++++++++++++++++++++-------------- app/components/FileList.tsx | 4 ++-- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/app/components/FileItem.tsx b/app/components/FileItem.tsx index c9469d4..907e335 100644 --- a/app/components/FileItem.tsx +++ b/app/components/FileItem.tsx @@ -7,6 +7,7 @@ import { Item, ItemActions, ItemContent, + ItemDescription, ItemMedia, ItemTitle, } from "@/components/ui/item"; @@ -123,12 +124,21 @@ export default function FileItem({ lastTapRef.current = currentTime; }; + const secondaryLabel = + file.lastModified + ? formatDate(file.lastModified) + : file.type === "folder" + ? "Folder" + : file.size + ? formatFileSize(file.size) + : "File"; + if (view === "grid") { return ( setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} @@ -144,7 +154,7 @@ export default function FileItem({ }} > {isHovered && ( - + )} - + {getFileIcon(file.type, file.mimeType)} - + {file.name} + + {secondaryLabel} + ); @@ -173,7 +186,7 @@ export default function FileItem({ // List view return ( - + {getFileIcon(file.type, file.mimeType)} - + {file.name} + + {secondaryLabel} + - - - {file.lastModified ? formatDate(file.lastModified) : ""} - - - - + - {file.size ? formatFileSize(file.size) : ""} + {file.size ? formatFileSize(file.size) : file.type === "folder" ? "—" : ""} diff --git a/app/components/FileList.tsx b/app/components/FileList.tsx index c33c6f2..014ac82 100644 --- a/app/components/FileList.tsx +++ b/app/components/FileList.tsx @@ -61,7 +61,7 @@ export default function FileList({ {files.length === 0 ? ( ) : view === "grid" ? ( - + {files.map((file) => ( ) : ( - + {files.map((file) => ( Date: Fri, 2 Oct 2026 15:54:14 +0100 Subject: [PATCH 6/6] Align toolbar spacing with file browser chrome --- app/components/shared/Toolbar.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/components/shared/Toolbar.tsx b/app/components/shared/Toolbar.tsx index 312e304..98c8935 100644 --- a/app/components/shared/Toolbar.tsx +++ b/app/components/shared/Toolbar.tsx @@ -18,8 +18,8 @@ export default function Toolbar({ actions, }: ToolbarProps) { return ( -
-