Repository navigation
[codex] Add full sidebar visibility controls #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,17 +6,35 @@ import { useTheme } from "@/shared/hooks/useTheme"; | |||||
| import useThemeStore, { COLOR_THEMES } from "@/store/themeStore"; | ||||||
| import { cn } from "@/shared/utils/cn"; | ||||||
| import { useTranslations } from "next-intl"; | ||||||
| import { | ||||||
| HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, | ||||||
| SIDEBAR_SECTIONS, | ||||||
| SIDEBAR_SETTINGS_UPDATED_EVENT, | ||||||
| normalizeHiddenSidebarItems, | ||||||
| type HideableSidebarItemId, | ||||||
| } from "@/shared/constants/sidebarVisibility"; | ||||||
|
|
||||||
| export default function AppearanceTab() { | ||||||
| const { theme, setTheme, isDark } = useTheme(); | ||||||
| const { colorTheme, customColor, setColorTheme, setCustomColorTheme } = useThemeStore(); | ||||||
| const t = useTranslations("settings"); | ||||||
| const tSidebar = useTranslations("sidebar"); | ||||||
| const [settings, setSettings] = useState<Record<string, any>>({}); | ||||||
| const [loading, setLoading] = useState(true); | ||||||
| const [customThemeColor, setCustomThemeColor] = useState(customColor || "#3b82f6"); | ||||||
| const isValidHex = /^#([0-9a-fA-F]{6})$/.test(customThemeColor.startsWith("#") ? customThemeColor : `#${customThemeColor}`); | ||||||
| const isValidHex = /^#([0-9a-fA-F]{6})$/.test( | ||||||
| customThemeColor.startsWith("#") ? customThemeColor : `#${customThemeColor}` | ||||||
| ); | ||||||
| const hiddenSidebarItems = normalizeHiddenSidebarItems( | ||||||
| settings[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY] | ||||||
| ); | ||||||
| const hiddenSidebarSet = new Set(hiddenSidebarItems); | ||||||
|
|
||||||
| const getSettingsLabel = (key: string, fallback: string) => | ||||||
| typeof t.has === "function" && t.has(key) ? t(key) : fallback; | ||||||
| const getSidebarLabel = (key: string, fallback: string) => | ||||||
| typeof tSidebar.has === "function" && tSidebar.has(key) ? tSidebar(key) : fallback; | ||||||
|
|
||||||
| // Subscribe to store changes (e.g. from another tab) via Zustand external subscription | ||||||
| useEffect(() => { | ||||||
| const unsubscribe = useThemeStore.subscribe((state) => { | ||||||
| if (state.customColor && state.customColor !== customThemeColor) { | ||||||
|
|
@@ -25,6 +43,7 @@ export default function AppearanceTab() { | |||||
| }); | ||||||
| return unsubscribe; | ||||||
| }, []); // eslint-disable-line react-hooks/exhaustive-deps | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||
|
|
||||||
| const themeOptionLabels: Record<string, string> = { | ||||||
| light: t("themeLight"), | ||||||
| dark: t("themeDark"), | ||||||
|
|
@@ -40,7 +59,12 @@ export default function AppearanceTab() { | |||||
| return res.json(); | ||||||
| }) | ||||||
| .then((data) => { | ||||||
| setSettings(data); | ||||||
| setSettings({ | ||||||
| ...data, | ||||||
| [HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]: normalizeHiddenSidebarItems( | ||||||
| data[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY] | ||||||
| ), | ||||||
| }); | ||||||
| setLoading(false); | ||||||
| }) | ||||||
| .catch(() => setLoading(false)); | ||||||
|
|
@@ -54,7 +78,18 @@ export default function AppearanceTab() { | |||||
| body: JSON.stringify({ [key]: value }), | ||||||
| }); | ||||||
| if (res.ok) { | ||||||
| setSettings((prev) => ({ ...prev, [key]: value })); | ||||||
| setSettings((prev) => ({ | ||||||
| ...prev, | ||||||
| [key]: | ||||||
| key === HIDDEN_SIDEBAR_ITEMS_SETTING_KEY ? normalizeHiddenSidebarItems(value) : value, | ||||||
| })); | ||||||
| if (typeof window !== "undefined") { | ||||||
| window.dispatchEvent( | ||||||
| new CustomEvent(SIDEBAR_SETTINGS_UPDATED_EVENT, { | ||||||
| detail: { [key]: value }, | ||||||
| }) | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
| } catch (err) { | ||||||
| console.error(`Failed to update ${key}:`, err); | ||||||
|
|
@@ -71,6 +106,20 @@ export default function AppearanceTab() { | |||||
| { id: "cyan", color: COLOR_THEMES.cyan, label: t("themeCyan") }, | ||||||
| ]; | ||||||
|
|
||||||
| const sidebarSections = SIDEBAR_SECTIONS.map((section) => ({ | ||||||
| ...section, | ||||||
| title: getSidebarLabel(section.titleKey, section.titleFallback), | ||||||
| items: section.items.map((item) => ({ ...item, label: tSidebar(item.i18nKey) })), | ||||||
| })); | ||||||
|
|
||||||
| const toggleSidebarItem = (itemId: HideableSidebarItemId) => { | ||||||
| const nextHiddenItems = hiddenSidebarSet.has(itemId) | ||||||
| ? hiddenSidebarItems.filter((id) => id !== itemId) | ||||||
| : [...hiddenSidebarItems, itemId]; | ||||||
|
|
||||||
| updateSetting(HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, nextHiddenItems); | ||||||
| }; | ||||||
|
|
||||||
| return ( | ||||||
| <Card> | ||||||
| <div className="flex items-center gap-3 mb-4"> | ||||||
|
|
@@ -164,10 +213,61 @@ export default function AppearanceTab() { | |||||
| maxLength={7} | ||||||
| className={`flex-1 h-10 px-3 rounded-lg bg-surface border text-sm text-text-main focus:outline-none ${isValidHex ? "border-border focus:border-primary" : "border-red-400 focus:border-red-500"}`} | ||||||
| /> | ||||||
| <Button onClick={() => setCustomColorTheme(customThemeColor)} disabled={!isValidHex}>{t("themeCreate")}</Button> | ||||||
| <Button onClick={() => setCustomColorTheme(customThemeColor)} disabled={!isValidHex}> | ||||||
| {t("themeCreate")} | ||||||
| </Button> | ||||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| <div className="pt-4 border-t border-border"> | ||||||
| <div className="mb-3"> | ||||||
| <p className="font-medium"> | ||||||
| {getSettingsLabel("sidebarVisibility", "Hide sidebar items")} | ||||||
| </p> | ||||||
| <p className="text-sm text-text-muted"> | ||||||
| {getSettingsLabel( | ||||||
| "sidebarVisibilityDesc", | ||||||
| "Hide any sidebar navigation entry to reduce visual clutter without disabling any features" | ||||||
| )} | ||||||
| </p> | ||||||
| </div> | ||||||
|
|
||||||
| <div className="flex flex-col gap-4"> | ||||||
| {sidebarSections.map((section) => ( | ||||||
| <div key={section.id} className="rounded-lg border border-border bg-surface/40"> | ||||||
| <div className="px-4 py-3 border-b border-border/70"> | ||||||
| <p className="text-xs font-semibold uppercase tracking-wider text-text-muted/70"> | ||||||
| {section.title} | ||||||
| </p> | ||||||
| </div> | ||||||
|
|
||||||
| <div className="divide-y divide-border/70"> | ||||||
| {section.items.map((item) => ( | ||||||
| <div | ||||||
| key={item.id} | ||||||
| className="flex items-center justify-between gap-4 px-4 py-3" | ||||||
| > | ||||||
| <p className="font-medium">{item.label}</p> | ||||||
| <Toggle | ||||||
| checked={hiddenSidebarSet.has(item.id)} | ||||||
| onChange={() => toggleSidebarItem(item.id)} | ||||||
| disabled={loading} | ||||||
| /> | ||||||
| </div> | ||||||
| ))} | ||||||
| </div> | ||||||
| </div> | ||||||
| ))} | ||||||
| </div> | ||||||
|
|
||||||
| <p className="mt-3 text-xs text-text-muted"> | ||||||
| {getSettingsLabel( | ||||||
| "sidebarVisibilityHint", | ||||||
| "Any sidebar section is hidden automatically when all of its entries are hidden" | ||||||
| )} | ||||||
| </p> | ||||||
| </div> | ||||||
|
|
||||||
| <div className="pt-4 border-t border-border"> | ||||||
| <div className="flex items-center justify-between"> | ||||||
| <div> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -170,6 +170,11 @@ | |||||||
| "debug": "Debug", | ||||||||
| "system": "System", | ||||||||
| "help": "Help", | ||||||||
| "primarySection": "Main", | ||||||||
| "cliSection": "CLI", | ||||||||
| "debugSection": "Debug", | ||||||||
| "systemSection": "System", | ||||||||
| "helpSection": "Help", | ||||||||
| "serverDisconnected": "Server Disconnected", | ||||||||
| "serverDisconnectedMsg": "The proxy server has been stopped or is restarting.", | ||||||||
| "expandSidebar": "Expand sidebar", | ||||||||
|
|
@@ -1724,6 +1729,9 @@ | |||||||
| "themeLight": "Light", | ||||||||
| "themeDark": "Dark", | ||||||||
| "themeSystem": "System", | ||||||||
| "sidebarVisibility": "Hide sidebar items", | ||||||||
| "sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter without disabling any features", | ||||||||
| "sidebarVisibilityHint": "Any sidebar section is hidden automatically when all of its entries are hidden", | ||||||||
|
Comment on lines
+1732
to
+1734
|
||||||||
| "sidebarVisibility": "Hide sidebar items", | |
| "sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter without disabling any features", | |
| "sidebarVisibilityHint": "Any sidebar section is hidden automatically when all of its entries are hidden", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
getSettingsLabelandgetSidebarLabelhelper functions are re-created on every render. This can lead to unnecessary re-renders of child components if these functions are passed as props. It's a good practice to memoize them usinguseCallbackto improve performance.Remember to import
useCallbackfromreact.