2025-04-07 12:12:10 -07:00
|
|
|
import * as React from 'react';
|
|
|
|
import { useState, useCallback, useMemo, useRef } from 'react';
|
|
|
|
import { FAB } from 'react-native-paper';
|
2023-01-08 04:22:41 -08:00
|
|
|
import { _ } from '@joplin/lib/locale';
|
2023-11-26 12:37:45 +01:00
|
|
|
import { Dispatch } from 'redux';
|
2025-04-07 12:12:10 -07:00
|
|
|
import { AccessibilityActionEvent, AccessibilityActionInfo, View } from 'react-native';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import BottomDrawer from '../BottomDrawer';
|
2023-11-26 12:37:45 +01:00
|
|
|
const Icon = require('react-native-vector-icons/Ionicons').default;
|
2023-01-08 04:22:41 -08:00
|
|
|
|
|
|
|
type OnButtonPress = ()=> void;
|
|
|
|
interface ButtonSpec {
|
|
|
|
icon: string;
|
|
|
|
label: string;
|
|
|
|
color?: string;
|
|
|
|
onPress?: OnButtonPress;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ActionButtonProps {
|
|
|
|
// If not given, an "add" button will be used.
|
2025-04-07 12:12:10 -07:00
|
|
|
mainButton: ButtonSpec;
|
2023-11-26 12:37:45 +01:00
|
|
|
dispatch: Dispatch;
|
2023-01-08 04:22:41 -08:00
|
|
|
|
2025-04-07 12:12:10 -07:00
|
|
|
menuContent?: React.ReactNode;
|
|
|
|
onMenuShow?: ()=> void;
|
|
|
|
|
|
|
|
accessibilityActions?: readonly AccessibilityActionInfo[];
|
|
|
|
// Can return a Promise to simplify unit testing
|
|
|
|
onAccessibilityAction?: (event: AccessibilityActionEvent)=> void|Promise<void>;
|
|
|
|
accessibilityHint?: string;
|
|
|
|
}
|
2023-01-08 04:22:41 -08:00
|
|
|
|
|
|
|
// Returns a render function compatible with React Native Paper.
|
|
|
|
const getIconRenderFunction = (iconName: string) => {
|
2024-04-05 12:16:49 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
2023-01-08 04:22:41 -08:00
|
|
|
return (props: any) => <Icon name={iconName} {...props} />;
|
|
|
|
};
|
|
|
|
|
|
|
|
const useIcon = (iconName: string) => {
|
|
|
|
return useMemo(() => {
|
|
|
|
return getIconRenderFunction(iconName);
|
|
|
|
}, [iconName]);
|
|
|
|
};
|
|
|
|
|
2024-08-02 06:51:49 -07:00
|
|
|
const FloatingActionButton = (props: ActionButtonProps) => {
|
2023-01-08 04:22:41 -08:00
|
|
|
const [open, setOpen] = useState(false);
|
2025-04-07 12:12:10 -07:00
|
|
|
const onMenuToggled = useCallback(() => {
|
|
|
|
const newOpen = !open;
|
2025-06-10 01:03:32 -07:00
|
|
|
if (newOpen) {
|
|
|
|
props.dispatch({
|
|
|
|
type: 'SIDE_MENU_CLOSE',
|
|
|
|
});
|
|
|
|
}
|
2025-04-07 12:12:10 -07:00
|
|
|
setOpen(newOpen);
|
|
|
|
}, [setOpen, open, props.dispatch]);
|
|
|
|
|
|
|
|
const onDismiss = useCallback(() => {
|
|
|
|
if (open) onMenuToggled();
|
|
|
|
}, [open, onMenuToggled]);
|
2023-01-08 04:22:41 -08:00
|
|
|
|
2025-04-24 00:48:58 -07:00
|
|
|
const mainButtonRef = useRef<View>(null);
|
2023-01-08 04:22:41 -08:00
|
|
|
|
2023-10-07 17:25:03 +01:00
|
|
|
const closedIcon = useIcon(props.mainButton?.icon ?? 'add');
|
2023-01-08 04:22:41 -08:00
|
|
|
const openIcon = useIcon('close');
|
|
|
|
|
2024-08-02 06:51:49 -07:00
|
|
|
const label = props.mainButton?.label ?? _('Add new');
|
|
|
|
|
2025-04-07 12:12:10 -07:00
|
|
|
const menuButton = <FAB
|
|
|
|
ref={mainButtonRef}
|
|
|
|
icon={open ? openIcon : closedIcon}
|
2024-08-02 06:51:49 -07:00
|
|
|
accessibilityLabel={label}
|
2025-04-07 12:12:10 -07:00
|
|
|
onPress={props.mainButton?.onPress ?? onMenuToggled}
|
|
|
|
style={{
|
|
|
|
alignSelf: 'flex-end',
|
2024-08-02 06:51:49 -07:00
|
|
|
}}
|
2025-04-07 12:12:10 -07:00
|
|
|
accessibilityActions={props.accessibilityActions}
|
|
|
|
onAccessibilityAction={props.onAccessibilityAction}
|
2024-08-02 06:51:49 -07:00
|
|
|
/>;
|
|
|
|
|
2025-04-07 12:12:10 -07:00
|
|
|
return <>
|
|
|
|
<View
|
|
|
|
style={{
|
|
|
|
position: 'absolute',
|
|
|
|
bottom: 10,
|
|
|
|
right: 10,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{menuButton}
|
|
|
|
</View>
|
|
|
|
<BottomDrawer
|
|
|
|
visible={open}
|
|
|
|
onDismiss={onDismiss}
|
|
|
|
onShow={props.onMenuShow}
|
|
|
|
>
|
|
|
|
{props.menuContent}
|
|
|
|
</BottomDrawer>
|
|
|
|
</>;
|
2023-01-08 04:22:41 -08:00
|
|
|
};
|
|
|
|
|
2025-04-07 12:12:10 -07:00
|
|
|
export default connect()(FloatingActionButton);
|