mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 02:30:51 +08:00
fix(auth-files): polish selection UI and animate batch action bar
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user