refactor(frontend): modernize login page with AntD primitives

- Theme cycle button switched from `<button.theme-cycle>` + custom CSS
  to AntD `<Button shape="circle" className="toolbar-btn">` (matches
  sub page chrome already established).
- Theme icons switched from hand-rolled inline SVG (sun, moon,
  moon+star) to AntD `<SunOutlined />`, `<MoonOutlined />`,
  `<MoonFilled />` for the three light / dark / ultra-dark states.
- Language popover content switched from `<ul.lang-list>` +
  `<button.lang-item>` to AntD `<Menu mode="vertical" selectable />`
  with `selectedKeys=[lang]`; native hover / keyboard nav / active
  highlight come for free.
- Drop CSS for `.theme-cycle`, `.lang-list`, `.lang-item*` (now unused).
  `.toolbar-btn` retained since it sizes both circular buttons.
This commit is contained in:
MHSanaei 2026-05-25 03:53:05 +02:00
parent 7e5f279284
commit 0362590b10
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
2 changed files with 31 additions and 108 deletions

View file

@ -228,36 +228,6 @@
font-size: 18px; font-size: 18px;
} }
.theme-cycle {
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid var(--color-border);
background: var(--bg-card);
color: var(--color-text);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 0;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
transition: background-color 0.2s, transform 0.15s, color 0.2s;
}
.theme-cycle:hover,
.theme-cycle:focus-visible {
background-color: rgba(99, 102, 241, 0.15);
color: var(--color-accent);
transform: scale(1.05);
outline: none;
}
.theme-cycle svg {
width: 18px;
height: 18px;
}
.login-wrapper { .login-wrapper {
position: relative; position: relative;
min-height: 100vh; min-height: 100vh;
@ -402,44 +372,3 @@
margin-bottom: 0; margin-bottom: 0;
} }
.lang-list {
list-style: none;
margin: 0;
padding: 0;
min-width: 160px;
display: flex;
flex-direction: column;
gap: 2px;
}
.lang-item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 8px 12px;
border: none;
border-radius: 8px;
background: transparent;
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
transition: background-color 0.15s, color 0.15s;
}
.lang-item:hover,
.lang-item:focus-visible {
background-color: rgba(99, 102, 241, 0.12);
outline: none;
}
.lang-item.is-active {
color: var(--color-accent);
font-weight: 600;
}
.lang-item-icon {
font-size: 16px;
line-height: 1;
}

View file

@ -6,13 +6,18 @@ import {
Form, Form,
Input, Input,
Layout, Layout,
Menu,
Popover, Popover,
Space,
Spin, Spin,
message, message,
} from 'antd'; } from 'antd';
import { import {
KeyOutlined, KeyOutlined,
LockOutlined, LockOutlined,
MoonFilled,
MoonOutlined,
SunOutlined,
TranslationOutlined, TranslationOutlined,
UserOutlined, UserOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
@ -105,26 +110,20 @@ export default function LoginPage() {
return classes.join(' '); return classes.join(' ');
}, [isDark, isUltra]); }, [isDark, isUltra]);
const langList = useMemo( const langMenuItems = useMemo(
() => LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[], () => (LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[]).map((l) => ({
key: l.value,
label: (
<Space size={8}>
<span aria-hidden="true">{l.icon}</span>
<span>{l.name}</span>
</Space>
),
})),
[], [],
); );
const themeIcon = !isDark ? ( const themeIcon = !isDark ? <SunOutlined /> : !isUltra ? <MoonOutlined /> : <MoonFilled />;
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
</svg>
) : !isUltra ? (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
) : (
<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
<path fill="none" d="M19 3l0.7 1.4 1.4 0.7-1.4 0.7L19 7.2l-0.7-1.4-1.4-0.7 1.4-0.7z" />
</svg>
);
return ( return (
<ConfigProvider theme={antdThemeConfig}> <ConfigProvider theme={antdThemeConfig}>
@ -132,35 +131,30 @@ export default function LoginPage() {
<Layout className={pageClass}> <Layout className={pageClass}>
<Layout.Content className="login-content"> <Layout.Content className="login-content">
<div className="login-toolbar"> <div className="login-toolbar">
<button <Button
type="button"
id="login-theme-cycle" id="login-theme-cycle"
className="theme-cycle" shape="circle"
size="large"
className="toolbar-btn"
aria-label={t('menu.theme')} aria-label={t('menu.theme')}
title={t('menu.theme')} title={t('menu.theme')}
icon={themeIcon}
onClick={cycleTheme} onClick={cycleTheme}
> />
{themeIcon}
</button>
<Popover <Popover
rootClassName={isDark ? 'dark' : 'light'} rootClassName={isDark ? 'dark' : 'light'}
placement="bottomRight" placement="bottomRight"
trigger="click" trigger="click"
styles={{ content: { padding: 4 } }}
content={ content={
<ul className="lang-list"> <Menu
{langList.map((l) => ( mode="vertical"
<li key={l.value}> selectable
<button selectedKeys={[lang]}
type="button" items={langMenuItems}
className={`lang-item${lang === l.value ? ' is-active' : ''}`} onClick={({ key }) => onLangChange(key)}
onClick={() => onLangChange(l.value)} style={{ border: 'none', minWidth: 160 }}
> />
<span className="lang-item-icon" aria-hidden="true">{l.icon}</span>
<span className="lang-item-name">{l.name}</span>
</button>
</li>
))}
</ul>
} }
> >
<Button <Button