1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: Improve focus handling for notebook edit, share, and sync dialogs (#10779)

This commit is contained in:
Henry Heino 2024-07-26 04:39:01 -07:00 committed by GitHub
parent 3fbb3b6b82
commit dd5240d018
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 80 additions and 51 deletions

View File

@ -1,49 +1,46 @@
import styled from 'styled-components';
const DialogModalLayer = styled.div`
z-index: 9999;
display: flex;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.6);
align-items: flex-start;
justify-content: center;
overflow: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
`;
const DialogRoot = styled.div`
background-color: ${props => props.theme.backgroundColor};
padding: 16px;
box-shadow: 6px 6px 20px rgba(0,0,0,0.5);
margin: 20px;
min-height: fit-content;
display: flex;
flex-direction: column;
border-radius: 10px;
`;
import * as React from 'react';
import { ReactElement, ReactEventHandler, useCallback, useEffect, useRef, useState } from 'react';
interface Props {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
renderContent: Function;
renderContent: ()=> ReactElement;
className?: string;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
onClose?: Function;
onClose?: ()=> void;
}
export default function Dialog(props: Props) {
const [dialogElement, setDialogRef] = useState<HTMLDialogElement>();
useEffect(() => {
if (!dialogElement) return;
// Use .showModal instead of the open attribute: .showModal correctly
// traps the keyboard focus in the dialog
dialogElement.showModal();
}, [dialogElement]);
const onCloseRef = useRef(props.onClose);
onCloseRef.current = props.onClose;
const onCancel: ReactEventHandler<HTMLDialogElement> = useCallback((event) => {
const canCancel = !!onCloseRef.current;
if (canCancel) {
// Prevents [Escape] from closing the dialog. In many places, this is handled
// elsewhere.
// See https://stackoverflow.com/a/61021326
event.preventDefault();
}
}, []);
return (
<DialogModalLayer className={props.className}>
<DialogRoot>
<dialog
ref={setDialogRef}
className={`dialog-modal-layer ${props.className}`}
onClose={props.onClose}
onCancel={onCancel}
>
<div className='content'>
{props.renderContent()}
</DialogRoot>
</DialogModalLayer>
</div>
</dialog>
);
}

View File

@ -21,7 +21,7 @@ interface Props {
export const IconSelector = (props: Props) => {
const [emojiButtonClassReady, setEmojiButtonClassReady] = useState<boolean>(false);
const [picker, setPicker] = useState<EmojiButton>();
const buttonRef = useRef(null);
const buttonRef = useRef<HTMLButtonElement>(null);
useAsyncEffect(async (event: AsyncEffectEvent) => {
const loadScripts = async () => {
@ -61,6 +61,7 @@ export const IconSelector = (props: Props) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const p: EmojiButton = new (window as any).EmojiButton({
zIndex: 10000,
rootElement: buttonRef.current?.parentElement,
});
const onEmoji = (selection: FolderIcon) => {
@ -73,6 +74,7 @@ export const IconSelector = (props: Props) => {
return () => {
p.off('emoji', onEmoji);
p.destroyPicker();
};
}, [emojiButtonClassReady, props.onChange]);

View File

@ -140,17 +140,16 @@ type SyncTargetInfoName = 'dropbox' | 'onedrive' | 'joplinCloud';
export default function(props: Props) {
const joplinCloudDescriptionRef = useRef(null);
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
function closeDialog(dispatch: Function) {
dispatch({
const closeDialog = useCallback(() => {
props.dispatch({
type: 'DIALOG_CLOSE',
name: 'syncWizard',
});
}
}, [props.dispatch]);
const onButtonRowClick = useCallback(() => {
closeDialog(props.dispatch);
}, [props.dispatch]);
closeDialog();
}, [closeDialog]);
const { height: descriptionHeight } = useElementSize(joplinCloudDescriptionRef);
@ -184,12 +183,12 @@ export default function(props: Props) {
Setting.setValue('sync.target', route.target);
await Setting.saveAll();
closeDialog(props.dispatch);
closeDialog();
props.dispatch({
type: 'NAV_GO',
routeName: route.name,
});
}, [props.dispatch]);
}, [props.dispatch, closeDialog]);
function renderSelectArea(info: SyncTargetInfo) {
return (
@ -229,7 +228,7 @@ export default function(props: Props) {
}
const onSelfHostingClick = useCallback(() => {
closeDialog(props.dispatch);
closeDialog();
props.dispatch({
type: 'NAV_GO',
@ -238,7 +237,7 @@ export default function(props: Props) {
defaultSection: 'sync',
},
});
}, [props.dispatch]);
}, [props.dispatch, closeDialog]);
function renderContent() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@ -278,6 +277,6 @@ export default function(props: Props) {
}
return (
<Dialog renderContent={renderDialogWrapper}/>
<Dialog onClose={closeDialog} renderContent={renderDialogWrapper}/>
);
}

View File

@ -0,0 +1,28 @@
.dialog-modal-layer {
display: flex;
top: 0;
left: 0;
width: 100%;
height: 100%;
align-items: flex-start;
justify-content: center;
border: none;
margin: 0;
background-color: transparent;
> .content {
background-color: var(--joplin-background-color);
padding: 16px;
box-shadow: 6px 6px 20px rgba(0,0,0,0.5);
margin: 20px;
min-height: fit-content;
display: flex;
flex-direction: column;
border-radius: 10px;
}
&::backdrop {
background-color: rgba(0,0,0,0.6);
}
}

View File

@ -0,0 +1,2 @@
@use './dialog-modal-layer.scss';

View File

@ -10,4 +10,5 @@
@use 'gui/NoteListHeader/style.scss' as note-list-header;
@use 'gui/TrashNotification/style.scss' as trash-notification;
@use 'gui/Sidebar/style.scss' as sidebar-styles;
@use 'gui/styles/index.scss';
@use 'main.scss' as main;