Add hotkeys to modals (#1305)
* Add hotkeys to modals * fix * fix * remove unnecessary type * restore type * add handleEnter * rename event props
This commit is contained in:
@ -60,10 +60,10 @@ export function ConfirmationModal({
|
|||||||
|
|
||||||
const handleInputConfimrationValueChange = (value: string) => {
|
const handleInputConfimrationValueChange = (value: string) => {
|
||||||
setInputConfirmationValue(value);
|
setInputConfirmationValue(value);
|
||||||
isValueMatchingUserEmail(confirmationValue, value);
|
isValueMatchingInput(confirmationValue, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValueMatchingUserEmail = debounce(
|
const isValueMatchingInput = debounce(
|
||||||
(value?: string, inputValue?: string) => {
|
(value?: string, inputValue?: string) => {
|
||||||
setIsValidValue(Boolean(value && inputValue && value === inputValue));
|
setIsValidValue(Boolean(value && inputValue && value === inputValue));
|
||||||
},
|
},
|
||||||
@ -75,11 +75,12 @@ export function ConfirmationModal({
|
|||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<StyledConfirmationModal
|
<StyledConfirmationModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onOutsideClick={() => {
|
onClose={() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onEnter={onConfirmClick}
|
||||||
>
|
>
|
||||||
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
|
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
|
||||||
<Section
|
<Section
|
||||||
@ -95,7 +96,7 @@ export function ConfirmationModal({
|
|||||||
onChange={handleInputConfimrationValueChange}
|
onChange={handleInputConfimrationValueChange}
|
||||||
placeholder={confirmationPlaceholder}
|
placeholder={confirmationPlaceholder}
|
||||||
fullWidth
|
fullWidth
|
||||||
key={'email-' + confirmationValue}
|
key={'input-' + confirmationValue}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import {
|
import {
|
||||||
ClickOutsideMode,
|
ClickOutsideMode,
|
||||||
useListenClickOutside,
|
useListenClickOutside,
|
||||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
|
||||||
|
import { ModalHotkeyScope } from './types/ModalHotkeyScope';
|
||||||
|
|
||||||
const StyledModalDiv = styled(motion.div)`
|
const StyledModalDiv = styled(motion.div)`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -83,7 +88,9 @@ function ModalFooter({ children, ...restProps }: ModalFooterProps) {
|
|||||||
type ModalProps = React.PropsWithChildren &
|
type ModalProps = React.PropsWithChildren &
|
||||||
React.ComponentProps<'div'> & {
|
React.ComponentProps<'div'> & {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
onOutsideClick?: () => void;
|
onClose?: () => void;
|
||||||
|
hotkeyScope?: ModalHotkeyScope;
|
||||||
|
onEnter?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalVariants = {
|
const modalVariants = {
|
||||||
@ -95,22 +102,55 @@ const modalVariants = {
|
|||||||
export function Modal({
|
export function Modal({
|
||||||
isOpen = false,
|
isOpen = false,
|
||||||
children,
|
children,
|
||||||
onOutsideClick,
|
onClose,
|
||||||
|
hotkeyScope = ModalHotkeyScope.Default,
|
||||||
|
onEnter,
|
||||||
...restProps
|
...restProps
|
||||||
}: ModalProps) {
|
}: ModalProps) {
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [modalRef],
|
refs: [modalRef],
|
||||||
callback: () => onOutsideClick?.(),
|
callback: () => onClose?.(),
|
||||||
mode: ClickOutsideMode.absolute,
|
mode: ClickOutsideMode.absolute,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isOpen) {
|
const {
|
||||||
return null;
|
goBackToPreviousHotkeyScope,
|
||||||
}
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
return (
|
useScopedHotkeys(
|
||||||
|
[Key.Escape],
|
||||||
|
() => {
|
||||||
|
onClose?.();
|
||||||
|
},
|
||||||
|
hotkeyScope,
|
||||||
|
[onClose],
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.Enter],
|
||||||
|
() => {
|
||||||
|
onEnter?.();
|
||||||
|
},
|
||||||
|
hotkeyScope,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
||||||
|
} else {
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
goBackToPreviousHotkeyScope,
|
||||||
|
hotkeyScope,
|
||||||
|
isOpen,
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return isOpen ? (
|
||||||
<StyledBackDrop>
|
<StyledBackDrop>
|
||||||
<StyledModalDiv
|
<StyledModalDiv
|
||||||
// framer-motion seems to have typing problems with refs
|
// framer-motion seems to have typing problems with refs
|
||||||
@ -127,6 +167,8 @@ export function Modal({
|
|||||||
{children}
|
{children}
|
||||||
</StyledModalDiv>
|
</StyledModalDiv>
|
||||||
</StyledBackDrop>
|
</StyledBackDrop>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
export enum ModalHotkeyScope {
|
||||||
|
Default = 'default',
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user