Merge pull request #93 from richardr1126/fix/content-views-mobile
fix(doclist): improve gallery + list view layout on small screens
This commit is contained in:
commit
f8c2f27e64
4 changed files with 24 additions and 17 deletions
|
|
@ -31,7 +31,7 @@ function IconsSkeleton({ iconSize }: { iconSize: IconSize }) {
|
||||||
function ListSkeleton() {
|
function ListSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full min-h-0 overflow-y-auto">
|
<div className="h-full min-h-0 overflow-y-auto">
|
||||||
<div className="sticky top-0 z-10 bg-base border-b border-offbase grid grid-cols-[minmax(0,1fr)_72px_88px_120px_28px] sm:grid-cols-[minmax(0,1fr)_88px_96px_140px_32px]">
|
<div className="sticky top-0 z-10 bg-base border-b border-offbase grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px]">
|
||||||
<div className="h-8 flex items-center px-2">
|
<div className="h-8 flex items-center px-2">
|
||||||
<div className="h-2.5 w-12 rounded bg-offbase" />
|
<div className="h-2.5 w-12 rounded bg-offbase" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -50,7 +50,7 @@ function ListSkeleton() {
|
||||||
{Array.from({ length: 10 }).map((_, index) => (
|
{Array.from({ length: 10 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="grid grid-cols-[minmax(0,1fr)_72px_88px_120px_28px] sm:grid-cols-[minmax(0,1fr)_88px_96px_140px_32px] items-center border-b border-offbase h-[35px]"
|
className="grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px] items-center border-b border-offbase h-[35px]"
|
||||||
>
|
>
|
||||||
<div className="px-2 flex items-center gap-2">
|
<div className="px-2 flex items-center gap-2">
|
||||||
<div className="h-3.5 w-3.5 rounded bg-offbase" />
|
<div className="h-3.5 w-3.5 rounded bg-offbase" />
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type { DocumentListDocument } from '@/types/documents';
|
||||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||||
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
||||||
import { formatDocumentSize } from '@/components/doclist/formatSize';
|
import { formatDocumentSize } from '@/components/doclist/formatSize';
|
||||||
|
import { buttonClass } from '@/components/formPrimitives';
|
||||||
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
||||||
import { DND_DOCUMENT, documentIdentityKey, type DocumentDragItem } from '../dnd/dndTypes';
|
import { DND_DOCUMENT, documentIdentityKey, type DocumentDragItem } from '../dnd/dndTypes';
|
||||||
|
|
||||||
|
|
@ -166,18 +167,25 @@ export function GalleryView({
|
||||||
setActiveIdx((i) => Math.min(documents.length - 1, i + 1));
|
setActiveIdx((i) => Math.min(documents.length - 1, i + 1));
|
||||||
} else if (e.key === 'ArrowLeft') {
|
} else if (e.key === 'ArrowLeft') {
|
||||||
setActiveIdx((i) => Math.max(0, i - 1));
|
setActiveIdx((i) => Math.max(0, i - 1));
|
||||||
|
} else if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||||
|
const doc = documents[activeIdx];
|
||||||
|
if (doc) {
|
||||||
|
e.preventDefault();
|
||||||
|
onDeleteDoc(doc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('keydown', onKey);
|
window.addEventListener('keydown', onKey);
|
||||||
return () => window.removeEventListener('keydown', onKey);
|
return () => window.removeEventListener('keydown', onKey);
|
||||||
}, [documents.length]);
|
}, [documents, activeIdx, onDeleteDoc]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 min-h-0 flex flex-col">
|
<div className="flex-1 min-h-0 flex flex-col">
|
||||||
<div className="flex-1 min-h-0 flex items-center justify-center p-6 bg-background">
|
<div className="flex-1 min-h-0 overflow-y-auto bg-background">
|
||||||
|
<div className="min-h-full flex items-center justify-center p-3 sm:p-6">
|
||||||
{activeDoc ? (
|
{activeDoc ? (
|
||||||
<div className="w-full max-w-[920px] flex flex-col md:flex-row items-center md:items-start justify-center gap-4 md:gap-6">
|
<div className="w-full max-w-[920px] flex flex-col md:flex-row items-center md:items-start justify-center gap-4 md:gap-6">
|
||||||
<div className="flex flex-col items-center gap-3 w-[260px] sm:w-[320px] shrink-0">
|
<div className="flex flex-col items-center gap-3 w-[180px] sm:w-[260px] md:w-[320px] shrink-0">
|
||||||
<div className="w-full aspect-[3/4] rounded-lg overflow-hidden border border-offbase shadow-lg">
|
<div className="w-full aspect-[3/4] rounded-lg overflow-hidden border border-offbase shadow-lg">
|
||||||
<DocumentPreview doc={activeDoc} />
|
<DocumentPreview doc={activeDoc} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -193,20 +201,20 @@ export function GalleryView({
|
||||||
<Link
|
<Link
|
||||||
href={openHref || '/app'}
|
href={openHref || '/app'}
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
className="h-8 px-4 inline-flex items-center justify-center rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
|
className={buttonClass({ variant: 'primary', size: 'sm' })}
|
||||||
>
|
>
|
||||||
Open
|
Open
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteDoc(activeDoc)}
|
onClick={() => onDeleteDoc(activeDoc)}
|
||||||
className="h-8 px-3 rounded-md border border-offbase bg-base text-[12px] text-muted hover:text-accent hover:border-accent hover:bg-offbase hover:scale-[1.01] transition-all duration-200 ease-out"
|
className={buttonClass({ variant: 'secondary', size: 'sm' })}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dl className="w-full max-w-[360px] md:max-w-[340px] grid grid-cols-2 gap-x-2 gap-y-1 rounded-md border border-offbase bg-base px-3 py-2 text-[11px] md:self-center">
|
<dl className="w-full max-w-[280px] sm:max-w-[360px] md:max-w-[340px] grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 rounded-md border border-offbase bg-base px-3 py-2 text-[11px] md:self-center">
|
||||||
<dt className="text-muted">Type</dt>
|
<dt className="text-muted">Type</dt>
|
||||||
<dd className="text-foreground text-right uppercase tracking-wide">{activeDoc.type}</dd>
|
<dd className="text-foreground text-right uppercase tracking-wide">{activeDoc.type}</dd>
|
||||||
<dt className="text-muted">Size</dt>
|
<dt className="text-muted">Size</dt>
|
||||||
|
|
@ -234,6 +242,7 @@ export function GalleryView({
|
||||||
) : (
|
) : (
|
||||||
<p className="text-[12px] text-muted">No documents to show</p>
|
<p className="text-[12px] text-muted">No documents to show</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="shrink-0 border-t border-offbase bg-gradient-to-b from-base to-offbase/30">
|
<div className="shrink-0 border-t border-offbase bg-gradient-to-b from-base to-offbase/30">
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ function formatDate(ms: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
||||||
if (doc.type === 'pdf') return <PDFIcon className="w-4 h-4 text-red-500" />;
|
if (doc.type === 'pdf') return <PDFIcon className="w-4 h-4 shrink-0 text-red-500" />;
|
||||||
if (doc.type === 'epub') return <EPUBIcon className="w-4 h-4 text-blue-500" />;
|
if (doc.type === 'epub') return <EPUBIcon className="w-4 h-4 shrink-0 text-blue-500" />;
|
||||||
return <FileIcon className="w-4 h-4 text-muted" />;
|
return <FileIcon className="w-4 h-4 shrink-0 text-muted" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderCell({
|
function HeaderCell({
|
||||||
|
|
@ -131,7 +131,7 @@ function DocRow({
|
||||||
data-doc-tile
|
data-doc-tile
|
||||||
aria-selected={isSelected}
|
aria-selected={isSelected}
|
||||||
className={
|
className={
|
||||||
'grid grid-cols-[minmax(0,1fr)_72px_88px_120px_28px] sm:grid-cols-[minmax(0,1fr)_88px_96px_140px_32px] items-center text-[12px] border-b border-offbase transition-colors duration-200 ease-out ' +
|
'grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px] items-center text-[12px] border-b border-offbase transition-colors duration-200 ease-out ' +
|
||||||
(isSelected
|
(isSelected
|
||||||
? 'bg-offbase text-accent'
|
? 'bg-offbase text-accent'
|
||||||
: 'text-foreground hover:bg-offbase') +
|
: 'text-foreground hover:bg-offbase') +
|
||||||
|
|
@ -198,7 +198,7 @@ export function ListView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={handleBackgroundClick} className="flex-1 min-h-0 overflow-y-auto">
|
<div onClick={handleBackgroundClick} className="flex-1 min-h-0 overflow-y-auto">
|
||||||
<div className="sticky top-0 z-10 bg-base border-b border-offbase grid grid-cols-[minmax(0,1fr)_72px_88px_120px_28px] sm:grid-cols-[minmax(0,1fr)_88px_96px_140px_32px]">
|
<div className="sticky top-0 z-10 bg-base border-b border-offbase grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px]">
|
||||||
<HeaderCell label="Name" field="name" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
<HeaderCell label="Name" field="name" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
||||||
<HeaderCell label="Kind" field="type" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
<HeaderCell label="Kind" field="type" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
||||||
<HeaderCell label="Size" field="size" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} align="right" />
|
<HeaderCell label="Size" field="size" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} align="right" />
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,8 @@ export const ListViewIcon = (props: IconProps) => (
|
||||||
|
|
||||||
export const GalleryViewIcon = (props: IconProps) => (
|
export const GalleryViewIcon = (props: IconProps) => (
|
||||||
<svg {...baseSvg(props)}>
|
<svg {...baseSvg(props)}>
|
||||||
<rect x="3" y="4" width="18" height="11" rx="1.5" />
|
<rect x="4" y="4" width="16" height="13" rx="1.5" />
|
||||||
<rect x="4" y="17" width="4" height="3" rx="0.6" />
|
<path d="M5 20h14" />
|
||||||
<rect x="10" y="17" width="4" height="3" rx="0.6" />
|
|
||||||
<rect x="16" y="17" width="4" height="3" rx="0.6" />
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue