refactor(login): use auth file checkbox style for login options

This commit is contained in:
Supra4E8C
2026-03-14 15:33:57 +08:00
parent df96297c68
commit 180b097b11
5 changed files with 149 additions and 53 deletions
@@ -0,0 +1,87 @@
@use '../../styles/variables' as *;
.root {
position: relative;
display: inline-flex;
align-items: center;
gap: $spacing-sm;
cursor: pointer;
user-select: none;
}
.disabled {
cursor: not-allowed;
opacity: 0.6;
}
.input {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}
.box {
width: 22px;
height: 22px;
flex-shrink: 0;
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;
transition:
border-color $transition-fast,
background-color $transition-fast,
box-shadow $transition-fast,
transform $transition-fast;
}
.root:hover .box {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 16%, transparent);
}
.root:active .box {
transform: scale(0.95);
}
.disabled:hover .box {
border-color: var(--border-color);
box-shadow: none;
}
.disabled:active .box {
transform: none;
}
.input:focus-visible + .box {
border-color: var(--primary-color);
box-shadow:
0 0 0 3px color-mix(in srgb, var(--primary-color) 16%, transparent),
0 0 0 1px color-mix(in srgb, var(--primary-color) 50%, transparent);
}
.boxChecked {
border-color: var(--primary-color);
background: var(--primary-color);
}
.boxChecked svg {
display: block;
stroke-width: 2.4;
}
.label {
color: var(--text-primary);
font-size: 14px;
font-weight: 500;
}
+50
View File
@@ -0,0 +1,50 @@
import type { ChangeEvent, ReactNode } from 'react';
import { IconCheck } from './icons';
import styles from './SelectionCheckbox.module.scss';
interface SelectionCheckboxProps {
checked: boolean;
onChange: (value: boolean) => void;
label?: ReactNode;
ariaLabel?: string;
title?: string;
disabled?: boolean;
className?: string;
labelClassName?: string;
}
export function SelectionCheckbox({
checked,
onChange,
label,
ariaLabel,
title,
disabled = false,
className,
labelClassName,
}: SelectionCheckboxProps) {
const rootClassName = [styles.root, disabled ? styles.disabled : '', className]
.filter(Boolean)
.join(' ');
const boxClassName = [styles.box, checked ? styles.boxChecked : ''].filter(Boolean).join(' ');
const textClassName = [styles.label, labelClassName].filter(Boolean).join(' ');
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.checked);
};
return (
<label className={rootClassName} title={title}>
<input
className={styles.input}
type="checkbox"
checked={checked}
onChange={handleChange}
aria-label={ariaLabel}
disabled={disabled}
/>
<span className={boxClassName}>{checked ? <IconCheck size={12} /> : null}</span>
{label ? <span className={textClassName}>{label}</span> : null}
</label>
);
}
@@ -1,10 +1,10 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
import { SelectionCheckbox } from '@/components/ui/SelectionCheckbox';
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
import {
IconBot,
IconCheck,
IconCode,
IconDownload,
IconInfo,
@@ -118,18 +118,14 @@ export function AuthFileCard(props: AuthFileCardProps) {
<div className={styles.fileCardMain}>
<div className={styles.cardHeader}>
{!isRuntimeOnly && (
<button
type="button"
className={`${styles.selectionToggle} ${selected ? styles.selectionToggleActive : ''}`}
onClick={() => onToggleSelect(file.name)}
<SelectionCheckbox
checked={selected}
onChange={() => 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}
-39
View File
@@ -565,45 +565,6 @@
min-height: 28px;
}
.selectionToggle {
width: 22px;
height: 22px;
margin: 0;
flex-shrink: 0;
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 {
padding: 4px 10px;
border-radius: 12px;
+7 -5
View File
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Select } from '@/components/ui/Select';
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
import { SelectionCheckbox } from '@/components/ui/SelectionCheckbox';
import { IconEye, IconEyeOff } from '@/components/ui/icons';
import { useAuthStore, useLanguageStore, useNotificationStore } from '@/stores';
import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection';
@@ -233,11 +233,12 @@ export function LoginPage() {
</div>
<div className={styles.toggleAdvanced}>
<ToggleSwitch
<SelectionCheckbox
checked={showCustomBase}
onChange={setShowCustomBase}
ariaLabel={t('login.custom_connection_label')}
label={<span className={styles.toggleLabel}>{t('login.custom_connection_label')}</span>}
label={t('login.custom_connection_label')}
labelClassName={styles.toggleLabel}
/>
</div>
@@ -281,11 +282,12 @@ export function LoginPage() {
/>
<div className={styles.toggleAdvanced}>
<ToggleSwitch
<SelectionCheckbox
checked={rememberPassword}
onChange={setRememberPassword}
ariaLabel={t('login.remember_password_label')}
label={<span className={styles.toggleLabel}>{t('login.remember_password_label')}</span>}
label={t('login.remember_password_label')}
labelClassName={styles.toggleLabel}
/>
</div>