refactor(perferences): use center wrapper layout

This commit is contained in:
jialin
2026-06-29 19:44:28 +08:00
committed by jialin
parent b8d7873b77
commit f719c11606
19 changed files with 684 additions and 144 deletions
+114 -56
View File
@@ -1,92 +1,150 @@
import useUserSettings from '@/hooks/use-user-settings';
import langConfigMap from '@/locales/lang-config-map';
import { MoonOutlined, SunOutlined } from '@ant-design/icons';
import { CheckCircleFilled } from '@ant-design/icons';
import { BaseSelect } from '@gpustack/core-ui';
import { getAllLocales, setLocale, useIntl } from '@umijs/max';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import _ from 'lodash';
import React from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
`;
import { SettingRow, SettingsGroup } from './settings-group';
import ThemePreview, { PreviewMode } from './theme-preview';
const SettingsItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
max-width: 300px;
.label {
const useStyles = createStyles(({ token, css }) => ({
cards: css`
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
`,
card: css`
padding: 8px;
border: 1px solid ${token.colorBorderSecondary};
border-radius: ${token.borderRadiusLG + 2}px;
background: ${token.colorBgContainer};
cursor: pointer;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:hover {
border-color: ${token.colorPrimaryBorderHover};
}
`,
cardActive: css`
border-color: ${token.colorPrimary};
box-shadow: 0 0 0 1px ${token.colorPrimary};
`,
meta: css`
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 8px 6px;
`,
label: css`
font-size: 14px;
font-weight: var(--font-weight-500);
}
`;
font-weight: var(--font-weight-medium);
color: ${token.colorText};
`,
check: css`
font-size: 18px;
color: ${token.colorPrimary};
`,
radio: css`
width: 16px;
height: 16px;
border-radius: 50%;
border: 1.5px solid ${token.colorBorder};
`
}));
const Appearance: React.FC = () => {
const { setTheme, userSettings } = useUserSettings();
const intl = useIntl();
const { styles } = useStyles();
const allLocals = getAllLocales();
const ThemeOptions = [
const themeOptions: { value: PreviewMode; label: string }[] = [
{
value: 'light',
label: intl.formatMessage({ id: 'common.appearance.light' }),
icon: <SunOutlined />
label: intl.formatMessage({ id: 'common.appearance.lightmode' })
},
{
value: 'realDark',
label: intl.formatMessage({ id: 'common.appearance.dark' }),
icon: <MoonOutlined />
label: intl.formatMessage({ id: 'common.appearance.darkmode' })
},
{
value: 'auto',
label: intl.formatMessage({ id: 'common.appearance.system' }),
icon: <SunOutlined />
label: intl.formatMessage({ id: 'common.appearance.system' })
}
];
const handleOnChange = (value: 'light' | 'realDark' | 'auto') => {
setTheme(value);
};
const languageOptions = allLocals.map((locale) => ({
value: locale,
label: _.get(langConfigMap, [locale, 'label'])
}));
const handleSelectTheme = (value: PreviewMode) => {
setTheme(value);
};
return (
<Wrapper>
<SettingsItem>
<span className="label">
<span>{intl.formatMessage({ id: 'common.appearance.theme' })}</span>
</span>
<BaseSelect
defaultValue={'light'}
value={userSettings.mode}
options={ThemeOptions}
onChange={handleOnChange}
style={{ width: 200 }}
></BaseSelect>
</SettingsItem>
<SettingsItem>
<span className="label">
<span>{intl.formatMessage({ id: 'common.settings.language' })}</span>
</span>
<BaseSelect
value={intl.locale}
options={languageOptions}
onChange={(value) => {
setLocale(value, false);
}}
style={{ width: 200 }}
></BaseSelect>
</SettingsItem>
</Wrapper>
<SettingsGroup>
<SettingRow
title={intl.formatMessage({ id: 'common.appearance.theme' })}
description={intl.formatMessage({ id: 'common.appearance.tips' })}
>
<div className={styles.cards}>
{themeOptions.map((option) => {
const active = userSettings.mode === option.value;
return (
<div
key={option.value}
role="radio"
aria-checked={active}
tabIndex={0}
className={classNames(styles.card, {
[styles.cardActive]: active
})}
onClick={() => handleSelectTheme(option.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSelectTheme(option.value);
}
}}
>
<ThemePreview mode={option.value} />
<div className={styles.meta}>
<span className={styles.label}>{option.label}</span>
{active ? (
<CheckCircleFilled className={styles.check} />
) : (
<span className={styles.radio} />
)}
</div>
</div>
);
})}
</div>
</SettingRow>
<SettingRow
title={intl.formatMessage({ id: 'common.settings.language' })}
description={intl.formatMessage({
id: 'common.settings.language.tips'
})}
extra={
<BaseSelect
value={intl.locale}
options={languageOptions}
onChange={(value: string) => {
setLocale(value, false);
}}
style={{ width: 200 }}
/>
}
/>
</SettingsGroup>
);
};