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 to join WeChat group')}
+
+
+ {t('If QR expires, add drizzle2001 to join')}
+
+
+
+ Telegram
+
+
+
+ } href={TELEGRAM_URL} target="_blank" rel="noopener noreferrer">
+ {t('Join Telegram group')}
+
+
+
+
+ );
+}
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 && (
-
- } href="https://github.com/DrizzleTime/Foxel" target="_blank" />
- } onClick={() => setIsModalOpen(true)} />
- } href="https://t.me/+thDsBfyqJxZkNTU1" target="_blank" />
- } href="https://foxel.cc" target="_blank" />
-
- )}
+
+
+ } href="https://github.com/DrizzleTime/Foxel" target="_blank" rel="noopener noreferrer" />
+
+
+ } onClick={() => {
+ if (mobile) onClose?.();
+ setCommunityOpen(true);
+ }} />
+
+
+ } href="https://foxel.cc" target="_blank" rel="noopener noreferrer" />
+
+ {user?.is_admin && (
+
+ } onClick={() => handleChange('settings')} />
+
+ )}
+
);
@@ -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 (
-
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);
}