fix(auth-files): polish selection UI and animate batch action bar

This commit is contained in:
Supra4E8C
2026-02-16 23:13:26 +08:00
parent b7794a91b4
commit 47c3874244
4 changed files with 73 additions and 19 deletions

View File

@@ -5,6 +5,7 @@
:global(.modal-body) {
padding: $spacing-md $spacing-lg;
max-height: none;
overflow: hidden;
}
}
@@ -39,17 +40,17 @@
:global(.cm-mergeView) {
height: 100%;
overflow: auto !important;
-webkit-overflow-scrolling: touch;
background: var(--bg-primary);
}
:global(.cm-mergeViewEditors),
:global(.cm-mergeViewEditor),
:global(.cm-editor) {
height: 100%;
:global(.cm-mergeViewEditors) {
min-height: 100%;
}
:global(.cm-scroller) {
overflow: auto;
:global(.cm-mergeViewEditor) {
height: 100%;
}
:global(.cm-gutters) {

View File

@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
import { IconBot, IconCode, IconDownload, IconInfo, IconTrash2 } from '@/components/ui/icons';
import { IconBot, IconCheck, IconCode, IconDownload, IconInfo, IconTrash2 } from '@/components/ui/icons';
import { ProviderStatusBar } from '@/components/providers/ProviderStatusBar';
import type { AuthFileItem } from '@/types';
import { resolveAuthProvider } from '@/utils/quota';
@@ -102,13 +102,16 @@ export function AuthFileCard(props: AuthFileCardProps) {
<div className={styles.fileCardMain}>
<div className={styles.cardHeader}>
{!isRuntimeOnly && (
<input
type="checkbox"
className={styles.selectionCheckbox}
checked={selected}
onChange={() => onToggleSelect(file.name)}
aria-label={t('auth_files.batch_select_all')}
/>
<button
type="button"
className={`${styles.selectionToggle} ${selected ? styles.selectionToggleActive : ''}`}
onClick={() => onToggleSelect(file.name)}
aria-label={selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all')}
aria-pressed={selected}
title={selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all')}
>
{selected && <IconCheck size={12} />}
</button>
)}
<span
className={styles.typeBadge}

View File

@@ -500,7 +500,7 @@
.fileCardSelected {
border-color: var(--primary-color);
box-shadow: 0 0 0 1px var(--primary-color);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary-color) 70%, transparent);
&:hover {
border-color: var(--primary-color);
@@ -538,13 +538,43 @@
min-height: 28px;
}
.selectionCheckbox {
width: 16px;
height: 16px;
.selectionToggle {
width: 22px;
height: 22px;
margin: 0;
flex-shrink: 0;
accent-color: var(--primary-color);
border-radius: 7px;
border: 1px solid var(--border-color);
background: color-mix(in srgb, var(--bg-secondary) 92%, transparent);
color: var(--primary-contrast, #fff);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition:
border-color $transition-fast,
background-color $transition-fast,
box-shadow $transition-fast,
transform $transition-fast;
&:hover {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 16%, transparent);
}
&:active {
transform: scale(0.95);
}
}
.selectionToggleActive {
border-color: var(--primary-color);
background: var(--primary-color);
}
.selectionToggleActive svg {
display: block;
stroke-width: 2.4;
}
.typeBadge {
@@ -904,6 +934,7 @@
transform: translateX(-50%);
z-index: 50;
width: min(960px, calc(100vw - 24px));
will-change: transform, opacity;
}
.batchActionBar {

View File

@@ -2,6 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, typ
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import gsap from 'gsap';
import { useInterval } from '@/hooks/useInterval';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { usePageTransitionLayer } from '@/components/common/PageTransitionLayer';
@@ -57,6 +58,7 @@ export function AuthFilesPage() {
const [selectedFile, setSelectedFile] = useState<AuthFileItem | null>(null);
const [viewMode, setViewMode] = useState<'diagram' | 'list'>('list');
const floatingBatchActionsRef = useRef<HTMLDivElement>(null);
const previousSelectionCountRef = useRef(0);
const { keyStats, usageDetails, loadKeyStats } = useAuthFilesStats();
const {
@@ -331,6 +333,23 @@ export function AuthFilesPage() {
};
}, [selectionCount]);
useLayoutEffect(() => {
const currentCount = selectionCount;
const previousCount = previousSelectionCountRef.current;
const actionsEl = floatingBatchActionsRef.current;
if (currentCount > 0 && previousCount === 0 && actionsEl) {
gsap.killTweensOf(actionsEl);
gsap.fromTo(
actionsEl,
{ y: 56, autoAlpha: 0 },
{ y: 0, autoAlpha: 1, duration: 0.28, ease: 'power3.out' }
);
}
previousSelectionCountRef.current = currentCount;
}, [selectionCount]);
const renderFilterTags = () => (
<div className={styles.filterTags}>
{existingTypes.map((type) => {