From 5abb7549876d0f36145afa365abf634a9aa3df2a Mon Sep 17 00:00:00 2001 From: Jo du Plessis Date: Wed, 12 Jun 2024 12:07:30 +0200 Subject: [PATCH] add InputPopover component & Popover autoFocus prop #252 --- packages/core/src/input/index.ts | 1 + packages/core/src/input/input-popover.tsx | 63 +++++++++++++++++++++++ packages/core/src/modal/modal.tsx | 5 +- packages/core/src/popover/popover.css | 4 ++ packages/core/src/popover/popover.tsx | 26 ++++++++-- 5 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/input/input-popover.tsx diff --git a/packages/core/src/input/index.ts b/packages/core/src/input/index.ts index 114afe81..546cad5b 100644 --- a/packages/core/src/input/index.ts +++ b/packages/core/src/input/index.ts @@ -1 +1,2 @@ export * from './input' +export * from './input-popover' diff --git a/packages/core/src/input/input-popover.tsx b/packages/core/src/input/input-popover.tsx new file mode 100644 index 00000000..add62757 --- /dev/null +++ b/packages/core/src/input/input-popover.tsx @@ -0,0 +1,63 @@ +import React, { ReactNode, cloneElement, useRef } from 'react' +import { Popover, PopoverProps, getKey, renderChildren, useId, useVisibility } from '../' + +export type InputPopoverProps = { + id?: string + defaultVisibility?: boolean + popoverProps?: PopoverProps + children: ReactNode + content: ReactNode +} + +export const InputPopover = (props: InputPopoverProps) => { + const { id, defaultVisibility = false, popoverProps = {}, children, content } = props + const childRef = useRef(null) + const firstTimeFocus = useRef(false) + const isOpen = useRef(false) + const { show, hide, visible, delayedShow } = useVisibility(defaultVisibility) + const internalId = useId(id) + + const handleFocus = (e) => { + if (!firstTimeFocus.current) { + e.stopPropagation() + e.preventDefault() + if (!isOpen.current) delayedShow(100) + } + } + + const handleClick = (e) => { + if (!isOpen.current) delayedShow(100) + } + + const handleKeyDown = (e) => { + const { isEnter } = getKey(e) + if (isEnter && !isOpen.current) delayedShow(100) + } + + const handleDismiss = (e) => { + hide() + isOpen.current = false + firstTimeFocus.current = true + childRef.current?.focus() + } + + return ( + + {renderChildren(children, (child) => { + return cloneElement(child, { + ...child.props, + onFocus: handleFocus, + onClick: handleClick, + onKeyDown: handleKeyDown, + ref: childRef, + id: internalId, + }) + })} + + ) +} diff --git a/packages/core/src/modal/modal.tsx b/packages/core/src/modal/modal.tsx index 8ff1016c..e98b7276 100644 --- a/packages/core/src/modal/modal.tsx +++ b/packages/core/src/modal/modal.tsx @@ -64,6 +64,7 @@ export const Modal = (props: ModalProps) => { const { trapFocus } = useFocus() const handleKeyDown = (e) => { + console.log('modal') const { isEscape } = getKey(e) if (isEscape && dismissOnEscape) onDismiss(e) } @@ -104,14 +105,14 @@ export const Modal = (props: ModalProps) => { return (
{header &&
{header}
} {props.children &&
{props.children}
} diff --git a/packages/core/src/popover/popover.css b/packages/core/src/popover/popover.css index 5fc1bb73..a2a53ece 100644 --- a/packages/core/src/popover/popover.css +++ b/packages/core/src/popover/popover.css @@ -19,6 +19,10 @@ pointer-events: all; } +.f-popover:focus { + outline: none +} + .f-popover:not(.is-ready) { visibility: hidden; } diff --git a/packages/core/src/popover/popover.tsx b/packages/core/src/popover/popover.tsx index c87e1739..48ef2c02 100644 --- a/packages/core/src/popover/popover.tsx +++ b/packages/core/src/popover/popover.tsx @@ -26,6 +26,8 @@ export const PopoverContent = forwardRef((props: CoreViewProps, ref) => ( export type PopoverAnchor = PopoutPosition export type PopoverProps = { + __autoFocusTimeoutDelay?: number + autoFocus?: boolean targetId?: string fixPosition?: { top: number; left: number } arrow?: boolean @@ -38,6 +40,8 @@ export type PopoverProps = { export const Popover = forwardRef((props: PopoverProps, ref) => { const { + __autoFocusTimeoutDelay = 200, + autoFocus = true, targetId, fixPosition, anchorProps = {}, @@ -54,7 +58,7 @@ export const Popover = forwardRef((props: PopoverProps, ref) => { const [ready, setReady] = useState(false) const [finalAnchor, setFinalAnchor] = useState('') const id = useId(targetId) - const showPopover = isVisible && id && finalAnchor + const showPopover = isVisible && id && !!finalAnchor const isFixed = !!fixPosition const className = classNames( { @@ -66,6 +70,8 @@ export const Popover = forwardRef((props: PopoverProps, ref) => { ) const dismissPopover = (e) => { + e.preventDefault() + e.stopPropagation() dispatchPopoverEvent('ondismiss', e) onDismiss(e) setReady(false) @@ -84,9 +90,21 @@ export const Popover = forwardRef((props: PopoverProps, ref) => { } } - useEvent('keydown', handleKeyDown, true) useEvent('click', handleClick, true) + useEffect(() => { + if (autoFocus && showPopover) { + setTimeout(() => { + containerRef.current?.focus() + + }, __autoFocusTimeoutDelay) + } + }, [ + __autoFocusTimeoutDelay, + autoFocus, + showPopover, + ]) + useLayoutEffect(() => { if (!id) return if (!isVisible) return @@ -137,7 +155,7 @@ export const Popover = forwardRef((props: PopoverProps, ref) => { return cloneElement(child, { ...child.props, ref: mergeRefs([child.ref, childRef]), - id, + id: child.props.id || id, }) })} @@ -153,6 +171,8 @@ export const Popover = forwardRef((props: PopoverProps, ref) => { {...anchorProps}>