mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Mobile: Accessibility: Add checked/unchecked accessibility information to the "sort notes by" dialog (#11411)
This commit is contained in:
parent
c9608cf4a1
commit
d648e43cfb
@ -588,6 +588,7 @@ packages/app-mobile/components/CameraView/types.js
|
||||
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
|
||||
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
|
||||
packages/app-mobile/components/Checkbox.js
|
||||
packages/app-mobile/components/DialogManager/PromptButton.js
|
||||
packages/app-mobile/components/DialogManager/PromptDialog.js
|
||||
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
|
||||
packages/app-mobile/components/DialogManager/index.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -564,6 +564,7 @@ packages/app-mobile/components/CameraView/types.js
|
||||
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
|
||||
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
|
||||
packages/app-mobile/components/Checkbox.js
|
||||
packages/app-mobile/components/DialogManager/PromptButton.js
|
||||
packages/app-mobile/components/DialogManager/PromptDialog.js
|
||||
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
|
||||
packages/app-mobile/components/DialogManager/index.js
|
||||
|
@ -0,0 +1,83 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { StyleSheet, TextStyle, View } from 'react-native';
|
||||
import { TouchableRipple, Text } from 'react-native-paper';
|
||||
import { PromptButtonSpec } from './types';
|
||||
import { ThemeStyle, themeStyle } from '../global-style';
|
||||
import Icon from '../Icon';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
buttonSpec: PromptButtonSpec;
|
||||
}
|
||||
|
||||
const useStyles = (theme: ThemeStyle) => {
|
||||
return useMemo(() => {
|
||||
const buttonText: TextStyle = {
|
||||
color: theme.color4,
|
||||
textAlign: 'center',
|
||||
};
|
||||
|
||||
return StyleSheet.create({
|
||||
buttonContainer: {
|
||||
// This applies the borderRadius to the TouchableRipple's parent, which
|
||||
// seems necessary on Android.
|
||||
borderRadius: theme.borderRadius,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
button: {
|
||||
borderRadius: theme.borderRadius,
|
||||
padding: 10,
|
||||
},
|
||||
buttonContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
buttonText,
|
||||
icon: {
|
||||
...buttonText,
|
||||
marginRight: 8,
|
||||
},
|
||||
});
|
||||
}, [theme]);
|
||||
};
|
||||
|
||||
const PromptButton: React.FC<Props> = props => {
|
||||
const theme = themeStyle(props.themeId);
|
||||
const styles = useStyles(theme);
|
||||
|
||||
const { checked, text, iconChecked, onPress } = props.buttonSpec;
|
||||
|
||||
const isCheckbox = (checked ?? null) !== null;
|
||||
const icon = checked ? (
|
||||
<>
|
||||
<Icon
|
||||
accessibilityLabel={null}
|
||||
style={styles.icon}
|
||||
name={iconChecked ?? 'fas fa-check'}
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableRipple
|
||||
onPress={onPress}
|
||||
style={styles.button}
|
||||
rippleColor={theme.backgroundColorHover4}
|
||||
accessibilityRole={isCheckbox ? 'checkbox' : 'button'}
|
||||
accessibilityState={isCheckbox ? { checked } : null}
|
||||
aria-checked={isCheckbox ? checked : undefined}
|
||||
>
|
||||
<View style={styles.buttonContent}>
|
||||
{icon}
|
||||
<Text style={styles.buttonText}>{text}</Text>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptButton;
|
@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { Button, Dialog, Divider, Surface, Text } from 'react-native-paper';
|
||||
import { Dialog, Divider, Surface, Text } from 'react-native-paper';
|
||||
import { DialogType, PromptDialogData } from './types';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { useMemo } from 'react';
|
||||
import { themeStyle } from '../global-style';
|
||||
import PromptButton from './PromptButton';
|
||||
|
||||
interface Props {
|
||||
dialog: PromptDialogData;
|
||||
@ -17,18 +18,12 @@ const useStyles = (themeId: number, isMenu: boolean) => {
|
||||
return StyleSheet.create({
|
||||
dialogContainer: {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
borderRadius: 24,
|
||||
paddingTop: 24,
|
||||
borderRadius: theme.borderRadius,
|
||||
paddingTop: theme.borderRadius,
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
},
|
||||
|
||||
buttonScrollerContent: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
|
||||
dialogContent: {
|
||||
paddingBottom: 14,
|
||||
},
|
||||
@ -53,12 +48,11 @@ const PromptDialog: React.FC<Props> = ({ dialog, themeId }) => {
|
||||
const styles = useStyles(themeId, isMenu);
|
||||
|
||||
const buttons = dialog.buttons.map((button, index) => {
|
||||
return (
|
||||
<Button
|
||||
key={`${index}-${button.text}`}
|
||||
onPress={button.onPress}
|
||||
>{button.text}</Button>
|
||||
);
|
||||
return <PromptButton
|
||||
key={`${index}-${button.text}`}
|
||||
buttonSpec={button}
|
||||
themeId={themeId}
|
||||
/>;
|
||||
});
|
||||
const titleComponent = <Text
|
||||
variant='titleMedium'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import { DialogControl, DialogType, MenuChoice, PromptButton, PromptDialogData, PromptOptions } from '../types';
|
||||
import { DialogControl, DialogType, MenuChoice, PromptButtonSpec, PromptDialogData, PromptOptions } from '../types';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { useMemo, useRef } from 'react';
|
||||
|
||||
@ -32,7 +32,7 @@ const useDialogControl = (setPromptDialogs: SetPromptDialogs) => {
|
||||
}]);
|
||||
});
|
||||
},
|
||||
prompt: (title: string, message: string, buttons: PromptButton[] = defaultButtons, options?: PromptOptions) => {
|
||||
prompt: (title: string, message: string, buttons: PromptButtonSpec[] = defaultButtons, options?: PromptOptions) => {
|
||||
// Alert.alert doesn't work on web.
|
||||
if (Platform.OS !== 'web') {
|
||||
// Note: Alert.alert provides a more native style on iOS.
|
||||
@ -71,11 +71,11 @@ const useDialogControl = (setPromptDialogs: SetPromptDialogs) => {
|
||||
key: `menu-dialog-${nextDialogIdRef.current++}`,
|
||||
title: '',
|
||||
message: title,
|
||||
buttons: choices.map(choice => ({
|
||||
text: choice.text,
|
||||
buttons: choices.map(({ id, ...buttonProps }) => ({
|
||||
...buttonProps,
|
||||
onPress: () => {
|
||||
dismiss();
|
||||
resolve(choice.id);
|
||||
resolve(id);
|
||||
},
|
||||
})),
|
||||
onDismiss: dismiss,
|
||||
|
@ -1,23 +1,28 @@
|
||||
|
||||
export interface PromptButton {
|
||||
interface BaseButtonSpec {
|
||||
text: string;
|
||||
onPress?: ()=> void;
|
||||
style?: 'cancel'|'default'|'destructive';
|
||||
|
||||
checked?: boolean|null;
|
||||
iconChecked?: string;
|
||||
}
|
||||
|
||||
export interface PromptButtonSpec extends BaseButtonSpec {
|
||||
onPress?: ()=> void;
|
||||
}
|
||||
|
||||
export interface PromptOptions {
|
||||
cancelable?: boolean;
|
||||
}
|
||||
|
||||
export interface MenuChoice<IdType> {
|
||||
text: string;
|
||||
export interface MenuChoice<IdType> extends BaseButtonSpec {
|
||||
id: IdType;
|
||||
}
|
||||
|
||||
export interface DialogControl {
|
||||
info(message: string): Promise<void>;
|
||||
error(message: string): Promise<void>;
|
||||
prompt(title: string, message: string, buttons?: PromptButton[], options?: PromptOptions): void;
|
||||
prompt(title: string, message: string, buttons?: PromptButtonSpec[], options?: PromptOptions): void;
|
||||
showMenu<IdType>(title: string, choices: MenuChoice<IdType>[]): Promise<IdType>;
|
||||
}
|
||||
|
||||
@ -31,7 +36,7 @@ export interface PromptDialogData {
|
||||
key: string;
|
||||
title: string;
|
||||
message: string;
|
||||
buttons: PromptButton[];
|
||||
buttons: PromptButtonSpec[];
|
||||
onDismiss: (()=> void)|null;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ import { Platform, TextStyle, ViewStyle } from 'react-native';
|
||||
import { themeById } from '@joplin/lib/theme';
|
||||
import { Theme as BaseTheme } from '@joplin/lib/themes/type';
|
||||
|
||||
const Color = require('color');
|
||||
|
||||
const baseStyle = {
|
||||
appearance: 'light',
|
||||
fontSize: 16,
|
||||
@ -16,12 +18,15 @@ const baseStyle = {
|
||||
};
|
||||
|
||||
export type ThemeStyle = BaseTheme & typeof baseStyle & {
|
||||
backgroundColorHover4: string;
|
||||
|
||||
fontSize: number;
|
||||
fontSizeSmaller: number;
|
||||
marginRight: number;
|
||||
marginLeft: number;
|
||||
marginTop: number;
|
||||
marginBottom: number;
|
||||
borderRadius: number;
|
||||
icon: TextStyle;
|
||||
lineInput: ViewStyle;
|
||||
buttonRow: ViewStyle;
|
||||
@ -112,6 +117,9 @@ function extraStyles(theme: BaseTheme) {
|
||||
keyboardAppearance: theme.appearance,
|
||||
color5: theme.color5 ?? theme.backgroundColor4,
|
||||
backgroundColor5: theme.backgroundColor5 ?? theme.color4,
|
||||
|
||||
backgroundColorHover4: Color(theme.color4).alpha(0.12).rgb().string(),
|
||||
borderRadius: 24,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import AccessibleView from '../accessibility/AccessibleView';
|
||||
import { Dispatch } from 'redux';
|
||||
import { DialogContext, DialogControl } from '../DialogManager';
|
||||
import { useContext } from 'react';
|
||||
import { MenuChoice } from '../DialogManager/types';
|
||||
|
||||
interface Props {
|
||||
dispatch: Dispatch;
|
||||
@ -68,34 +69,35 @@ class NotesScreenComponent extends BaseScreenComponent<ComponentProps, State> {
|
||||
};
|
||||
|
||||
private sortButton_press = async () => {
|
||||
const buttons = [];
|
||||
type IdType = { name: string; value: string|boolean };
|
||||
const buttons: MenuChoice<IdType>[] = [];
|
||||
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
|
||||
|
||||
const makeCheckboxText = function(selected: boolean, sign: string, label: string) {
|
||||
const s = sign === 'tick' ? '✓' : '⬤';
|
||||
return (selected ? `${s} ` : '') + label;
|
||||
};
|
||||
|
||||
for (const field in sortNoteOptions) {
|
||||
if (!sortNoteOptions.hasOwnProperty(field)) continue;
|
||||
buttons.push({
|
||||
text: makeCheckboxText(Setting.value('notes.sortOrder.field') === field, 'bullet', sortNoteOptions[field]),
|
||||
text: sortNoteOptions[field],
|
||||
iconChecked: 'fas fa-circle',
|
||||
checked: Setting.value('notes.sortOrder.field') === field,
|
||||
id: { name: 'notes.sortOrder.field', value: field },
|
||||
});
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
text: makeCheckboxText(Setting.value('notes.sortOrder.reverse'), 'tick', `[ ${Setting.settingMetadata('notes.sortOrder.reverse').label()} ]`),
|
||||
text: `[ ${Setting.settingMetadata('notes.sortOrder.reverse').label()} ]`,
|
||||
checked: Setting.value('notes.sortOrder.reverse'),
|
||||
id: { name: 'notes.sortOrder.reverse', value: !Setting.value('notes.sortOrder.reverse') },
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
text: makeCheckboxText(Setting.value('uncompletedTodosOnTop'), 'tick', `[ ${Setting.settingMetadata('uncompletedTodosOnTop').label()} ]`),
|
||||
text: `[ ${Setting.settingMetadata('uncompletedTodosOnTop').label()} ]`,
|
||||
checked: Setting.value('uncompletedTodosOnTop'),
|
||||
id: { name: 'uncompletedTodosOnTop', value: !Setting.value('uncompletedTodosOnTop') },
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
text: makeCheckboxText(Setting.value('showCompletedTodos'), 'tick', `[ ${Setting.settingMetadata('showCompletedTodos').label()} ]`),
|
||||
text: `[ ${Setting.settingMetadata('showCompletedTodos').label()} ]`,
|
||||
checked: Setting.value('showCompletedTodos'),
|
||||
id: { name: 'showCompletedTodos', value: !Setting.value('showCompletedTodos') },
|
||||
});
|
||||
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@react-native-community/slider": "4.5.2",
|
||||
"assert-browserify": "2.0.0",
|
||||
"buffer": "6.0.3",
|
||||
"color": "3.2.1",
|
||||
"constants-browserify": "1.0.0",
|
||||
"crypto-browserify": "3.12.0",
|
||||
"deprecated-react-native-prop-types": "5.0.0",
|
||||
|
@ -3,16 +3,16 @@ import { Alert } from 'react-native';
|
||||
import { DialogControl } from '../components/DialogManager';
|
||||
import { RefObject } from 'react';
|
||||
import { MessageBoxType, ShowMessageBoxOptions } from '@joplin/lib/shim';
|
||||
import { PromptButton } from '../components/DialogManager/types';
|
||||
import { PromptButtonSpec } from '../components/DialogManager/types';
|
||||
|
||||
|
||||
const makeShowMessageBox = (dialogControl: null|RefObject<DialogControl>) => (message: string, options: ShowMessageBoxOptions = null) => {
|
||||
return new Promise<number>(resolve => {
|
||||
const okButton: PromptButton = {
|
||||
const okButton: PromptButtonSpec = {
|
||||
text: _('OK'),
|
||||
onPress: () => resolve(0),
|
||||
};
|
||||
const cancelButton: PromptButton = {
|
||||
const cancelButton: PromptButtonSpec = {
|
||||
text: _('Cancel'),
|
||||
onPress: () => resolve(1),
|
||||
style: 'cancel',
|
||||
|
Loading…
Reference in New Issue
Block a user