feat: enhance mobile layout with FAB visibility and responsive adjustments

This commit is contained in:
shuaiplus
2026-04-26 19:59:50 +08:00
parent 0cffbcd1f8
commit 2f7e66ee69
4 changed files with 34 additions and 24 deletions
+2
View File
@@ -908,6 +908,8 @@ function folderName(id: string | null | undefined): string {
selectedCipherId={selectedCipherId} selectedCipherId={selectedCipherId}
selectedMap={selectedMap} selectedMap={selectedMap}
sidebarFilter={sidebarFilter} sidebarFilter={sidebarFilter}
isMobileLayout={isMobileLayout}
mobileFabVisible={!isMobileLayout || mobilePanel === 'list'}
createMenuOpen={createMenuOpen} createMenuOpen={createMenuOpen}
createMenuRef={createMenuRef} createMenuRef={createMenuRef}
sortMenuRef={sortMenuRef} sortMenuRef={sortMenuRef}
+30 -21
View File
@@ -1,4 +1,5 @@
import type { RefObject } from 'preact'; import type { RefObject } from 'preact';
import { createPortal } from 'preact/compat';
import { Archive, ArrowUpDown, Check, CheckCheck, FolderInput, Plus, RefreshCw, RotateCcw, Trash2, X } from 'lucide-preact'; import { Archive, ArrowUpDown, Check, CheckCheck, FolderInput, Plus, RefreshCw, RotateCcw, Trash2, X } from 'lucide-preact';
import type { Cipher } from '@/lib/types'; import type { Cipher } from '@/lib/types';
import { t } from '@/lib/i18n'; import { t } from '@/lib/i18n';
@@ -32,6 +33,8 @@ interface VaultListPanelProps {
selectedCipherId: string; selectedCipherId: string;
selectedMap: Record<string, boolean>; selectedMap: Record<string, boolean>;
sidebarFilter: SidebarFilter; sidebarFilter: SidebarFilter;
isMobileLayout: boolean;
mobileFabVisible: boolean;
createMenuOpen: boolean; createMenuOpen: boolean;
createMenuRef: RefObject<HTMLDivElement>; createMenuRef: RefObject<HTMLDivElement>;
sortMenuRef: RefObject<HTMLDivElement>; sortMenuRef: RefObject<HTMLDivElement>;
@@ -60,6 +63,30 @@ interface VaultListPanelProps {
} }
export default function VaultListPanel(props: VaultListPanelProps) { export default function VaultListPanel(props: VaultListPanelProps) {
const createMenu = (
<div className="create-menu-wrap mobile-fab-wrap" ref={props.createMenuRef}>
<button
type="button"
className="btn btn-primary small mobile-fab-trigger"
aria-label={t('txt_add')}
title={t('txt_add')}
onClick={props.onToggleCreateMenu}
>
<Plus size={14} className="btn-icon" />
</button>
{props.createMenuOpen && (
<div className="create-menu">
{CREATE_TYPE_OPTIONS.map((option) => (
<button key={option.type} type="button" className="create-menu-item" onClick={() => props.onStartCreate(option.type)}>
<CreateTypeIcon type={option.type} />
<span>{option.label}</span>
</button>
))}
</div>
)}
</div>
);
return ( return (
<section className="list-col"> <section className="list-col">
<div className="list-head"> <div className="list-head">
@@ -159,27 +186,9 @@ export default function VaultListPanel(props: VaultListPanelProps) {
<button type="button" className="btn btn-secondary small" disabled={!props.filteredCiphers.length} onClick={props.onSelectAll}> <button type="button" className="btn btn-secondary small" disabled={!props.filteredCiphers.length} onClick={props.onSelectAll}>
<CheckCheck size={14} className="btn-icon" /> {t('txt_select_all')} <CheckCheck size={14} className="btn-icon" /> {t('txt_select_all')}
</button> </button>
<div className="create-menu-wrap mobile-fab-wrap" ref={props.createMenuRef}> {props.isMobileLayout && typeof document !== 'undefined'
<button ? props.mobileFabVisible ? createPortal(createMenu, document.body) : null
type="button" : createMenu}
className="btn btn-primary small mobile-fab-trigger"
aria-label={t('txt_add')}
title={t('txt_add')}
onClick={props.onToggleCreateMenu}
>
<Plus size={14} className="btn-icon" />
</button>
{props.createMenuOpen && (
<div className="create-menu">
{CREATE_TYPE_OPTIONS.map((option) => (
<button key={option.type} type="button" className="create-menu-item" onClick={() => props.onStartCreate(option.type)}>
<CreateTypeIcon type={option.type} />
<span>{option.label}</span>
</button>
))}
</div>
)}
</div>
</div> </div>
<div className="list-panel" ref={props.listPanelRef} onScroll={(event) => props.onScroll((event.currentTarget as HTMLDivElement).scrollTop)}> <div className="list-panel" ref={props.listPanelRef} onScroll={(event) => props.onScroll((event.currentTarget as HTMLDivElement).scrollTop)}>
+2 -2
View File
@@ -327,11 +327,11 @@
.mobile-fab-wrap { .mobile-fab-wrap {
@apply fixed right-3.5 z-[45]; @apply fixed right-3.5 z-[45];
bottom: calc(14px + var(--mobile-tabbar-height) + env(safe-area-inset-bottom)); bottom: calc(14px + var(--mobile-tabbar-height, 70px) + env(safe-area-inset-bottom));
} }
.mobile-fab-trigger { .mobile-fab-trigger {
@apply h-14 w-9 gap-0 rounded-full p-0 text-[0]; @apply h-9 w-9 gap-0 rounded-full p-0 text-[0];
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.28); box-shadow: 0 14px 30px rgba(37, 99, 235, 0.28);
transition: transform 180ms var(--ease-spring), box-shadow var(--dur-fast) var(--ease-out-soft); transition: transform 180ms var(--ease-spring), box-shadow var(--dur-fast) var(--ease-out-soft);
} }
-1
View File
@@ -184,7 +184,6 @@
.route-stage { .route-stage {
@apply h-full min-h-0 overflow-auto; @apply h-full min-h-0 overflow-auto;
animation: route-stage-in 220ms var(--ease-out-expo) both;
} }
.mobile-sidebar-mask { .mobile-sidebar-mask {