1- import { useState , useRef , useEffect } from 'react' ;
2- import { ReactNode } from 'react' ;
1+ import { Popover as HPopover , Transition } from '@headlessui/ react' ;
2+ import { CSSProperties , ElementType , HTMLAttributes , ReactNode } from 'react' ;
33
4- export interface PopoverProps {
4+ export type PopoverPanel = ReactNode | ( ( closePopover : ( ) => void ) => ReactNode ) ;
5+
6+ export interface PopoverProps extends Omit < HTMLAttributes < HTMLDivElement > , 'className' > {
57 childrenButton : ReactNode ;
6- panel : ( closePopover : ( ) => void ) => ReactNode ;
8+ panel : PopoverPanel ;
79 className ?: string ;
810 classButton ?: string ;
11+ buttonAs ?: ElementType ;
12+ buttonProps ?: HTMLAttributes < HTMLElement > ;
13+ panelClassName ?: string ;
14+ panelStyle ?: CSSProperties ;
15+ staticPanel ?: boolean ;
16+ 'data-test' ?: string ;
917}
1018
1119/**
@@ -14,9 +22,9 @@ export interface PopoverProps {
1422 * @property {ReactNode } childrenButton
1523 * - The content to be displayed inside the trigger button.
1624 *
17- * @property {( closePopover: () => void) => ReactNode } panel
18- * - A function that returns the content to be displayed inside the popover panel.
19- * It receives a `closePopover` function as a parameter, which can be used to programmatically close the popover.
25+ * @property {ReactNode | (( closePopover: () => void) => ReactNode) } panel
26+ * - Content to be displayed inside the popover panel. If a function is provided,
27+ * it receives a `closePopover` callback that can be used to close the popover.
2028 *
2129 * @property {string } [className]
2230 * - Additional custom classes for the outermost container of the popover.
@@ -29,72 +37,57 @@ export interface PopoverProps {
2937 * - The rendered Popover component.
3038 */
3139
32- const Popover = ( { childrenButton, panel, className, classButton } : PopoverProps ) : JSX . Element => {
33- const [ isOpen , setIsOpen ] = useState ( false ) ;
34- const panelRef = useRef < HTMLDivElement | null > ( null ) ;
35- const [ showContent , setShowContent ] = useState ( isOpen ) ;
36- const [ transitionOpacity , setTransitionOpacity ] = useState < string > ( 'opacity-0' ) ;
37- const [ transitionScale , setTransitionScale ] = useState < string > ( 'scale-95' ) ;
38-
39- const togglePopover = ( ) => setIsOpen ( ( prev ) => ! prev ) ;
40-
41- const handleMouseDown = ( event : MouseEvent ) => {
42- if ( panelRef . current && ! panelRef . current . contains ( event . target as Node ) ) {
43- closePopover ( ) ;
44- }
45- } ;
40+ const Popover = ( {
41+ childrenButton,
42+ panel,
43+ className = '' ,
44+ classButton = '' ,
45+ buttonAs,
46+ buttonProps,
47+ panelClassName = '' ,
48+ panelStyle,
49+ staticPanel = false ,
50+ style,
51+ ...rest
52+ } : Readonly < PopoverProps > ) : JSX . Element => {
53+ const renderPanel = ( closePopover : ( ) => void ) => ( typeof panel === 'function' ? panel ( closePopover ) : panel ) ;
54+ const { className : buttonPropsClassName = '' , ...restButtonProps } = buttonProps ?? { } ;
4655
47- const closePopover = ( ) => {
48- setIsOpen ( false ) ;
49- } ;
50-
51- useEffect ( ( ) => {
52- if ( isOpen ) {
53- const timeout = setTimeout ( ( ) => {
54- setTransitionOpacity ( 'opacity-100' ) ;
55- setTransitionScale ( 'scale-100' ) ;
56- } , 10 ) ;
57- setShowContent ( true ) ;
58- return ( ) => clearTimeout ( timeout ) ;
59- } else {
60- setTransitionOpacity ( 'opacity-0' ) ;
61- setTransitionScale ( 'scale-95' ) ;
62- const timeout = setTimeout ( ( ) => {
63- setShowContent ( false ) ;
64- } , 100 ) ;
65- return ( ) => clearTimeout ( timeout ) ;
66- }
67- } , [ isOpen ] ) ;
68-
69- useEffect ( ( ) => {
70- document . addEventListener ( 'mousedown' , handleMouseDown ) ;
71- return ( ) => {
72- document . removeEventListener ( 'mousedown' , handleMouseDown ) ;
73- } ;
74- } , [ ] ) ;
56+ const panelContent = (
57+ < HPopover . Panel
58+ className = { `absolute right-0 z-50 mt-1 rounded-md border border-gray-10 bg-surface py-1.5 shadow-subtle dark:bg-gray-5 ${ panelClassName } ` }
59+ style = { panelStyle }
60+ static = { staticPanel }
61+ >
62+ { ( { close } ) => renderPanel ( close ) }
63+ </ HPopover . Panel >
64+ ) ;
7565
7666 return (
77- < div style = { { lineHeight : 0 } } className = { `relative ${ className } ` } >
78- < button
79- onClick = { togglePopover }
80- className = { `cursor-pointer outline-none ${ classButton } ` }
81- aria-expanded = { isOpen }
82- data-testid = "popover-button"
67+ < HPopover style = { { lineHeight : 0 , ...( style ?? { } ) } } className = { `relative ${ className } ` } { ...rest } >
68+ < HPopover . Button
69+ as = { buttonAs }
70+ className = { `cursor-pointer outline-none ${ classButton } ${ buttonPropsClassName } ` }
71+ { ...restButtonProps }
8372 >
8473 { childrenButton }
85- </ button >
86- { showContent && (
87- < div
88- ref = { panelRef }
89- className = {
90- 'absolute right-0 z-50 mt-1 origin-top-right transform rounded-md border border-gray-10 ' +
91- `bg-surface py-1.5 shadow-subtle duration-100 ease-out dark:bg-gray-5 ${ transitionOpacity } ${ transitionScale } `
92- }
74+ </ HPopover . Button >
75+ { staticPanel ? (
76+ panelContent
77+ ) : (
78+ < Transition
79+ enter = "transition duration-100 ease-out"
80+ enterFrom = "scale-95 opacity-0"
81+ enterTo = "scale-100 opacity-100"
82+ leave = "transition duration-75 ease-out"
83+ leaveFrom = "scale-100 opacity-100"
84+ leaveTo = "scale-95 opacity-0"
85+ className = "z-50"
9386 >
94- { panel ( closePopover ) }
95- </ div >
87+ { panelContent }
88+ </ Transition >
9689 ) }
97- </ div >
90+ </ HPopover >
9891 ) ;
9992} ;
10093
0 commit comments