feat(doclist): improve sidebar responsiveness and toolbar interaction in document list
Enhance mobile and desktop sidebar behavior by introducing a separate state for mobile sidebar visibility and ensuring it does not persist from desktop state. Update FinderToolbar to visually indicate sidebar open state and make the toolbar sticky for better accessibility. Refine FinderSidebar width handling for improved responsiveness. Remove redundant border in mobile sidebar dialog for a cleaner appearance. These changes provide a more consistent and intuitive sidebar experience across viewports.
This commit is contained in:
parent
0470f044ef
commit
53993aa3c6
4 changed files with 140 additions and 119 deletions
|
|
@ -124,6 +124,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_STATE.sidebarWidth);
|
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_STATE.sidebarWidth);
|
||||||
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>('all');
|
const [sidebarFilter, setSidebarFilter] = useState<SidebarFilter>('all');
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
|
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
|
|
@ -206,6 +207,12 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
isInitialized,
|
isInitialized,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Mobile drawer should never auto-open from persisted desktop state.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isNarrow) return;
|
||||||
|
setMobileSidebarOpen(false);
|
||||||
|
}, [isNarrow]);
|
||||||
|
|
||||||
// Build the union document list.
|
// Build the union document list.
|
||||||
const allDocuments: DocumentListDocument[] = useMemo(
|
const allDocuments: DocumentListDocument[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
|
@ -413,6 +420,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
|
|
||||||
const fallbackViewMode: ViewMode =
|
const fallbackViewMode: ViewMode =
|
||||||
viewMode === 'columns' && isNarrow ? 'list' : viewMode;
|
viewMode === 'columns' && isNarrow ? 'list' : viewMode;
|
||||||
|
const effectiveSidebarOpen = isNarrow ? mobileSidebarOpen : sidebarOpen;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinderWindow
|
<FinderWindow
|
||||||
|
|
@ -434,7 +442,12 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
setNewFolderName('');
|
setNewFolderName('');
|
||||||
setManualFolderPrompt(true);
|
setManualFolderPrompt(true);
|
||||||
}}
|
}}
|
||||||
onToggleSidebar={() => setSidebarOpen((p) => !p)}
|
onToggleSidebar={() =>
|
||||||
|
isNarrow
|
||||||
|
? setMobileSidebarOpen((p) => !p)
|
||||||
|
: setSidebarOpen((p) => !p)
|
||||||
|
}
|
||||||
|
isSidebarOpen={effectiveSidebarOpen}
|
||||||
isNarrow={isNarrow}
|
isNarrow={isNarrow}
|
||||||
leftSlot={brand}
|
leftSlot={brand}
|
||||||
rightSlot={appActions}
|
rightSlot={appActions}
|
||||||
|
|
@ -460,8 +473,11 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
summary={summary}
|
summary={summary}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
sidebarOpen={sidebarOpen}
|
sidebarOpen={effectiveSidebarOpen}
|
||||||
onSidebarOpenChange={setSidebarOpen}
|
onSidebarOpenChange={(open) => {
|
||||||
|
if (isNarrow) setMobileSidebarOpen(open);
|
||||||
|
else setSidebarOpen(open);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{!isLoading && showHint && allDocuments.length > 1 && (
|
{!isLoading && showHint && allDocuments.length > 1 && (
|
||||||
<div className="px-3 pt-3 shrink-0 bg-background">
|
<div className="px-3 pt-3 shrink-0 bg-background">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, type ReactNode } from 'react';
|
import { useRef, type CSSProperties, type ReactNode } from 'react';
|
||||||
import { useDrop } from 'react-dnd';
|
import { useDrop } from 'react-dnd';
|
||||||
import type { Folder, SidebarFilter } from '@/types/documents';
|
import type { Folder, SidebarFilter } from '@/types/documents';
|
||||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||||
|
|
@ -158,8 +158,8 @@ export function FinderSidebar({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
style={{ width }}
|
style={{ '--sidebar-width': `${width}px` } as CSSProperties}
|
||||||
className="relative h-full bg-base border-r border-offbase shrink-0 overflow-y-auto"
|
className="relative h-full w-full md:[width:var(--sidebar-width)] bg-base border-r border-offbase shrink-0 overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div className="p-2 flex flex-col gap-0.5">
|
<div className="p-2 flex flex-col gap-0.5">
|
||||||
{topSlot && (
|
{topSlot && (
|
||||||
|
|
@ -234,4 +234,3 @@ export function FinderSidebar({
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ interface FinderToolbarProps {
|
||||||
onQueryChange: (q: string) => void;
|
onQueryChange: (q: string) => void;
|
||||||
onNewFolder: () => void;
|
onNewFolder: () => void;
|
||||||
onToggleSidebar: () => void;
|
onToggleSidebar: () => void;
|
||||||
|
isSidebarOpen: boolean;
|
||||||
/** True when the columns view should be disabled (mobile viewport). */
|
/** True when the columns view should be disabled (mobile viewport). */
|
||||||
isNarrow: boolean;
|
isNarrow: boolean;
|
||||||
/** App-level content rendered at the far left (brand/logo). */
|
/** App-level content rendered at the far left (brand/logo). */
|
||||||
|
|
@ -61,6 +62,7 @@ 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.01]';
|
'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 =
|
const TOOLBAR_BTN_INACTIVE =
|
||||||
'border-offbase text-foreground hover:text-accent hover:border-accent hover:bg-offbase';
|
'border-offbase text-foreground hover:text-accent hover:border-accent hover:bg-offbase';
|
||||||
|
const TOOLBAR_BTN_ACTIVE = 'border-accent bg-offbase text-accent';
|
||||||
|
|
||||||
// Pill-grouped segmented control. Outer pill carries the border; inner segments are
|
// 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
|
// borderless and rely on bg/text color to show active/hover. Sized so the whole pill
|
||||||
|
|
@ -84,6 +86,7 @@ export function FinderToolbar({
|
||||||
onQueryChange,
|
onQueryChange,
|
||||||
onNewFolder,
|
onNewFolder,
|
||||||
onToggleSidebar,
|
onToggleSidebar,
|
||||||
|
isSidebarOpen,
|
||||||
isNarrow,
|
isNarrow,
|
||||||
leftSlot,
|
leftSlot,
|
||||||
rightSlot,
|
rightSlot,
|
||||||
|
|
@ -92,7 +95,8 @@ export function FinderToolbar({
|
||||||
const directionLabel = sortDirection === 'asc' ? currentSort.asc : currentSort.desc;
|
const directionLabel = sortDirection === 'asc' ? currentSort.asc : currentSort.desc;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-10 px-2 sm:px-3 py-1 flex items-center gap-1.5 sm:gap-2 bg-base border-b border-offbase">
|
<div className="sticky top-0 z-40 w-full border-b border-offbase bg-base">
|
||||||
|
<div className="px-2 sm:px-3 py-1 min-h-10 flex items-center gap-1.5 sm:gap-2">
|
||||||
{leftSlot && (
|
{leftSlot && (
|
||||||
<div className="shrink-0 flex items-center gap-2 pr-1 sm:pr-2 sm:border-r sm:border-offbase">
|
<div className="shrink-0 flex items-center gap-2 pr-1 sm:pr-2 sm:border-r sm:border-offbase">
|
||||||
{leftSlot}
|
{leftSlot}
|
||||||
|
|
@ -102,7 +106,8 @@ export function FinderToolbar({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onToggleSidebar}
|
onClick={onToggleSidebar}
|
||||||
className={`${TOOLBAR_BTN} ${TOOLBAR_BTN_INACTIVE} shrink-0`}
|
className={`${TOOLBAR_BTN} ${isSidebarOpen ? TOOLBAR_BTN_ACTIVE : TOOLBAR_BTN_INACTIVE} shrink-0`}
|
||||||
|
aria-pressed={isSidebarOpen}
|
||||||
aria-label="Toggle sidebar"
|
aria-label="Toggle sidebar"
|
||||||
title="Toggle sidebar"
|
title="Toggle sidebar"
|
||||||
>
|
>
|
||||||
|
|
@ -225,5 +230,6 @@ export function FinderToolbar({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export function FinderWindow({
|
||||||
leaveTo="-translate-x-full"
|
leaveTo="-translate-x-full"
|
||||||
>
|
>
|
||||||
<DialogPanel
|
<DialogPanel
|
||||||
className="w-[80vw] max-w-[280px] h-full bg-base border-r border-offbase shadow-xl"
|
className="w-[80vw] max-w-[280px] h-full bg-base shadow-xl"
|
||||||
onClick={() => onSidebarOpenChange(false)}
|
onClick={() => onSidebarOpenChange(false)}
|
||||||
>
|
>
|
||||||
{sidebar}
|
{sidebar}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue