diff --git a/src/components/settings/settings-section.tsx b/src/components/settings/settings-section.tsx index 39a7dab4..1c43d8f9 100644 --- a/src/components/settings/settings-section.tsx +++ b/src/components/settings/settings-section.tsx @@ -7,6 +7,7 @@ import { Modal, ScrollView, ViewStyle, + type LayoutChangeEvent, } from 'react-native'; import { FlaskConical, Lock, ChevronDown, ChevronRight, Check, ExternalLink, type AppIcon } from '../icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; @@ -68,6 +69,9 @@ export function SettingsSection({ ); } +/** Narrowest label column before a row's control moves under the label. */ +const STACK_BELOW = 140; + // The label of the SettingItem a control sits in. A switch has no text of // its own, so without it a screen reader only announces "switch, off". const SettingLabelContext = React.createContext(undefined); @@ -95,10 +99,27 @@ export function SettingItem({ const c = useColors(); const styles = React.useMemo(() => makeStyles(c), [c]); const highlight = useSearchHighlight(label); - const rowStyle = [styles.settingItem, !noBorder && styles.settingItemBorder, locked && styles.locked, highlight.highlighted && styles.searchHighlight]; + // The control sits on the right. When that leaves the label column narrower + // than STACK_BELOW (a long translation, two buttons), the control moves + // under the label, as the webmail stacks it on a phone. A new row width + // (rotation, split view) measures again. + const [stacked, setStacked] = useState(false); + const rowWidth = React.useRef(0); + const onRowLayout = (e: LayoutChangeEvent) => { + highlight.onLayout(e); + const width = e.nativeEvent.layout.width; + if (Math.abs(width - rowWidth.current) > 1) { + rowWidth.current = width; + setStacked(false); + } + }; + const onContentLayout = (e: LayoutChangeEvent) => { + if (!stacked && children && e.nativeEvent.layout.width < STACK_BELOW) setStacked(true); + }; + const rowStyle = [styles.settingItem, stacked && styles.settingItemStacked, !noBorder && styles.settingItemBorder, locked && styles.locked, highlight.highlighted && styles.searchHighlight]; const body = ( <> - + {label} {locked && } @@ -119,7 +140,7 @@ export function SettingItem({ return ( [...rowStyle, pressed && styles.settingItemPressed]} @@ -129,7 +150,7 @@ export function SettingItem({ ); } return ( - + {body} ); @@ -366,6 +387,16 @@ function makeStyles(c: ThemePalette) { settingContent: { flex: 1, }, + // The control under the label instead of beside it (see SettingItem). + settingItemStacked: { + flexDirection: 'column', + alignItems: 'flex-start', + gap: spacing.sm, + }, + settingContentStacked: { + flex: 0, + alignSelf: 'stretch', + }, settingLabelRow: { flexDirection: 'row', alignItems: 'center',