mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: enhance mobile layout with FAB visibility and responsive adjustments
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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)}>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user