diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d754a7452f..6f1d97d75d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,9 @@ jobs: - name: Run verification-code countdown lifecycle contract run: yarn run test:verification-code-countdown + - name: Run settings layout contract + run: yarn run test:settings-layout + - name: Build Community web client run: yarn run build:web:community --app_version=5.3.0 diff --git a/chat2db-community-client/package.json b/chat2db-community-client/package.json index ad599ee9f6..b79cc0bfa6 100644 --- a/chat2db-community-client/package.json +++ b/chat2db-community-client/package.json @@ -41,6 +41,7 @@ "test:search-result-tab-selection": "tsx src/blocks/SearchResult/tabSelection.test.ts", "test:saved-console-tree-refresh": "tsx src/store/tree/savedConsoleTreeRefresh.test.ts", "test:saved-console-lifecycle": "tsx src/store/workspace/utils/savedConsoleLifecycle.test.ts", + "test:settings-layout": "tsx src/blocks/Setting/navigation.test.ts && tsx src/blocks/Setting/search.test.ts && tsx src/blocks/Setting/BaseSetting/model.test.ts && tsx src/blocks/Setting/settingsLayout.test.ts", "test:sql-execution-log": "tsx src/service/sqlExecutionLog.test.ts", "test:sql-in-clipboard": "tsx src/utils/sqlInClipboard.test.ts && tsx src/components/SQLEditor/helper/sqlInsertValueDefaults.test.ts", "test:tree-title-highlight": "tsx src/blocks/NewTree/components/TitleRender/highlightSearchText.test.ts", diff --git a/chat2db-community-client/scripts/i18n-source-hashes.json b/chat2db-community-client/scripts/i18n-source-hashes.json index 70e67c8641..cfa1f1fb35 100644 --- a/chat2db-community-client/scripts/i18n-source-hashes.json +++ b/chat2db-community-client/scripts/i18n-source-hashes.json @@ -16,12 +16,12 @@ "license.ts": "6aff8c38b1a7475264aa1e16e88d9b9731ecef112af70fe53edab449788ea788", "login.ts": "d6b15f2b4db713711c093108372027ad65899aec6dd963d3f954231a9ce5b890", "menu.ts": "1f9adabb7465ed545bdaa57fd2a0c10913e04979e710f65c6822f295c4d3e6ed", - "monaco.ts": "0c7f14a176a65e1e534d68bead392907430b943b80edd1ce173a1ddfcc50ab4f", + "monaco.ts": "da4d9ea1a29de505f94ca9c49d125805fbef1c3132c7aa0f49f1c278b8cb1e93", "notification.ts": "500751da2f02391622f074448eb46611fd41da2e5530d6c7d65f17a1382bd236", "plugin.ts": "01f15b6342cc5b29899ee4144d67b04bb4bc02c6bb10779806be322dc5e9a2bc", "price.ts": "26e35cb96368585f83a29adbb14e300da9b7f5278a4320063e81a1d1dd0adee9", "redis.ts": "a73e4fca6d783031370060e20bb1bbba13f663a0773cd031e1744be1cb4e93af", - "setting.ts": "0cbee33ef6464e82b35af4fc22bbe15fceb34a5b395fc582bac50ee77424f38c", + "setting.ts": "447e759e9842d790e80e6d5680032bf150ec259c6d48daa87458277a78a9ba1f", "spaceSetting.ts": "defdf3138e5e1782f0282760bcef4ce1b8550cc80b03052fea468d97165cb980", "sqlEditor.ts": "5c41e5f24d5ba78909795bcc681cabb8460cca075233b665dc4940afe82ce0c9", "stream.ts": "8418dd25b0fd54b2ef2b15a2573edf0a4876efb4e69936ff82a157ea5cf42be4", @@ -43,12 +43,12 @@ "license.ts": "6aff8c38b1a7475264aa1e16e88d9b9731ecef112af70fe53edab449788ea788", "login.ts": "d6b15f2b4db713711c093108372027ad65899aec6dd963d3f954231a9ce5b890", "menu.ts": "1f9adabb7465ed545bdaa57fd2a0c10913e04979e710f65c6822f295c4d3e6ed", - "monaco.ts": "0c7f14a176a65e1e534d68bead392907430b943b80edd1ce173a1ddfcc50ab4f", + "monaco.ts": "da4d9ea1a29de505f94ca9c49d125805fbef1c3132c7aa0f49f1c278b8cb1e93", "notification.ts": "500751da2f02391622f074448eb46611fd41da2e5530d6c7d65f17a1382bd236", "plugin.ts": "01f15b6342cc5b29899ee4144d67b04bb4bc02c6bb10779806be322dc5e9a2bc", "price.ts": "26e35cb96368585f83a29adbb14e300da9b7f5278a4320063e81a1d1dd0adee9", "redis.ts": "a73e4fca6d783031370060e20bb1bbba13f663a0773cd031e1744be1cb4e93af", - "setting.ts": "0cbee33ef6464e82b35af4fc22bbe15fceb34a5b395fc582bac50ee77424f38c", + "setting.ts": "447e759e9842d790e80e6d5680032bf150ec259c6d48daa87458277a78a9ba1f", "spaceSetting.ts": "defdf3138e5e1782f0282760bcef4ce1b8550cc80b03052fea468d97165cb980", "sqlEditor.ts": "5c41e5f24d5ba78909795bcc681cabb8460cca075233b665dc4940afe82ce0c9", "stream.ts": "8418dd25b0fd54b2ef2b15a2573edf0a4876efb4e69936ff82a157ea5cf42be4", diff --git a/chat2db-community-client/src/blocks/Setting/BaseSetting/index.tsx b/chat2db-community-client/src/blocks/Setting/BaseSetting/index.tsx index 9abd7d7c82..c0e1581307 100644 --- a/chat2db-community-client/src/blocks/Setting/BaseSetting/index.tsx +++ b/chat2db-community-client/src/blocks/Setting/BaseSetting/index.tsx @@ -3,26 +3,18 @@ import themeDarkDimmedImg from '@/assets/img/theme-dark-dimmed.png'; import themeDarkImg from '@/assets/img/theme-dark.png'; import themeLightImg from '@/assets/img/theme-light.png'; import { runtimeEditionConfig } from '@/constants/runtimeEdition'; -import { LangType } from '@/constants/settings'; +import type { LangType } from '@/constants/settings'; import i18n from '@/i18n'; import { useGlobalStore } from '@/store/global'; import { settingSelectors } from '@/store/global/selectors'; import { refreshPage } from '@/utils'; -import { PrimaryColors, primaryColorsScales, PrimaryGradient, Swatches, ThemeAppearance } from '@chat2db/ui'; -import { Form, Input, Select } from 'antd'; -import { ChevronDown } from 'lucide-react'; +import { PrimaryColors, primaryColorsScales, PrimaryGradient, ThemeAppearance } from '@chat2db/ui'; +import { Input, Select } from 'antd'; +import { Check, ChevronDown, Globe, Palette, Type } from 'lucide-react'; import { useMemo } from 'react'; -import SettingSubsection from '../SettingSubsection'; +import { getAvailableLanguageOptions, resolveCurrentLanguage } from './model'; import { useStyles } from './style'; -const languageOptions = [ - { value: LangType.ZH_CN, label: '简体中文' }, - { value: LangType.EN_US, label: 'English' }, - { value: LangType.JA_JP, label: '日本語' }, - { value: LangType.ES_ES, label: 'Español' }, - { value: LangType.KO_KR, label: '한국어' }, -]; - const customFontSizeOptions = [ { value: 12, label: '12px' }, { value: 13, label: '13px' }, @@ -74,7 +66,6 @@ export default function BaseSetting() { setAppearance: state.setAppearance, isCN: state.appConfig.isCN, setPrimaryColor: state.setPrimaryColor, - setNeutralColor: state.setNeutralColor, setLanguage: state.setLanguage, setCustomFont: state.setCustomFont, setCustomFontSize: state.setCustomFontSize, @@ -83,18 +74,12 @@ export default function BaseSetting() { // If it is not a domestic version, Chinese will not be displayed. const curLanguageOptions = useMemo(() => { - if (runtimeEditionConfig.languageRegionRestricted && !isCN) { - return languageOptions.filter((item) => item.value !== LangType.ZH_CN); - } - return languageOptions; + return getAvailableLanguageOptions(runtimeEditionConfig.languageRegionRestricted, isCN); }, [isCN]); // If it is not a domestic version, Chinese will not be displayed. const curLanguage = useMemo(() => { - if (runtimeEditionConfig.languageRegionRestricted && !isCN && language === LangType.ZH_CN) { - return LangType.EN_US; - } - return language; + return resolveCurrentLanguage(language, runtimeEditionConfig.languageRegionRestricted, isCN); }, [language, isCN]); const isDark = appearance.includes('dark'); @@ -113,108 +98,138 @@ export default function BaseSetting() { window.setTimeout(refreshPage, 0); } - function changeThemeMode(item: any) { - setAppearance(item.code); + function changeThemeMode(code: ThemeAppearance) { + setAppearance(code); } function changePrimaryColor(item: any) { setPrimaryColor(item); } - // function changeNeutralColor(item: any) { - // setNeutralColor(item); - // } - return ( -
-
- -
- {themeList.map((t) => { - return ( -
-
-
{t.name}
-
- ); - })} +
+
+
+
+
+
+
{i18n('setting.title.backgroundColor')}
+
+ {themeList.map((t) => { + const isActive = appearance === t.code; + return ( + + ); + })} +
+
+
+
{i18n('setting.title.themeColor')}
+
+ {primaryColorsSwatches.map((color) => { + const isActive = primaryColor?.label === color.label; + return ( + + ); + })} +
+
-
- - { - setCustomFont(e.target.value); - }} - placeholder={i18n('setting.placeholder.customFont')} - /> - {/* - */} +
+
+
+
{ + setCustomFont(e.target.value); + }} + placeholder={i18n('setting.placeholder.customFont')} + /> + +
+ - - - {i18n('monaco.minimap.on')} - {i18n('monaco.minimap.off')} - - - - - {i18n('monaco.wordWrap.on')} - {i18n('monaco.wordWrap.off')} - - - - - {i18n('monaco.minimap.on')} - {i18n('monaco.minimap.off')} - - - - +
+

+

+
+ {i18n('monaco.lineNumbers')}} + > + + {i18n('monaco.lineNumbers.on')} + {i18n('monaco.lineNumbers.off')} + + + {i18n('monaco.minimap')}} + tooltip={i18n('monaco.minimap.tooltip')} + > + + {i18n('monaco.minimap.on')} + {i18n('monaco.minimap.off')} + + + {i18n('monaco.wordWrap')}} + tooltip={i18n('monaco.wordWrap.tooltip')} + > + + {i18n('monaco.wordWrap.on')} + {i18n('monaco.wordWrap.off')} + + + {i18n('monaco.folding')}} + tooltip={i18n('monaco.folding.tooltip')} + > + + {i18n('monaco.minimap.on')} + {i18n('monaco.minimap.off')} + + + + {i18n('monaco.renderLineHighlight')} + + } + tooltip={i18n('monaco.renderLineHighlight.tooltip')} + > } @@ -196,40 +266,55 @@ function EditorSettings() { }))} /> - - - - {i18n('monaco.errorContinue.true')} - {i18n('monaco.errorContinue.false')} - - - - - {ddlTriggerOptions.map((option) => ( - - {option.label} - - ))} - - - - - {i18n('monaco.completionAcceptKey.enter')} - {i18n('monaco.completionAcceptKey.tab')} - - - - - - -
- -
+ + {i18n('monaco.tableDDLTriggerMode')} + + } + tooltip={i18n('monaco.tableDDLTriggerMode.tooltip', ddlClickModifierKey)} + > + + {ddlTriggerOptions.map((option) => ( + + {option.label} + + ))} + + +
+
+ +
+

+

+
+ {i18n('monaco.errorContinue')} + } + tooltip={i18n('monaco.errorContinue.tooltip')} + > + + {i18n('monaco.errorContinue.true')} + {i18n('monaco.errorContinue.false')} + + +
+
+ +
+
); diff --git a/chat2db-community-client/src/blocks/Setting/EditorSetting/style.ts b/chat2db-community-client/src/blocks/Setting/EditorSetting/style.ts index 519da8c409..179f60eb13 100644 --- a/chat2db-community-client/src/blocks/Setting/EditorSetting/style.ts +++ b/chat2db-community-client/src/blocks/Setting/EditorSetting/style.ts @@ -3,20 +3,88 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles(({ css, token }) => { return { container: css` - display: flex; - gap: 20px; width: 100%; + padding-top: 24px; + display: flex; + gap: 32px; + + @container (max-width: 960px) { + flex-direction: column; + } `, formWrapper: css` flex: 1; - min-width: 240px; + min-width: 0; + `, + settingSection: css` + width: 100%; + padding-bottom: 24px; + + & + & { + padding-top: 24px; + border-top: 1px solid ${token.colorSplit}; + } + + &:last-child { + padding-bottom: 0; + } + `, + sectionTitle: css` + margin: 0 0 18px; + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 600; + line-height: 22px; + `, + sectionIcon: css` + flex: 0 0 auto; + color: ${token.colorPrimary}; + `, + fieldGrid: css` + width: 100%; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 18px; + + :global(.ant-form-item) { + min-width: 0; + margin-bottom: 18px; + } + + :global(.ant-select), + :global(.ant-input-number-group-wrapper), + :global(.ant-input-number) { + width: 100%; + } + + @container (max-width: 520px) { + grid-template-columns: minmax(0, 1fr); + } + `, + fullWidthField: css` + grid-column: 1 / -1; `, editorWrapper: css` /* max-height: calc(100vh - 120px); */ /* max-height: calc(100vh - 60px); */ /* border: 1px solid ${token.colorBorderSecondary}; */ /* padding-top: 8px; */ + position: sticky; + top: 24px; flex: 1; + min-width: 0; + height: clamp(420px, calc(100vh - 190px), 720px); + overflow: hidden; + + @container (max-width: 960px) { + position: static; + width: 100%; + height: 420px; + min-height: 320px; + flex: none; + } `, }; }); diff --git a/chat2db-community-client/src/blocks/Setting/License/index.tsx b/chat2db-community-client/src/blocks/Setting/License/index.tsx index de12a59fbe..d9c62f145e 100644 --- a/chat2db-community-client/src/blocks/Setting/License/index.tsx +++ b/chat2db-community-client/src/blocks/Setting/License/index.tsx @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import { useStyles } from './style'; import i18n from '@/i18n'; -import SettingSubsection from '../SettingSubsection'; import { Flex, Button } from 'antd'; import { useUserStore } from '@/store/user'; import { useGlobalStore } from '@/store/global'; @@ -150,7 +149,6 @@ const License = () => { return (
-
{i18n('setting.license.title')}
diff --git a/chat2db-community-client/src/blocks/Setting/License/style.ts b/chat2db-community-client/src/blocks/Setting/License/style.ts index dd7c7594b7..81b29cf5be 100644 --- a/chat2db-community-client/src/blocks/Setting/License/style.ts +++ b/chat2db-community-client/src/blocks/Setting/License/style.ts @@ -3,6 +3,7 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles(({ css, token }) => { return { wrapper: css` + padding-top: 24px; display: flex; flex-direction: column; gap: 16px; diff --git a/chat2db-community-client/src/blocks/Setting/McpSetting/index.tsx b/chat2db-community-client/src/blocks/Setting/McpSetting/index.tsx index a1cdba0231..2c33bcb5b1 100644 --- a/chat2db-community-client/src/blocks/Setting/McpSetting/index.tsx +++ b/chat2db-community-client/src/blocks/Setting/McpSetting/index.tsx @@ -55,17 +55,19 @@ export default function McpSetting() { return (
-
- +
- +
-
- +
+ {i18n('setting.title.mcpToken')}} + describe={i18n('setting.text.mcpTokenDescribe')} + />
- +
- - {i18n('setting.title.networkProxy')} - - - } - describe={i18n('setting.text.networkProxyDescribe')} - /> - + + {i18n('setting.networkProxy.mode')} + + + } + className={styles.modeItem} + > )} - + {i18n('setting.networkProxy.testUrl')} + } + > diff --git a/chat2db-community-client/src/blocks/Setting/NetworkProxySetting/style.ts b/chat2db-community-client/src/blocks/Setting/NetworkProxySetting/style.ts index 4cb3b1a383..f82b59fb70 100644 --- a/chat2db-community-client/src/blocks/Setting/NetworkProxySetting/style.ts +++ b/chat2db-community-client/src/blocks/Setting/NetworkProxySetting/style.ts @@ -1,11 +1,6 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles(({ css, token }) => ({ - subsection: css` - :global(.ant-divider) { - display: none; - } - `, titleWithHelp: css` display: inline-flex; align-items: center; @@ -25,6 +20,7 @@ export const useStyles = createStyles(({ css, token }) => ({ modeGroup: css` display: flex; align-items: center; + flex-wrap: wrap; gap: 30px; :global(.ant-radio-wrapper) { @@ -41,9 +37,14 @@ export const useStyles = createStyles(({ css, token }) => ({ display: grid; grid-template-columns: minmax(0, 1fr) 160px; gap: 16px; + + @container (max-width: 480px) { + grid-template-columns: minmax(0, 1fr); + } `, actions: css` display: flex; + flex-wrap: wrap; gap: 12px; align-items: center; `, diff --git a/chat2db-community-client/src/blocks/Setting/Personal/index.tsx b/chat2db-community-client/src/blocks/Setting/Personal/index.tsx index 2d6db9e840..2789557e93 100644 --- a/chat2db-community-client/src/blocks/Setting/Personal/index.tsx +++ b/chat2db-community-client/src/blocks/Setting/Personal/index.tsx @@ -28,7 +28,7 @@ export default function Personal() {
diff --git a/chat2db-community-client/src/blocks/Setting/Personal/style.ts b/chat2db-community-client/src/blocks/Setting/Personal/style.ts index b21f758a1b..286d141ea3 100644 --- a/chat2db-community-client/src/blocks/Setting/Personal/style.ts +++ b/chat2db-community-client/src/blocks/Setting/Personal/style.ts @@ -3,6 +3,7 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles(({ css, token }) => { return { personalBox: css` + padding-top: 24px; display: flex; flex-direction: column; gap: 30px; diff --git a/chat2db-community-client/src/blocks/Setting/SearchTargetLabel.tsx b/chat2db-community-client/src/blocks/Setting/SearchTargetLabel.tsx new file mode 100644 index 0000000000..50d06635f1 --- /dev/null +++ b/chat2db-community-client/src/blocks/Setting/SearchTargetLabel.tsx @@ -0,0 +1,15 @@ +import type { ReactNode } from 'react'; + +interface SearchTargetLabelProps { + children: ReactNode; + className?: string; + targetId: string; +} + +export default function SearchTargetLabel({ children, className, targetId }: SearchTargetLabelProps) { + return ( + + {children} + + ); +} diff --git a/chat2db-community-client/src/blocks/Setting/SettingLayout.tsx b/chat2db-community-client/src/blocks/Setting/SettingLayout.tsx new file mode 100644 index 0000000000..86fe0be676 --- /dev/null +++ b/chat2db-community-client/src/blocks/Setting/SettingLayout.tsx @@ -0,0 +1,294 @@ +import i18n from '@/i18n'; +import { IconfontSvg, SearchBar } from '@chat2db/ui'; +import { X, type LucideIcon } from 'lucide-react'; +import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; +import { history } from 'umi'; +import { groupSettingMenuItems, type SettingNavigationGroupCode } from './navigation'; +import { + getSettingTargetScrollTop, + isSettingsSearchShortcut, + searchSettings, + type SettingSearchResult, +} from './search'; +import { getSettingSearchItems } from './searchCatalog'; +import { useStyles } from './style'; + +interface SettingMenuItemBase { + body: ReactNode; + code: string; + describe: string; + group: SettingNavigationGroupCode; + hidePageHeader?: boolean; + title: string; +} + +type SettingMenuIcon = + | { icon: LucideIcon; iconCode?: never } + | { icon?: never; iconCode: string }; + +export type SettingMenuItem = SettingMenuItemBase & SettingMenuIcon; + +interface SettingLayoutProps { + activeTab: string | false; + menus: SettingMenuItem[]; + onActiveTabChange: (tab: string | false) => void; +} + +type I18nKey = Parameters[0]; + +interface SearchBarHandle { + blur: () => void; + focus: () => void; +} + +const groupTitleKeys: Record = { + general: 'setting.nav.group.general', + services: 'setting.nav.group.services', + account: 'setting.nav.group.account', + information: 'setting.nav.group.information', +}; + +export default function SettingLayout({ activeTab, menus, onActiveTabChange }: SettingLayoutProps) { + const { styles, cx } = useStyles(); + const settingBoxRef = useRef(null); + const menuContentRef = useRef(null); + const searchBarRef = useRef(null); + const highlightedSearchTargetRef = useRef(null); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedSearchResultKey, setSelectedSearchResultKey] = useState(null); + const [pendingSearchTarget, setPendingSearchTarget] = useState<{ + menuCode: string; + targetId: string; + } | null>(null); + const menuGroups = groupSettingMenuItems(menus); + const activeMenu = menus.find((item) => item.code === activeTab) ?? menus[0]; + const searchableMenus = useMemo( + () => menus.map((menu) => ({ ...menu, searchItems: getSettingSearchItems(menu.code) })), + [menus], + ); + const searchResults = useMemo(() => searchSettings(searchableMenus, searchQuery), [searchQuery, searchableMenus]); + const isSearching = !!searchQuery.trim(); + + useEffect(() => { + const focusSettingsSearch = (event: KeyboardEvent) => { + if (!isSettingsSearchShortcut(event)) { + return; + } + event.preventDefault(); + event.stopPropagation(); + searchBarRef.current?.focus(); + }; + + document.addEventListener('keydown', focusSettingsSearch, true); + return () => document.removeEventListener('keydown', focusSettingsSearch, true); + }, []); + + useEffect( + () => () => { + highlightedSearchTargetRef.current?.removeAttribute('data-setting-search-highlighted'); + }, + [], + ); + + useEffect(() => { + if (!pendingSearchTarget || activeTab !== pendingSearchTarget.menuCode) { + return; + } + + let targetFrame = 0; + const pageFrame = window.requestAnimationFrame(() => { + targetFrame = window.requestAnimationFrame(() => { + const target = settingBoxRef.current?.querySelector( + `[data-setting-search-id="${pendingSearchTarget.targetId}"]`, + ); + if (target) { + if (target.dataset.settingSearchExpandable === 'true' && target.getAttribute('aria-expanded') === 'false') { + target.click(); + } + highlightedSearchTargetRef.current?.removeAttribute('data-setting-search-highlighted'); + target.setAttribute('data-setting-search-highlighted', 'true'); + highlightedSearchTargetRef.current = target; + const menuContent = menuContentRef.current; + if (menuContent) { + const menuContentRect = menuContent.getBoundingClientRect(); + const targetRect = target.getBoundingClientRect(); + menuContent.scrollTo({ + behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth', + top: getSettingTargetScrollTop({ + containerHeight: menuContent.clientHeight, + containerTop: menuContentRect.top, + scrollTop: menuContent.scrollTop, + targetHeight: targetRect.height, + targetTop: targetRect.top, + }), + }); + } + } + setPendingSearchTarget(null); + }); + }); + + return () => { + window.cancelAnimationFrame(pageFrame); + if (targetFrame) { + window.cancelAnimationFrame(targetFrame); + } + }; + }, [activeTab, pendingSearchTarget]); + + function closeSettings() { + const pathName = window.location.pathname.split('/')[1]; + onActiveTabChange(false); + if (pathName === 'settings') { + history.push('/'); + } + } + + function openSearchResult(result: SettingSearchResult) { + highlightedSearchTargetRef.current?.removeAttribute('data-setting-search-highlighted'); + highlightedSearchTargetRef.current = null; + setSelectedSearchResultKey(result.key); + onActiveTabChange(result.menuCode); + setPendingSearchTarget( + result.targetId + ? { + menuCode: result.menuCode, + targetId: result.targetId, + } + : null, + ); + } + + function updateSearchQuery(value: string) { + highlightedSearchTargetRef.current?.removeAttribute('data-setting-search-highlighted'); + highlightedSearchTargetRef.current = null; + setPendingSearchTarget(null); + setSelectedSearchResultKey(null); + setSearchQuery(value); + } + + return ( +
+
+
{i18n('setting.title.setting')}
+ +
+
+ +
+
+ {activeMenu?.hidePageHeader ? null : ( +
+

+ {activeMenu?.title} +

+ {activeMenu?.describe ?

{activeMenu.describe}

: null} +
+ )} +
{activeMenu?.body}
+
+
+
+
+ ); +} + +function SettingMenuIcon({ className, item }: { className: string; item: SettingMenuItem }) { + if (item.iconCode) { + return