diff --git a/web/src/components/CommunityModal.tsx b/web/src/components/CommunityModal.tsx new file mode 100644 index 0000000..214370e --- /dev/null +++ b/web/src/components/CommunityModal.tsx @@ -0,0 +1,43 @@ +import { Button, Modal, QRCode, theme } from 'antd'; +import { SendOutlined, WechatOutlined } from '@ant-design/icons'; +import { useI18n } from '../i18n'; + +export interface CommunityModalProps { + open: boolean; + onClose: () => void; +} + +const TELEGRAM_URL = 'https://t.me/+thDsBfyqJxZkNTU1'; + +export default function CommunityModal({ open, onClose }: CommunityModalProps) { + const { token } = theme.useToken(); + const { t } = useI18n(); + + return ( + +
+
+

{t('WeChat')}

+
+ {t('Scan +
+
+ {t('Scan to join WeChat group')} +
+
+ {t('If QR expires, add drizzle2001 to join')} +
+
+
+

Telegram

+
+ +
+ +
+
+
+ ); +} diff --git a/web/src/global.css b/web/src/global.css index bb80461..4a2b869 100644 --- a/web/src/global.css +++ b/web/src/global.css @@ -240,23 +240,19 @@ body { margin: 0; font-family: system-ui,-apple-system,BlinkMacSystemFont,'Segoe min-height: 0; padding: 5px; } -.processors-tabs .ant-tabs-content-holder, -.processors-tabs .ant-tabs-content { +.processors-tabs > .ant-tabs-body-holder, +.processors-tabs .processors-tabs-body { flex: 1; height: 100%; min-height: 0; display: flex; flex-direction: column; } -.processors-tabs .ant-tabs-tabpane { +.processors-tabs .processors-tabs-pane { flex: 1; height: 100%; min-height: 0; - display: none; - flex-direction: column; -} -.processors-tabs .ant-tabs-tabpane-active { - display: flex; + overflow: auto; } .plugins-tabs { diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 5620307..ae0b662 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -173,6 +173,10 @@ "Directory conflicts with existing file": "A file with the same name already exists at the target location", "Join Community": "Join Community", "Scan to join WeChat group": "Scan to join WeChat group", + "Community": "Community", + "Documentation": "Documentation", + "WeChat": "WeChat", + "Join Telegram group": "Join Telegram group", "If QR expires, add drizzle2001 to join": "If QR expires, add drizzle2001 to join", "Version Info": "Version Info", "Current Version": "Current Version", @@ -300,6 +304,10 @@ "Save failed": "Save failed", "Loading...": "Loading...", "Appearance Settings": "Appearance Settings", + "Appearance": "Appearance", + "System": "System", + "AI": "AI", + "Mappings": "Mappings", "Theme": "Theme", "Theme Mode": "Theme Mode", "Light": "Light", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index b753474..bb46708 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -193,6 +193,10 @@ "Directory conflicts with existing file": "目标存在同名文件,无法创建目录", "Join Community": "加入社区", "Scan to join WeChat group": "微信扫码加入交流群", + "Community": "社区", + "Documentation": "文档", + "WeChat": "微信", + "Join Telegram group": "加入 Telegram 群组", "If QR expires, add drizzle2001 to join": "如二维码失效,请添加 drizzle2001 拉群", "Version Info": "版本信息", "Current Version": "当前版本", @@ -319,6 +323,9 @@ "Save failed": "保存失败", "Loading...": "加载中...", "Appearance Settings": "外观设置", + "Appearance": "外观", + "AI": "AI", + "Mappings": "映射", "Theme": "主题", "Theme Mode": "主题模式", "Light": "亮色", diff --git a/web/src/layout/SideNav.tsx b/web/src/layout/SideNav.tsx index aab672f..ae7dd35 100644 --- a/web/src/layout/SideNav.tsx +++ b/web/src/layout/SideNav.tsx @@ -8,8 +8,8 @@ import { FileTextOutlined, GithubOutlined, MenuFoldOutlined, - SendOutlined, - WechatOutlined, + SettingOutlined, + TeamOutlined, WarningOutlined, } from '@ant-design/icons'; import '../styles/sider-menu.css'; @@ -18,7 +18,7 @@ import ReactMarkdown from 'react-markdown'; import { useTheme } from '../contexts/ThemeContext'; import { useI18n } from '../i18n'; import { useAppWindows } from '../contexts/AppWindowsContext'; -import WeChatModal from '../components/WeChatModal'; +import CommunityModal from '../components/CommunityModal'; import { useAuth } from '../contexts/AuthContext'; const { Sider } = Layout; @@ -47,7 +47,7 @@ const SideNav = memo(function SideNav({ const { resolvedMode } = useTheme(); const { t } = useI18n(); const { user } = useAuth(); - const [isModalOpen, setIsModalOpen] = useState(false); + const [communityOpen, setCommunityOpen] = useState(false); const [isVersionModalOpen, setIsVersionModalOpen] = useState(false); const [latestVersion, setLatestVersion] = useState<{ version: string; @@ -255,14 +255,25 @@ const SideNav = memo(function SideNav({ )} - {!bodyCollapsed && ( -
-
- )} +
+ +
); @@ -296,7 +307,7 @@ const SideNav = memo(function SideNav({ )} - setIsModalOpen(false)} /> + setCommunityOpen(false)} /> setIsVersionModalOpen(false)} diff --git a/web/src/layout/nav.ts b/web/src/layout/nav.ts index 295898c..e8bc982 100644 --- a/web/src/layout/nav.ts +++ b/web/src/layout/nav.ts @@ -4,7 +4,6 @@ import { ApiOutlined, ShareAltOutlined, CloudDownloadOutlined, - SettingOutlined, RobotOutlined, BugOutlined, DatabaseOutlined, @@ -44,7 +43,6 @@ export const navGroups: NavGroup[] = [ title: 'System', children: [ { key: 'users', icon: React.createElement(UserOutlined), label: 'User Management', adminOnly: true }, - { key: 'settings', icon: React.createElement(SettingOutlined), label: 'System Settings', adminOnly: true }, { key: 'backup', icon: React.createElement(DatabaseOutlined), label: 'Backup & Restore', hideOnMobile: true }, { key: 'audit', icon: React.createElement(BugOutlined), label: 'Audit Logs' } ] diff --git a/web/src/pages/ProcessorsPage.tsx b/web/src/pages/ProcessorsPage.tsx index e092d6d..763e795 100644 --- a/web/src/pages/ProcessorsPage.tsx +++ b/web/src/pages/ProcessorsPage.tsx @@ -585,6 +585,7 @@ const ProcessorsPage = memo(function ProcessorsPage() { styles={{ body: { padding: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' } }} > setActiveTab(key as TabKey)} items={tabs as any} diff --git a/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx b/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx index f7d340d..ae9e1d9 100644 --- a/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx +++ b/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx @@ -1,8 +1,7 @@ -import { Alert, message, Tabs, Space } from 'antd'; +import { Alert, message, Tabs, Modal } from 'antd'; import { useEffect, useState } from 'react'; -import PageCard from '../../components/PageCard'; import { getAllConfig, setConfig } from '../../api/config'; -import { AppstoreOutlined, RobotOutlined, DatabaseOutlined, SkinOutlined, MailOutlined, CloudSyncOutlined } from '@ant-design/icons'; +import { AppstoreOutlined, RobotOutlined, DatabaseOutlined, SkinOutlined, MailOutlined, CloudSyncOutlined, CloseOutlined } from '@ant-design/icons'; import { useTheme } from '../../contexts/ThemeContext'; import '../../styles/settings-tabs.css'; import { useI18n } from '../../i18n'; @@ -18,12 +17,21 @@ type TabKey = 'appearance' | 'app' | 'email' | 'ai' | 'vector-db' | 'mappings'; const TAB_KEYS: TabKey[] = ['appearance', 'app', 'email', 'ai', 'vector-db', 'mappings']; const DEFAULT_TAB: TabKey = 'appearance'; +const TAB_TITLES: Record = { + appearance: 'Appearance', + app: 'System', + email: 'Email', + ai: 'AI', + 'vector-db': 'Database', + mappings: 'Mappings', +}; const isValidTab = (key?: string): key is TabKey => !!key && (TAB_KEYS as string[]).includes(key); interface SystemSettingsPageProps { tabKey?: string; onTabNavigate?: (key: TabKey, options?: { replace?: boolean }) => void; + onClose?: () => void; } const APP_CONFIG_KEYS: { key: string, label: string, default?: string }[] = [ @@ -67,7 +75,7 @@ const CONFIG_DEFAULTS: Record = { const stringifyConfigValue = (value: unknown) => String(value ?? ''); -export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSettingsPageProps) { +export default function SystemSettingsPage({ tabKey, onTabNavigate, onClose }: SystemSettingsPageProps) { const [loading, setLoading] = useState(false); const [config, setConfigState] = useState | null>(null); const [loadError, setLoadError] = useState(null); @@ -145,35 +153,47 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett onTabNavigate?.(nextKey); }; - if (loadError) { - return ( - - - - ); - } - - if (!config) { - return
{t('Loading...')}
; - } - return ( - } + title={ +
+ {t('System Settings')} + {t(TAB_TITLES[activeTab])} +
+ } + footer={null} + width={isMobile ? 'calc(100vw - 24px)' : 1180} + style={{ maxWidth: 'calc(100vw - 24px)' }} + centered + destroyOnHidden={false} + className="fx-settings-modal" + styles={{ + body: { padding: 0, overflow: 'hidden' }, + container: { padding: 0, overflow: 'hidden' }, + }} > - + {loadError ? ( +
+ ) : !config ? ( +
{t('Loading...')}
+ ) : ( - {t('Appearance Settings')} + {t(TAB_TITLES.appearance)} ), children: ( @@ -190,7 +210,7 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett label: ( - {t('App Settings')} + {t(TAB_TITLES.app)} ), children: ( @@ -207,7 +227,7 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett label: ( - {t('Email Settings')} + {t(TAB_TITLES.email)} ), children: ( @@ -223,7 +243,7 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett label: ( - {t('AI Settings')} + {t(TAB_TITLES.ai)} ), children: ( @@ -236,7 +256,7 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett label: ( - {t('Vector Database')} + {t(TAB_TITLES['vector-db'])} ), children: ( @@ -248,7 +268,7 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett label: ( - {t('Protocol Mappings')} + {t(TAB_TITLES.mappings)} ), children: ( @@ -261,7 +281,7 @@ export default function SystemSettingsPage({ tabKey, onTabNavigate }: SystemSett }, ]} /> -
-
+ )} +
); } diff --git a/web/src/pages/SystemSettingsPage/components/AppearanceSettingsTab.tsx b/web/src/pages/SystemSettingsPage/components/AppearanceSettingsTab.tsx index da0bc8b..5570196 100644 --- a/web/src/pages/SystemSettingsPage/components/AppearanceSettingsTab.tsx +++ b/web/src/pages/SystemSettingsPage/components/AppearanceSettingsTab.tsx @@ -1,4 +1,6 @@ -import { Form, Input, Button, InputNumber, Card, Radio, message } from 'antd'; +import { Form, Input, Button, InputNumber, ColorPicker, Radio, message } from 'antd'; +import { SaveOutlined } from '@ant-design/icons'; +import { useEffect } from 'react'; import { useTheme } from '../../../contexts/ThemeContext'; import { useI18n } from '../../../i18n'; @@ -23,9 +25,11 @@ export default function AppearanceSettingsTab({ onSave, themeKeys, }: AppearanceSettingsTabProps) { - const { previewTheme } = useTheme(); + const { previewTheme, refreshTheme } = useTheme(); const { t } = useI18n(); + useEffect(() => () => { void refreshTheme(); }, [refreshTheme]); + return (
- +
+

{t('Theme')}

{t('Light')} @@ -77,23 +82,28 @@ export default function AppearanceSettingsTab({ {t('Follow System')} - - + hex} + > + - + - - +
+
+

{t('Advanced')}

- + - + - - -
+ + diff --git a/web/src/router/LayoutShell.tsx b/web/src/router/LayoutShell.tsx index d380b05..c014b9a 100644 --- a/web/src/router/LayoutShell.tsx +++ b/web/src/router/LayoutShell.tsx @@ -1,6 +1,7 @@ import { Layout, Flex } from 'antd'; -import { memo, useEffect, useState } from 'react'; -import { useParams, useNavigate } from 'react-router'; +import { memo, useCallback, useEffect, useState } from 'react'; +import { useParams, useNavigate, useLocation } from 'react-router'; +import type { Location } from 'react-router'; import SideNav from '../layout/SideNav.tsx'; import TopHeader from '../layout/TopHeader.tsx'; import FileExplorerPage from '../pages/FileExplorerPage/FileExplorerPage.tsx'; @@ -20,11 +21,14 @@ import { AppWindowsLayer } from '../apps/AppWindowsLayer'; import AiAgentWidget from '../components/AiAgentWidget'; import useResponsive from '../hooks/useResponsive'; -const ShellBody = memo(function ShellBody() { +const ShellBody = memo(function ShellBody({ navigationLocation }: { navigationLocation?: Location }) { const params = useParams<{ navKey?: string; '*': string }>(); const navKey = params.navKey ?? 'files'; const subPath = params['*'] ?? ''; const navigate = useNavigate(); + const pageLocation = useLocation(); + const location = navigationLocation ?? pageLocation; + const settingsOpen = /^\/settings(?:\/|$)/.test(location.pathname); const { isMobile } = useResponsive(); const COLLAPSED_KEY = 'layout.siderCollapsed'; const [collapsed, setCollapsed] = useState(() => localStorage.getItem(COLLAPSED_KEY) === '1'); @@ -40,7 +44,27 @@ const ShellBody = memo(function ShellBody() { }, [isMobile, navKey, subPath]); const { windows, closeWindow, toggleMax, bringToFront, updateWindow } = useAppWindows(); - const settingsTab = navKey === 'settings' ? (subPath.split('/')[0] || undefined) : undefined; + const settingsTab = settingsOpen ? location.pathname.split('/')[2] : undefined; + const handleNavChange = useCallback((key: string) => { + setMobileNavOpen(false); + if (key === 'settings') { + navigate('/settings/appearance', { + state: { settingsBackground: location }, + }); + } else { + navigate(`/${key}`); + } + }, [location, navigate]); + const handleSettingsTabNavigate = useCallback((key: string) => { + navigate(`/settings/${key}`, { replace: true, state: location.state }); + }, [location.state, navigate]); + const handleCloseSettings = () => { + const background = location.state?.settingsBackground; + const target = background && !/^\/settings(?:\/|$)/.test(background.pathname) + ? background + : { pathname: '/files' }; + navigate(target, { replace: true, state: background?.state }); + }; const agentCurrentPath = navKey === 'files' ? ('/' + subPath).replace(/\/+/g, '/').replace(/\/+$/, '') || '/' : null; const handleToggleNav = () => { if (isMobile) { @@ -57,13 +81,7 @@ const ShellBody = memo(function ShellBody() { collapsed={collapsed} onToggle={handleToggleNav} activeKey={navKey} - onChange={(key) => { - if (key === 'settings') { - navigate('/settings/appearance', { replace: true }); - } else { - navigate(`/${key}`); - } - }} + onChange={handleNavChange} /> )} @@ -75,13 +93,7 @@ const ShellBody = memo(function ShellBody() { collapsed={false} onToggle={handleToggleNav} activeKey={navKey} - onChange={(key) => { - if (key === 'settings') { - navigate('/settings/appearance', { replace: true }); - } else { - navigate(`/${key}`); - } - }} + onChange={handleNavChange} /> )} @@ -113,9 +125,6 @@ const ShellBody = memo(function ShellBody() { {navKey === 'processors' && } {navKey === 'offline' && } {navKey === 'plugins' && } - {navKey === 'settings' && ( - navigate(`/settings/${key}`, options)} /> - )} {navKey === 'audit' && } {navKey === 'backup' && } {navKey === 'users' && } @@ -124,6 +133,13 @@ const ShellBody = memo(function ShellBody() { + {settingsOpen && ( + + )} - + ); }); diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index c1ea71b..becfa50 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -35,11 +35,24 @@ function RequireAuth({ children }: { children: JSX.Element }) { } export function AppRouter() { + const location = useLocation(); + const settingsOpen = /^\/settings(?:\/|$)/.test(location.pathname); + const background = location.state?.settingsBackground; + const pageLocation = settingsOpen + ? background && !/^\/settings(?:\/|$)/.test(background.pathname) + ? background + : { ...location, pathname: '/files', search: '', hash: '' } + : location; + return ( - + {routes.map(r => ( - + : r.element} + /> ))} diff --git a/web/src/styles/settings-tabs.css b/web/src/styles/settings-tabs.css index 7c87843..d4f3591 100644 --- a/web/src/styles/settings-tabs.css +++ b/web/src/styles/settings-tabs.css @@ -1,67 +1,229 @@ -.fx-settings-tabs .ant-tabs-nav { - margin-bottom: 12px; +.fx-settings-modal { + max-width: calc(100vw - 64px); + padding-bottom: 0; } -.fx-settings-tabs .ant-tabs-nav-list { - padding: 4px 8px; - width: 100%; - gap: 8px; +.fx-settings-modal .ant-modal-container { + padding: 0; + overflow: hidden; + border-radius: 20px; + box-shadow: 0 20px 64px rgba(0, 0, 0, 0.18); } -.fx-settings-tabs .ant-tabs-tab { - margin: 0 !important; +.fx-settings-modal .ant-modal-header { + margin: 0; + padding: 0; +} + +.fx-settings-modal .ant-modal-close { + top: 16px; + right: 18px; +} + +.fx-settings-modal-heading { + display: grid; + grid-template-columns: 180px minmax(0, 1fr); + min-height: 68px; + font-size: 18px; + letter-spacing: 0; +} + +.fx-settings-modal-title, +.fx-settings-modal-section-title { + display: flex; + align-items: center; + padding: 18px 24px; +} + +.fx-settings-modal-title { + background: var(--ant-color-fill-quaternary); + border-right: 1px solid var(--ant-color-border-secondary); +} + +.fx-settings-modal-section-title { + padding-right: 64px; + border-bottom: 1px solid var(--ant-color-border-secondary); +} + +.fx-settings-tabs { + height: min(730px, calc(100dvh - 164px)); + min-height: 0; +} + +.fx-settings-tabs > .ant-tabs-nav { + width: 180px; + min-width: 180px; + padding: 12px 10px; + margin: 0; + background: var(--ant-color-fill-quaternary); + border-right: 1px solid var(--ant-color-border-secondary); +} + +.fx-settings-tabs > .ant-tabs-nav::before, +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-ink-bar { + display: none; +} + +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab { + margin: 0 0 4px; + padding: 11px 12px; border-radius: 8px; - padding: 8px 12px !important; - flex: 1 1 auto; - justify-content: center; + text-align: left; + white-space: normal; } -.fx-settings-tabs .ant-tabs-tab .ant-tabs-tab-btn { - color: var(--ant-color-text-secondary) !important; +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab-btn { + width: 100%; + color: var(--ant-color-text-secondary); + text-shadow: none; } -.fx-settings-tabs .ant-tabs-tab:hover { - background: var(--ant-color-fill-tertiary) !important; +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab-btn > span { + display: flex; + align-items: center; } -/* 选中态:按主题细分 */ -html[data-theme='dark'] .fx-settings-tabs .ant-tabs-tab-active { - background: var(--ant-color-primary-bg) !important; +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab:hover { + background: var(--ant-color-fill-tertiary); } -html[data-theme='dark'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn, -html[data-theme='dark'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn .anticon { - color: var(--ant-color-text) !important; + +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab-active { + background: var(--ant-color-fill-secondary); +} + +.fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab-active .ant-tabs-tab-btn { + color: var(--ant-color-text); font-weight: 600; } -html[data-theme='light'] .fx-settings-tabs .ant-tabs-tab-active { - background: var(--ant-color-primary) !important; +.fx-settings-tabs > .ant-tabs-body-holder { + min-width: 0; + min-height: 0; + border: 0; + overflow: hidden; } -html[data-theme='light'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn, -html[data-theme='light'] .fx-settings-tabs .ant-tabs-tab-active .ant-tabs-tab-btn .anticon { - color: var(--ant-color-text-light-solid) !important; + +.fx-settings-tabs .fx-settings-body { + height: 100%; +} + +.fx-settings-modal .fx-settings-tabs > .ant-tabs-body-holder > .fx-settings-body > .fx-settings-pane { + height: 100%; + padding: 24px 32px 32px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: var(--ant-color-border) transparent; + overscroll-behavior: contain; +} + +.fx-settings-pane > .ant-form, +.fx-settings-pane > .ant-space, +.fx-settings-pane > .ant-card { + margin-top: 0 !important; +} + +.fx-settings-section { + padding-bottom: 8px; +} + +.fx-settings-section + .fx-settings-section { + padding-top: 24px; + border-top: 1px solid var(--ant-color-border-secondary); +} + +.fx-settings-section h3 { + margin: 0 0 20px; + color: var(--ant-color-text-secondary); + font-size: 14px; font-weight: 600; } -.fx-settings-tabs .ant-tabs-ink-bar { - background: var(--ant-color-primary) !important; +.fx-settings-save { + position: sticky; + bottom: -32px; + margin: 0; + padding: 16px 0; + background: var(--ant-color-bg-container); + border-top: 1px solid var(--ant-color-border-secondary); +} + +.fx-settings-modal-message { + min-height: 240px; + padding: 32px; +} + +.fx-settings-tabs .fx-ai-top-bar h3 { + font-size: 18px; } @media (max-width: 767px) { - .fx-settings-tabs .ant-tabs-nav { - overflow-x: auto; + .fx-settings-modal { + width: calc(100vw - 24px) !important; + max-width: calc(100vw - 24px); + margin: 12px auto; } - .fx-settings-tabs .ant-tabs-nav-list { - width: max-content; - min-width: 100%; - flex-wrap: nowrap; - padding: 2px 4px; + .fx-settings-modal .ant-modal-container { + border-radius: 16px; } - .fx-settings-tabs .ant-tabs-tab { + .fx-settings-modal-heading { + grid-template-columns: minmax(0, 1fr); + min-height: 60px; + } + + .fx-settings-modal-title { + padding: 18px 20px; + padding-right: 60px; + border: 0; + background: var(--ant-color-bg-container); + } + + .fx-settings-modal-section-title { + display: none; + } + + .fx-settings-tabs { + height: calc(100dvh - 108px); + flex-direction: column; + } + + .fx-settings-tabs > .ant-tabs-nav { + width: 100%; + min-width: 0; + padding: 6px 12px; + border: 0; + border-bottom: 1px solid var(--ant-color-border-secondary); + } + + .fx-settings-tabs > .ant-tabs-nav .ant-tabs-tab { flex: 0 0 auto; - justify-content: flex-start; + padding: 9px 12px; + margin: 0 4px 0 0; white-space: nowrap; } + + .fx-settings-tabs > .ant-tabs-body-holder { + flex: 1; + } + + .fx-settings-modal .fx-settings-tabs > .ant-tabs-body-holder > .fx-settings-body > .fx-settings-pane { + padding: 20px 18px 24px; + } + + .fx-settings-save { + bottom: -24px; + } +} + +.fx-settings-modal .ant-color-picker-trigger, +.fx-settings-modal .ant-input-outlined, +.fx-settings-modal .ant-input-number-outlined { + background: var(--ant-color-bg-container); + color: var(--ant-color-text); +} + +.fx-settings-modal .ant-radio-button-wrapper:not(.ant-radio-button-wrapper-checked) { + background: var(--ant-color-bg-container); + color: var(--ant-color-text); }