mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
refactor(login): use auth file checkbox style for login options
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user