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.
This commit is contained in:
parent
7a88dabc53
commit
eb8820d888
11 changed files with 187 additions and 105 deletions
|
|
@ -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}
|
||||
>
|
||||
<SettingsIcon className="w-4 h-4 transition-transform duration-200 ease-out hover:scale-[1.08] hover:rotate-45" />
|
||||
<SettingsIcon className="w-4 h-4 transition-transform duration-200 ease-out hover:scale-[1.01] hover:rotate-45" />
|
||||
</Button>
|
||||
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
|
|
@ -1447,7 +1447,7 @@ function SettingsChangelogPanel({
|
|||
<div className="flex items-center gap-3 px-4 py-3 border-b border-offbase bg-background">
|
||||
<Button
|
||||
onClick={onClose}
|
||||
className="inline-flex items-center justify-center rounded-md text-muted hover:text-accent hover:bg-base transition-all duration-200 ease-in-out transform hover:scale-[1.08]"
|
||||
className="inline-flex items-center justify-center rounded-md text-muted hover:text-accent hover:bg-base transition-all duration-200 ease-in-out transform hover:scale-[1.01]"
|
||||
aria-label="Back to settings"
|
||||
title="Back"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ export function UserMenu({ className = '' }: { className?: string }) {
|
|||
return (
|
||||
<div className={`flex gap-2 ${className}`}>
|
||||
<Link href="/signin">
|
||||
<Button className="inline-flex items-center rounded-md bg-base border border-offbase px-2 py-1 text-xs font-medium text-foreground hover:bg-offbase focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 transform transition-all duration-200 ease-in-out hover:scale-[1.09] hover:text-accent">
|
||||
<Button className="inline-flex items-center rounded-md bg-base border border-offbase px-2 py-1 text-xs font-medium text-foreground hover:bg-offbase focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 transform transition-all duration-200 ease-in-out hover:scale-[1.01] hover:text-accent">
|
||||
Connect
|
||||
</Button>
|
||||
</Link>
|
||||
{enableUserSignups && (
|
||||
<Link href="/signup">
|
||||
<Button className="inline-flex items-center rounded-md bg-accent px-2 py-1 text-xs font-medium text-background hover:bg-secondary-accent focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 transform transition-all duration-200 ease-in-out hover:scale-[1.09]">
|
||||
<Button className="inline-flex items-center rounded-md bg-accent px-2 py-1 text-xs font-medium text-background hover:bg-secondary-accent focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 transform transition-all duration-200 ease-in-out hover:scale-[1.01]">
|
||||
Create account
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -49,7 +49,7 @@ export function UserMenu({ className = '' }: { className?: string }) {
|
|||
|
||||
<Button
|
||||
onClick={handleDisconnectAccount}
|
||||
className="inline-flex items-center text-foreground text-xs hover:text-accent transform transition-all duration-200 ease-in-out hover:scale-[1.09]"
|
||||
className="inline-flex items-center text-foreground text-xs hover:text-accent transform transition-all duration-200 ease-in-out hover:scale-[1.01]"
|
||||
title="Disconnect account"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
|
|
|
|||
|
|
@ -489,63 +489,65 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{fallbackViewMode === 'icons' && (
|
||||
<IconsView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
iconSize={iconSize}
|
||||
collapsedFolders={collapsedFolders}
|
||||
onToggleCollapse={toggleFolderCollapse}
|
||||
onDeleteFolder={handleDeleteFolder}
|
||||
onDeleteDoc={(doc) =>
|
||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
||||
}
|
||||
onDropOnFolder={handleDropOnFolder}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
{fallbackViewMode === 'list' && (
|
||||
<ListView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
sortBy={sortBy}
|
||||
sortDirection={sortDirection}
|
||||
onSortChange={(b, d) => {
|
||||
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' && (
|
||||
<ColumnsView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
onDeleteDoc={(doc) =>
|
||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
||||
}
|
||||
onDropOnFolder={handleDropOnFolder}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
{fallbackViewMode === 'gallery' && (
|
||||
<GalleryView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
onDeleteDoc={(doc) =>
|
||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
||||
}
|
||||
onDropOnFolder={handleDropOnFolder}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
<DocumentUploader variant="overlay" className="flex-1 min-h-0 flex flex-col">
|
||||
{fallbackViewMode === 'icons' && (
|
||||
<IconsView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
iconSize={iconSize}
|
||||
collapsedFolders={collapsedFolders}
|
||||
onToggleCollapse={toggleFolderCollapse}
|
||||
onDeleteFolder={handleDeleteFolder}
|
||||
onDeleteDoc={(doc) =>
|
||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
||||
}
|
||||
onDropOnFolder={handleDropOnFolder}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
{fallbackViewMode === 'list' && (
|
||||
<ListView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
sortBy={sortBy}
|
||||
sortDirection={sortDirection}
|
||||
onSortChange={(b, d) => {
|
||||
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' && (
|
||||
<ColumnsView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
onDeleteDoc={(doc) =>
|
||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
||||
}
|
||||
onDropOnFolder={handleDropOnFolder}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
{fallbackViewMode === 'gallery' && (
|
||||
<GalleryView
|
||||
folders={visibleFolders}
|
||||
unfolderedDocs={unfolderedDocs}
|
||||
onDeleteDoc={(doc) =>
|
||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
||||
}
|
||||
onDropOnFolder={handleDropOnFolder}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
</DocumentUploader>
|
||||
|
||||
<CreateFolderDialog
|
||||
isOpen={pendingMerge !== null}
|
||||
|
|
|
|||
|
|
@ -329,8 +329,16 @@ export function DocumentPreview({ doc }: DocumentPreviewProps) {
|
|||
</>
|
||||
)}
|
||||
|
||||
<div className="absolute left-1 top-1 z-20 rounded bg-black/45 px-1.5 py-0.5 text-[10px] font-semibold tracking-wide text-white">
|
||||
{isGenerating ? '…' : typeLabel}
|
||||
<div className="absolute left-1 bottom-1 z-20 rounded bg-black/45 px-1.5 py-0.5 text-[9px] font-semibold tracking-wide text-white/90">
|
||||
{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`
|
||||
}`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -216,7 +216,12 @@ export function ColumnsView({
|
|||
{selected.doc.name}
|
||||
</h3>
|
||||
<p className="text-[11px] text-muted">
|
||||
{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`}
|
||||
</p>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import type { DocumentListDocument, IconSize } from '@/types/documents';
|
|||
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
||||
import { useAuthSession } from '@/hooks/useAuthSession';
|
||||
import { useAuthConfig } from '@/contexts/AuthRateLimitContext';
|
||||
import { buttonClass } from '@/components/formPrimitives';
|
||||
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
||||
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
||||
|
||||
|
|
@ -111,9 +110,51 @@ export function DocumentTile({
|
|||
|
||||
const sizeClasses: Record<IconSize, string> = {
|
||||
sm: 'text-[10px]',
|
||||
md: 'text-[12px]',
|
||||
lg: 'text-[13px]',
|
||||
xl: 'text-[14px]',
|
||||
md: 'text-[11px]',
|
||||
lg: 'text-[12px]',
|
||||
xl: 'text-[13px]',
|
||||
};
|
||||
|
||||
const bottomPaddingClasses: Record<IconSize, string> = {
|
||||
sm: 'px-[7px] py-[4px]',
|
||||
md: 'px-[8px] py-[5px]',
|
||||
lg: 'px-[9px] py-[5px]',
|
||||
xl: 'px-[10px] py-[6px]',
|
||||
};
|
||||
|
||||
const linkPaddingClasses: Record<IconSize, string> = {
|
||||
sm: 'px-[2px] py-[2px]',
|
||||
md: 'px-[2px] py-[2px]',
|
||||
lg: 'px-[2px] py-[2px]',
|
||||
xl: 'px-[2px] py-[2px]',
|
||||
};
|
||||
|
||||
const gapClasses: Record<IconSize, string> = {
|
||||
sm: 'gap-1',
|
||||
md: 'gap-1.5',
|
||||
lg: 'gap-2',
|
||||
xl: 'gap-2',
|
||||
};
|
||||
|
||||
const fileIconClasses: Record<IconSize, string> = {
|
||||
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<IconSize, string> = {
|
||||
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<IconSize, string> = {
|
||||
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({
|
|||
>
|
||||
<DocumentPreview doc={doc} />
|
||||
</Link>
|
||||
<div className="flex items-center w-full px-1.5 py-1.5">
|
||||
<div className={`flex items-center w-full ${bottomPaddingClasses[iconSize]}`}>
|
||||
<Link
|
||||
href={href}
|
||||
draggable={false}
|
||||
className="flex items-center gap-2 flex-1 min-w-0 rounded-md py-0.5 px-0.5"
|
||||
className={`flex items-center flex-1 min-w-0 rounded-md ${linkPaddingClasses[iconSize]} ${gapClasses[iconSize]}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<span className="flex-shrink-0">
|
||||
<span className="flex-shrink-0 flex items-center">
|
||||
{doc.type === 'pdf' ? (
|
||||
<PDFIcon className="w-4 h-4 text-red-500" />
|
||||
<PDFIcon className={`${fileIconClasses[iconSize]} text-red-500`} />
|
||||
) : doc.type === 'epub' ? (
|
||||
<EPUBIcon className="w-4 h-4 text-blue-500" />
|
||||
<EPUBIcon className={`${fileIconClasses[iconSize]} text-blue-500`} />
|
||||
) : (
|
||||
<FileIcon className="w-4 h-4 text-muted" />
|
||||
<FileIcon className={`${fileIconClasses[iconSize]} text-muted`} />
|
||||
)}
|
||||
</span>
|
||||
<span className="flex flex-col min-w-0 w-full">
|
||||
<span
|
||||
className={
|
||||
'leading-tight font-medium truncate ' +
|
||||
sizeClasses[iconSize] +
|
||||
' ' +
|
||||
(isSelected ? 'text-accent' : 'text-foreground group-hover:text-accent')
|
||||
}
|
||||
>
|
||||
{doc.name}
|
||||
</span>
|
||||
<span className="text-[9px] leading-tight text-muted truncate">
|
||||
{(doc.size / 1024 / 1024).toFixed(2)} MB
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
'leading-none font-medium truncate flex-1 min-w-0 ' +
|
||||
sizeClasses[iconSize] +
|
||||
' ' +
|
||||
(isSelected ? 'text-accent' : 'text-foreground group-hover:text-accent')
|
||||
}
|
||||
>
|
||||
{doc.name}
|
||||
</span>
|
||||
</Link>
|
||||
{showDeleteButton && (
|
||||
<Button
|
||||
onClick={() => onDelete(doc)}
|
||||
className={buttonClass({
|
||||
variant: 'ghost',
|
||||
size: 'icon',
|
||||
className: 'ml-1 h-6 w-6 text-muted hover:bg-base',
|
||||
})}
|
||||
className={`inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base focus:outline-none transition-colors duration-200 ${trashBtnClasses[iconSize]}`}
|
||||
aria-label={`Delete ${doc.name}`}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className={trashIconClasses[iconSize]} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
|
|||
|
|
@ -181,7 +181,11 @@ export function GalleryView({
|
|||
</h2>
|
||||
<p className="text-[11px] text-muted">
|
||||
{activeDoc.type.toUpperCase()} •{' '}
|
||||
{(activeDoc.size / 1024 / 1024).toFixed(2)} MB
|
||||
{activeDoc.size >= 1024 * 1024 * 1024
|
||||
? `${(activeDoc.size / 1024 / 1024 / 1024).toFixed(2)} GB`
|
||||
: activeDoc.size >= 1024 * 1024
|
||||
? `${(activeDoc.size / 1024 / 1024).toFixed(2)} MB`
|
||||
: `${(activeDoc.size / 1024).toFixed(1)} KB`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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') +
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div {...rootProps} className={`relative w-full h-full ${className}`}>
|
||||
<input {...getInputProps()} />
|
||||
{children}
|
||||
{isDragActive && (
|
||||
<div className="absolute inset-0 z-50 flex flex-col items-center justify-center bg-background/90 backdrop-blur-md pointer-events-none p-6">
|
||||
<div className="w-full h-full border-2 border-dashed border-accent rounded-xl flex flex-col items-center justify-center bg-base/60 text-center p-4">
|
||||
<UploadIcon className="w-14 h-14 text-accent mb-4 animate-bounce" />
|
||||
<p className="text-xl font-bold text-foreground mb-1.5">
|
||||
Drop files here to upload
|
||||
</p>
|
||||
<p className="text-sm text-foreground/70">
|
||||
{enableDocx
|
||||
? 'Accepts PDF, EPUB, TXT, MD, or DOCX'
|
||||
: 'Accepts PDF, EPUB, TXT, or MD'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
|
|
|
|||
Loading…
Reference in a new issue