Mobile: Toolbar: Show only half of last button to indicate scroll (#11772)

pull/11795/head^2
Henry Heino 2025-02-06 09:56:56 -08:00 committed by GitHub
parent 8312196faa
commit cc09f92d3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 13 deletions

View File

@ -619,6 +619,7 @@ packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromSta
packages/app-mobile/components/EditorToolbar/utils/isSelected.js
packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.js
packages/app-mobile/components/EditorToolbar/utils/toolbarButtonsFromState.js
packages/app-mobile/components/EditorToolbar/utils/useButtonSize.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js

1
.gitignore vendored
View File

@ -594,6 +594,7 @@ packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromSta
packages/app-mobile/components/EditorToolbar/utils/isSelected.js
packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.js
packages/app-mobile/components/EditorToolbar/utils/toolbarButtonsFromState.js
packages/app-mobile/components/EditorToolbar/utils/useButtonSize.js
packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { AppState } from '../../utils/types';
import { connect } from 'react-redux';
import { ScrollView, StyleSheet, View } from 'react-native';
import { LayoutChangeEvent, ScrollView, StyleSheet, View } from 'react-native';
import { ToolbarButtonInfo, ToolbarItem } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import toolbarButtonsFromState from './utils/toolbarButtonsFromState';
import { useCallback, useMemo, useRef, useState } from 'react';
@ -12,6 +12,7 @@ import { EditorState } from './types';
import ToolbarButton from './ToolbarButton';
import isSelected from './utils/isSelected';
import { _ } from '@joplin/lib/locale';
import useButtonSize from './utils/useButtonSize';
interface Props {
themeId: number;
@ -19,6 +20,33 @@ interface Props {
editorState: EditorState;
}
const useButtonPadding = (buttonCount: number) => {
const { buttonSize } = useButtonSize();
const [containerWidth, setContainerWidth] = useState(0);
const onContainerLayout = useCallback((event: LayoutChangeEvent) => {
setContainerWidth(event.nativeEvent.layout.width);
}, []);
const buttonPadding = useMemo(() => {
if (buttonCount <= 1) return 0;
// Number of offscreen buttons -- round up because we can't have negative padding
const overflowButtonCount = Math.max(0, Math.ceil((buttonCount * buttonSize - containerWidth) / buttonSize));
const visibleButtonCount = buttonCount - overflowButtonCount;
const allVisible = visibleButtonCount === buttonCount;
if (allVisible) return 0;
const targetContentWidth = containerWidth + buttonSize / 2;
const actualContentWidth = visibleButtonCount * buttonSize;
const widthDifference = targetContentWidth - actualContentWidth;
// Only half of the gap for the rightmost visible button is on the screen
const gapCount = visibleButtonCount - 1 / 2;
return widthDifference / gapCount;
}, [containerWidth, buttonCount, buttonSize]);
return { onContainerLayout, buttonPadding };
};
const useStyles = (themeId: number) => {
return useMemo(() => {
const theme = themeStyle(themeId);
@ -53,7 +81,6 @@ const useSettingButtonInfo = (setSettingsVisible: SetSettingsVisible) => {
};
const EditorToolbar: React.FC<Props> = props => {
const styles = useStyles(props.themeId);
const buttonInfos: ToolbarButtonInfo[] = [];
@ -63,11 +90,17 @@ const EditorToolbar: React.FC<Props> = props => {
}
}
// Include setting button in the count
const buttonCount = buttonInfos.length + 1;
const { buttonPadding, onContainerLayout } = useButtonPadding(buttonCount);
const styles = useStyles(props.themeId);
const renderButton = (info: ToolbarButtonInfo) => {
return <ToolbarButton
key={`command-${info.name}`}
buttonInfo={info}
themeId={props.themeId}
extraPadding={buttonPadding}
selected={isSelected(info.name, props.editorState)}
/>;
};
@ -89,6 +122,7 @@ const EditorToolbar: React.FC<Props> = props => {
const settingsButtonInfo = useSettingButtonInfo(setSettingsVisible);
const settingsButton = <ToolbarButton
buttonInfo={settingsButtonInfo}
extraPadding={buttonPadding}
themeId={props.themeId}
/>;
@ -99,6 +133,7 @@ const EditorToolbar: React.FC<Props> = props => {
horizontal={true}
style={styles.content}
contentContainerStyle={styles.contentContainer}
onLayout={onContainerLayout}
>
{buttonInfos.map(renderButton)}
<View style={styles.spacer}/>

View File

@ -2,41 +2,41 @@ import * as React from 'react';
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import IconButton from '../IconButton';
import { memo, useMemo } from 'react';
import { StyleSheet, useWindowDimensions } from 'react-native';
import { StyleSheet } from 'react-native';
import { themeStyle } from '../global-style';
import useButtonSize from './utils/useButtonSize';
interface Props {
themeId: number;
extraPadding: number;
buttonInfo: ToolbarButtonInfo;
selected?: boolean;
}
const useStyles = (themeId: number, selected: boolean, enabled: boolean) => {
const { fontScale } = useWindowDimensions();
const useStyles = (themeId: number, selected: boolean, enabled: boolean, extraPadding: number) => {
const { buttonSize, iconSize } = useButtonSize();
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
icon: {
color: theme.color,
fontSize: 22 * fontScale,
fontSize: iconSize,
},
button: {
// Scaling the button width/height by the device font scale causes the button to scale
// with the user's device font size.
width: 48 * fontScale,
height: 48 * fontScale,
width: buttonSize + extraPadding,
height: buttonSize,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: selected ? theme.backgroundColorHover3 : theme.backgroundColor3,
opacity: enabled ? 1 : theme.disabledOpacity,
},
});
}, [themeId, selected, enabled, fontScale]);
}, [themeId, selected, enabled, buttonSize, iconSize, extraPadding]);
};
const ToolbarButton: React.FC<Props> = memo(({ themeId, buttonInfo, selected }) => {
const styles = useStyles(themeId, selected, buttonInfo.enabled);
const ToolbarButton: React.FC<Props> = memo(({ themeId, buttonInfo, selected, extraPadding }) => {
const styles = useStyles(themeId, selected, buttonInfo.enabled, extraPadding);
const isToggleButton = selected !== undefined;
return <IconButton

View File

@ -0,0 +1,17 @@
import { useMemo } from 'react';
import { useWindowDimensions } from 'react-native';
const useButtonSize = () => {
const { fontScale } = useWindowDimensions();
return useMemo(() => {
return {
// Scaling the button width/height by the device font scale causes the button to scale
// with the user's device font size.
buttonSize: 48 * fontScale,
iconSize: 22 * fontScale,
};
}, [fontScale]);
};
export default useButtonSize;