mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
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:
parent
7e5f279284
commit
0362590b10
2 changed files with 31 additions and 108 deletions
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue