'use client'; /* * The ONLY React subtree subscribed to useColorScheme(). When the user picks a mode: * 1. setMode(...) writes MUI's mode * 2. the resolved scheme lands on * 3. CSS custom properties resolve to new values → browser repaints * 4. React re-renders ONLY this component (which segment reads as selected) * Nothing above it in the tree is touched. */ import { FunctionComponent } from 'react'; import { Stack, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; import { useColorScheme } from '@mui/material/styles'; import { useTranslations } from 'next-intl'; import AppIcon from '@/components/common/AppIcon'; type ThemeMode = 'light' | 'dark' | 'system'; const MODES: ReadonlyArray<{ mode: ThemeMode; icon: string; key: string }> = [ { mode: 'light', icon: 'light_mode', key: 'theme_light' }, { mode: 'dark', icon: 'dark_mode', key: 'theme_dark' }, { mode: 'system', icon: 'system_mode', key: 'theme_system' }, ]; /** * The app's one appearance control: a three-way segmented choice — روشن / تیره / سیستم. * * It replaced an on/off `Switch`, which was wrong twice over. A switch is a **boolean**, so it * could not express "follow my phone" — yet `system` is the app's actual default (`ThemeProvider` * `defaultMode`), which left the control silently lying about the current state on a first visit. * And a lone switch beside a label reads like a feature being enabled rather than a look being * chosen. Three labelled segments say what each option does and which one is live. * * This is the one place `mode` is the right source rather than `colorScheme`: `mode` is the user's * **choice** (and can be `'system'`), `colorScheme` is only the resolved result. The `?? 'system'` * fallback is hydration-safe — MUI reports `mode` as `undefined` until it mounts, and the server, * the first client render, and the pre-mount state all agree on the same default. * @component ThemeModeSetting */ const ThemeModeSetting: FunctionComponent = () => { const { mode, setMode } = useColorScheme(); const t = useTranslations('common'); return ( { // `exclusive` emits null when the active segment is tapped again — keep the choice. if (next) setMode(next); }} aria-label={t('appearance')} sx={{ gap: 1, // Each segment is already individually rounded, so MUI's default treatment — segments // butted edge to edge with the shared border collapsed via a negative inline margin — put // two rounded corners flush against each other with no separation at all. Spacing them // makes each one read as its own choice; the negative margin has to be undone explicitly, // otherwise the gap is eaten by it. '& .MuiToggleButtonGroup-grouped': { marginInlineStart: 0, gap: 0.75, paddingBlock: 0.75, border: '1px solid', borderColor: 'divider', borderRadius: 'var(--bal-radius-sm)', }, }} > {MODES.map((option) => ( ))} ); }; export default ThemeModeSetting;