Mobile: Implement redesigned screen header (#16345)

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
Henry HeinoandLaurent Cozic authored and GitHub committed 2026-10-08 12:03:38 +01:00
1 parent 00d62e8700
commit b0affc0a30
8 files changed
+65 -42

No files matched your search

@@ -5,7 +5,7 @@ import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
import { themeStyle } from './global-style';
import Dropdown, { DropdownListItem, OnValueChangedListener } from './Dropdown';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { View } from 'react-native';
import { TextStyle, View } from 'react-native';
import { Button } from 'react-native-paper';
import { DialogContext } from './DialogManager';
@@ -18,6 +18,7 @@ interface FolderPickerProps {
placeholder?: string;
darkText?: boolean;
themeId?: number;
headerStyle?: TextStyle;
coverableChildrenRight?: ReactElement|ReactElement[];
onNewFolder?: (title: string)=> void;
}
@@ -30,6 +31,7 @@ const FolderPicker: FunctionComponent<FolderPickerProps> = ({
mustSelect,
folders,
placeholder,
headerStyle,
darkText,
coverableChildrenRight,
onNewFolder,
@@ -86,9 +88,11 @@ const FolderPicker: FunctionComponent<FolderPickerProps> = ({
backgroundColor: theme.backgroundColor,
}}
headerStyle={{
color: darkText ? theme.colorFaded : theme.colorBright2,
color: darkText ? theme.color : theme.colorFaded,
fontWeight: darkText ? 'bold' : 'normal',
fontSize: theme.fontSize,
opacity: disabled ? theme.disabledOpacity : 1,
...headerStyle,
}}
itemStyle={{
color: theme.color,
@@ -114,22 +114,21 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
innerContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.backgroundColor2,
shadowColor: '#000000',
elevation: 5,
backgroundColor: theme.backgroundColor,
borderBottomColor: theme.dividerColor,
borderBottomWidth: 1,
// 2026-08-28: Including a fully transparent shadow avoids a crash on iOS when opening settings:
shadowColor: '#000',
shadowOpacity: 0,
},
// A small border above the header: Covers the part of the shadow that would otherwise
// be shown above the header on Android.
aboveHeader: {
backgroundColor: theme.backgroundColor2,
paddingBottom: 6,
marginTop: -6,
zIndex: 2,
folderPicker: {
marginRight: theme.marginMedium,
fontSize: theme.fontSizeLarger,
},
sideMenuButton: {
flex: 1,
alignItems: 'center',
backgroundColor: theme.backgroundColor2,
backgroundColor: theme.backgroundColor,
paddingLeft: theme.marginLeft,
paddingRight: 5,
marginRight: 2,
@@ -138,7 +137,7 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
},
iconButton: {
flex: 1,
backgroundColor: theme.backgroundColor2,
backgroundColor: theme.backgroundColor,
paddingLeft: 10,
paddingRight: 10,
paddingTop: PADDING_V,
@@ -153,61 +152,60 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
minHeight: 40,
borderWidth: 1,
borderColor: theme.colorBright2,
borderColor: theme.color,
borderRadius: 4,
marginRight: 8,
},
saveButtonText: {
textAlignVertical: 'center',
color: theme.colorBright2,
color: theme.color,
fontWeight: 'bold',
},
savedButtonIcon: {
...theme.icon,
fontSize: 20,
color: theme.colorBright2,
width: 18,
height: 18,
},
saveButtonIcon: {
...theme.icon,
fontSize: 25,
color: theme.colorBright2,
},
contextMenuTrigger: {
fontSize: 30,
paddingLeft: 5,
paddingRight: theme.marginRight,
color: theme.color2,
color: theme.color,
fontWeight: 'bold',
},
titleText: {
flex: 1,
textAlignVertical: 'center',
marginLeft: 10,
color: theme.colorBright2,
color: theme.color,
fontWeight: 'bold',
fontSize: theme.fontSize,
fontSize: theme.fontSizeLarger,
paddingTop: 15,
paddingBottom: 15,
},
viewToggleButton: {
flex: 1,
backgroundColor: theme.backgroundColor2,
paddingLeft: 22,
backgroundColor: theme.backgroundColor,
paddingLeft: 10,
paddingRight: 10,
paddingTop: PADDING_V,
paddingBottom: PADDING_V,
},
viewToggleIcon: {
fontSize: 27,
color: theme.colorBright2,
color: theme.color,
flex: 1,
textAlignVertical: 'center',
},
};
const topIcon: TextStyle = { ...theme.icon, flex: 1, textAlignVertical: 'center', color: theme.colorBright2 };
const topIcon: TextStyle = { ...theme.icon, flex: 1, textAlignVertical: 'center', color: theme.color };
styleObject.topIcon = topIcon;
styleObject.backButton = { ...styleObject.iconButton };
@@ -553,7 +551,7 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
themeId={themeId}
description={_('Sort notes by')}
iconName='ionicon filter-outline'
iconName='ionicon filter'
contentWrapperStyle={styles.iconButton}
iconStyle={styles.topIcon}
/>
@@ -589,7 +587,9 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
<FolderPicker
themeId={themeId}
disabled={disabled}
headerStyle={this.styles().folderPicker}
selectedFolderId={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null}
darkText
onValueChange={async (folderId) => {
// If onValueChange is specified, use this as a callback, otherwise do the default
// which is to take the selectedNoteIds from the state and move them to the
@@ -709,7 +709,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
return (
<View style={this.styles().outerContainer}>
<View style={this.styles().aboveHeader}/>
<View style={this.styles().innerContainer}>
{zeroWidthSpacer}
{sideMenuComp}
@@ -185,7 +185,6 @@ const NotePreview: React.FC<Props> = ({
/>
<FolderPicker
themeId={themeId}
darkText
placeholder={_('Select notebook')}
folders={realFolders}
onValueChange={setSelectedFolderId}
@@ -552,14 +552,25 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
flexDirection: 'row',
flexBasis: 'auto',
paddingLeft: theme.marginLeft,
borderBottomColor: theme.dividerColor,
borderBottomWidth: 1,
paddingRight: theme.marginRight,
maxHeight: '40%',
// Prevents the title/content divider overlaying the title's focus indicator on web:
zIndex: 1,
};
styles.titleContainerTodo = { ...styles.titleContainer };
styles.titleContainerTodo.paddingLeft = 0;
styles.titleDivider = {
flex: 0,
height: 0,
marginLeft: theme.marginLeft,
marginRight: theme.marginRight,
borderBottomColor: theme.dividerColor,
borderBottomWidth: 1,
};
styles.titleTextInput = {
flex: 1,
marginTop: 0,
@@ -567,8 +578,8 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
color: theme.color,
fontWeight: 'bold',
fontSize: theme.fontSize,
paddingTop: theme.itemMarginTop, // Added for iOS (Not needed for Android??)
paddingBottom: theme.itemMarginBottom, // Added for iOS (Not needed for Android??)
paddingTop: theme.marginTop,
paddingBottom: theme.marginBottom,
};
this.styles_[cacheKey] = StyleSheet.create(styles);
@@ -2003,7 +2014,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
style={{ width: 30, height: 30, alignSelf: 'center' }}
/>;
const titleComp = (
const titleComp = <>
<View
style={titleContainerStyle}
onLayout={(e) => {
@@ -2046,7 +2057,8 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
/>
{ titleToggleButton }
</View>
);
<View style={this.styles().titleDivider}/>
</>;
const noteTagDialog = !this.state.noteTagDialogShown ? null : <NoteTagsDialog onCloseRequested={this.noteTagDialog_closeRequested} />;
@@ -126,7 +126,6 @@ class FolderScreenComponent extends BaseScreenComponent<Props, State> {
selectedFolderId={this.state.folder.parent_id}
onValueChange={newValue => this.parent_changeValue(newValue)}
mustSelect
darkText
/>
</View>
<View style={{ flex: 1 }} />
@@ -133,6 +133,12 @@ const useSource = (props: UseSourceProps) => {
${rendererCss}
${markdownEditorCss}
body.rich-text-editor {
/* Zero the body padding to align the editor content with the note title */
padding-left: 0;
padding-right: 0;
}
/* Increase the size of the editor to make it easier to focus the editor. */
.prosemirror-editor {
min-height: 75vh;
@@ -143,6 +149,8 @@ const useSource = (props: UseSourceProps) => {
${rendererJs}
${markdownEditorJs}
document.body.classList.add('rich-text-editor');
if (!window.richTextEditorCreated) {
window.richTextEditorCreated = true;
${shim.injectedJs('richTextEditorBundle')}
+2 -2
View File
@@ -781,7 +781,7 @@ class AppComponent extends React.Component<AppComponentProps, AppComponentState>
// const statusBarStyle = theme.appearance === 'light-content';
const statusBarStyle = 'light-content';
const statusBarStyle = theme.appearance === ThemeAppearance.Light ? 'dark-content' : 'light-content';
const shouldShowMainContent = !biometricsEnabled(this.state.sensorInfo) || this.props.biometricsDone;
@@ -793,7 +793,7 @@ class AppComponent extends React.Component<AppComponentProps, AppComponentState>
const mainContent = (
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: '100%' }}>
<SafeAreaView style={{ flex: 1 }} titleBarUnderlayColor={theme.backgroundColor2}>
<SafeAreaView style={{ flex: 1 }} titleBarUnderlayColor={theme.backgroundColor}>
<SideMenu
menu={sideMenuContent}
toleranceX={4}
+7 -5
View File
@@ -89,6 +89,7 @@ const createTheme = (theme: EditorTheme): Extension[] => {
paddingBottom: '0.2em',
};
const linePaddingLeft = 1;
const codeMirrorTheme = EditorView.theme({
// Include &.CodeMirror to handle the case where additional CodeMirror 5 styles
// need to be overridden.
@@ -104,7 +105,8 @@ const createTheme = (theme: EditorTheme): Extension[] => {
fontFamily: theme.fontFamily,
...baseContentStyle,
paddingBottom: `${theme.paddingBottom}px`,
marginLeft: `${theme.marginLeft}px`,
marginLeft: `${Math.max(theme.marginLeft - linePaddingLeft, 0)}px`,
marginRight: `${theme.marginRight}px`,
},
@@ -172,12 +174,12 @@ const createTheme = (theme: EditorTheme): Extension[] => {
marginRight: 'auto',
} : undefined,
// Allows editor content to be left-aligned with the toolbar on desktop.
// Allows editor content to be left-aligned with the toolbar.
// See https://github.com/laurent22/joplin/issues/11279
[`${editorNoGuttersSelector} .cm-line`]: theme.isDesktop ? {
[`${editorNoGuttersSelector} .cm-line`]: {
// Note: This cannot be zero:
paddingLeft: '1px',
} : undefined,
paddingLeft: `${linePaddingLeft}px`,
},
// Override the default URL style when the URL is within a link
'& .tok-url.tok-link, & .tok-link.tok-meta, & .tok-link.tok-string': {