From eb8820d888f8cc12142e37161e0f93d13166715b Mon Sep 17 00:00:00 2001 From: Richard R Date: Wed, 27 May 2026 19:35:03 -0600 Subject: [PATCH] feat(doclist): enhance document list UI with overlay uploader and improved file size formatting Integrate DocumentUploader in overlay mode to wrap all document views, enabling drag-and-drop uploads across the entire list area. Refine file size formatting to support KB, MB, and GB units in all views and tiles. Standardize button scaling and padding for consistent UI rhythm. Update DocumentPreview and detail panels to display both type and human-readable size. Add overlay drag visual feedback for uploads. These changes improve usability, visual clarity, and consistency in the document list interface. --- src/components/SettingsModal.tsx | 6 +- src/components/auth/UserMenu.tsx | 6 +- src/components/doclist/DocumentList.tsx | 116 +++++++++--------- src/components/doclist/DocumentPreview.tsx | 12 +- src/components/doclist/views/ColumnsView.tsx | 7 +- src/components/doclist/views/DocumentTile.tsx | 92 +++++++++----- src/components/doclist/views/GalleryView.tsx | 6 +- src/components/doclist/views/ListView.tsx | 3 + .../doclist/window/FinderSidebar.tsx | 2 +- .../doclist/window/FinderToolbar.tsx | 3 +- src/components/documents/DocumentUploader.tsx | 39 +++++- 11 files changed, 187 insertions(+), 105 deletions(-) diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index 4c25005..67b49ec 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -501,11 +501,11 @@ export function SettingsModal({ className = '' }: { className?: string }) { onClick={() => { void requestOpenSettings(); }} - className={`inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase hover:text-accent transition-transform transition-colors duration-200 ease-out hover:scale-[1.08] ${className}`} + className={`inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase hover:text-accent transition-transform transition-colors duration-200 ease-out hover:scale-[1.01] ${className}`} aria-label="Settings" tabIndex={0} > - + @@ -1447,7 +1447,7 @@ function SettingsChangelogPanel({
{enableUserSignups && ( - @@ -49,7 +49,7 @@ export function UserMenu({ className = '' }: { className?: string }) {
)} - {fallbackViewMode === 'icons' && ( - - setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) - } - onDropOnFolder={handleDropOnFolder} - onMergeIntoFolder={handleMergeIntoFolder} - /> - )} - {fallbackViewMode === 'list' && ( - { - setSortBy(b); - setSortDirection(d); - }} - collapsedFolders={collapsedFolders} - onToggleCollapse={toggleFolderCollapse} - onDeleteFolder={handleDeleteFolder} - onDeleteDoc={(doc) => - setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) - } - onDropOnFolder={handleDropOnFolder} - onMergeIntoFolder={handleMergeIntoFolder} - /> - )} - {fallbackViewMode === 'columns' && ( - - setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) - } - onDropOnFolder={handleDropOnFolder} - onMergeIntoFolder={handleMergeIntoFolder} - /> - )} - {fallbackViewMode === 'gallery' && ( - - setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) - } - onDropOnFolder={handleDropOnFolder} - onMergeIntoFolder={handleMergeIntoFolder} - /> - )} + + {fallbackViewMode === 'icons' && ( + + setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) + } + onDropOnFolder={handleDropOnFolder} + onMergeIntoFolder={handleMergeIntoFolder} + /> + )} + {fallbackViewMode === 'list' && ( + { + setSortBy(b); + setSortDirection(d); + }} + collapsedFolders={collapsedFolders} + onToggleCollapse={toggleFolderCollapse} + onDeleteFolder={handleDeleteFolder} + onDeleteDoc={(doc) => + setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) + } + onDropOnFolder={handleDropOnFolder} + onMergeIntoFolder={handleMergeIntoFolder} + /> + )} + {fallbackViewMode === 'columns' && ( + + setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) + } + onDropOnFolder={handleDropOnFolder} + onMergeIntoFolder={handleMergeIntoFolder} + /> + )} + {fallbackViewMode === 'gallery' && ( + + setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type }) + } + onDropOnFolder={handleDropOnFolder} + onMergeIntoFolder={handleMergeIntoFolder} + /> + )} + )} -
- {isGenerating ? '…' : typeLabel} +
+ {isGenerating + ? '…' + : `${typeLabel} • ${ + doc.size >= 1024 * 1024 * 1024 + ? `${(doc.size / 1024 / 1024 / 1024).toFixed(2)} GB` + : doc.size >= 1024 * 1024 + ? `${(doc.size / 1024 / 1024).toFixed(2)} MB` + : `${(doc.size / 1024).toFixed(1)} KB` + }`}
); diff --git a/src/components/doclist/views/ColumnsView.tsx b/src/components/doclist/views/ColumnsView.tsx index 0af734b..0142433 100644 --- a/src/components/doclist/views/ColumnsView.tsx +++ b/src/components/doclist/views/ColumnsView.tsx @@ -216,7 +216,12 @@ export function ColumnsView({ {selected.doc.name}

- {selected.doc.type.toUpperCase()} • {(selected.doc.size / 1024 / 1024).toFixed(2)} MB + {selected.doc.type.toUpperCase()} •{' '} + {selected.doc.size >= 1024 * 1024 * 1024 + ? `${(selected.doc.size / 1024 / 1024 / 1024).toFixed(2)} GB` + : selected.doc.size >= 1024 * 1024 + ? `${(selected.doc.size / 1024 / 1024).toFixed(2)} MB` + : `${(selected.doc.size / 1024).toFixed(1)} KB`}

= { sm: 'text-[10px]', - md: 'text-[12px]', - lg: 'text-[13px]', - xl: 'text-[14px]', + md: 'text-[11px]', + lg: 'text-[12px]', + xl: 'text-[13px]', + }; + + const bottomPaddingClasses: Record = { + sm: 'px-[7px] py-[4px]', + md: 'px-[8px] py-[5px]', + lg: 'px-[9px] py-[5px]', + xl: 'px-[10px] py-[6px]', + }; + + const linkPaddingClasses: Record = { + sm: 'px-[2px] py-[2px]', + md: 'px-[2px] py-[2px]', + lg: 'px-[2px] py-[2px]', + xl: 'px-[2px] py-[2px]', + }; + + const gapClasses: Record = { + sm: 'gap-1', + md: 'gap-1.5', + lg: 'gap-2', + xl: 'gap-2', + }; + + const fileIconClasses: Record = { + sm: 'w-3 h-3', + md: 'w-3.5 h-3.5', + lg: 'w-3.5 h-3.5', + xl: 'w-4 h-4', + }; + + const trashBtnClasses: Record = { + sm: 'ml-0.5 h-[18px] w-[18px] rounded-sm', + md: 'ml-0.5 h-[21px] w-[21px] rounded-sm', + lg: 'ml-1 h-[23px] w-[23px] rounded', + xl: 'ml-1.5 h-[25px] w-[25px] rounded', + }; + + const trashIconClasses: Record = { + sm: 'w-[10px] h-[10px]', + md: 'w-[11px] h-[11px]', + lg: 'w-[12px] h-[12px]', + xl: 'w-[13px] h-[13px]', }; return ( @@ -140,49 +181,40 @@ export function DocumentTile({ > -
+
- + {doc.type === 'pdf' ? ( - + ) : doc.type === 'epub' ? ( - + ) : ( - + )} - - - {doc.name} - - - {(doc.size / 1024 / 1024).toFixed(2)} MB - + + {doc.name} {showDeleteButton && (
diff --git a/src/components/doclist/views/ListView.tsx b/src/components/doclist/views/ListView.tsx index 1233184..e6bc86a 100644 --- a/src/components/doclist/views/ListView.tsx +++ b/src/components/doclist/views/ListView.tsx @@ -36,6 +36,9 @@ function formatDate(ms: number): string { function formatSize(bytes: number): string { const mb = bytes / 1024 / 1024; + if (mb >= 1024) { + return `${(mb / 1024).toFixed(2)} GB`; + } return mb >= 1 ? `${mb.toFixed(2)} MB` : `${(bytes / 1024).toFixed(1)} KB`; } diff --git a/src/components/doclist/window/FinderSidebar.tsx b/src/components/doclist/window/FinderSidebar.tsx index 5298520..7de467b 100644 --- a/src/components/doclist/window/FinderSidebar.tsx +++ b/src/components/doclist/window/FinderSidebar.tsx @@ -47,7 +47,7 @@ function SidebarRow({ type="button" onClick={onClick} className={ - 'group w-full flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition-all duration-200 ease-out text-left hover:scale-[1.02] ' + + 'group w-full flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition-all duration-200 ease-out text-left hover:scale-[1.01] ' + (active ? 'border-accent bg-offbase text-accent' : 'border-transparent text-foreground hover:border-accent hover:bg-offbase hover:text-accent') + diff --git a/src/components/doclist/window/FinderToolbar.tsx b/src/components/doclist/window/FinderToolbar.tsx index d929043..e40761f 100644 --- a/src/components/doclist/window/FinderToolbar.tsx +++ b/src/components/doclist/window/FinderToolbar.tsx @@ -58,10 +58,9 @@ const ICON_SIZES: Array<{ value: IconSize; label: string }> = [ // Match SettingsModal / UserMenu trigger sizing exactly so all bar buttons share one rhythm. const TOOLBAR_BTN = - 'inline-flex items-center py-1 px-2 rounded-md border bg-base text-xs transition-all duration-200 ease-out hover:scale-[1.02]'; + 'inline-flex items-center py-1 px-2 rounded-md border bg-base text-xs transition-all duration-200 ease-out hover:scale-[1.01]'; const TOOLBAR_BTN_INACTIVE = 'border-offbase text-foreground hover:text-accent hover:border-accent hover:bg-offbase'; -const TOOLBAR_BTN_ACTIVE = 'border-accent text-accent bg-offbase'; // Pill-grouped segmented control. Outer pill carries the border; inner segments are // borderless and rely on bg/text color to show active/hover. Sized so the whole pill diff --git a/src/components/documents/DocumentUploader.tsx b/src/components/documents/DocumentUploader.tsx index efe1afb..2ff1766 100644 --- a/src/components/documents/DocumentUploader.tsx +++ b/src/components/documents/DocumentUploader.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, type ReactNode } from 'react'; import { useDropzone } from 'react-dropzone'; import { UploadIcon } from '@/components/icons/Icons'; import { useDocuments } from '@/contexts/DocumentContext'; @@ -10,10 +10,11 @@ import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; interface DocumentUploaderProps { className?: string; - variant?: 'default' | 'compact'; + variant?: 'default' | 'compact' | 'overlay'; + children?: ReactNode; } -export function DocumentUploader({ className = '', variant = 'default' }: DocumentUploaderProps) { +export function DocumentUploader({ className = '', variant = 'default', children }: DocumentUploaderProps) { const enableDocx = useFeatureFlag('enableDocxConversion'); const { addPDFDocument: addPDF, @@ -71,11 +72,13 @@ export function DocumentUploader({ className = '', variant = 'default' }: Docume } : {}) }, multiple: true, - disabled: isUploading || isConverting + disabled: isUploading || isConverting, + noClick: variant === 'overlay', + noKeyboard: variant === 'overlay' }); const containerBase = `group w-full rounded transform transition-all duration-200 ease-in-out ${ - variant === 'compact' ? 'hover:scale-[1.02]' : '' + variant === 'compact' ? 'hover:scale-[1.01]' : '' } ${ isUploading || isConverting ? 'cursor-not-allowed opacity-50' : 'cursor-pointer' } ${className}`; @@ -95,6 +98,32 @@ export function DocumentUploader({ className = '', variant = 'default' }: Docume const paddingClass = variant === 'compact' ? 'py-1 px-2 rounded-md' : 'py-5 px-3 rounded-lg'; + if (variant === 'overlay') { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { onClick, onKeyDown, ...rootProps } = getRootProps(); + return ( +
+ + {children} + {isDragActive && ( +
+
+ +

+ Drop files here to upload +

+

+ {enableDocx + ? 'Accepts PDF, EPUB, TXT, MD, or DOCX' + : 'Accepts PDF, EPUB, TXT, or MD'} +

+
+
+ )} +
+ ); + } + return (