1
1
import * as React from 'react' ;
2
- import PropTypes from 'prop-types' ;
3
2
import styled from 'styled-components' ;
4
- import { useFloating , flip , shift , offset , autoUpdate } from '@floating-ui/react-dom' ;
3
+ import { useFloating , flip , shift , offset , autoUpdate , Placement } from '@floating-ui/react-dom' ;
5
4
6
- import { Box } from '../Box' ;
5
+ import { Box , BoxProps } from '../Box' ;
7
6
import { Portal } from '../Portal' ;
8
7
9
8
import { useIntersection } from '../helpers/useIntersection' ;
@@ -21,7 +20,7 @@ export const POPOVER_PLACEMENTS = [
21
20
'left' ,
22
21
'left-start' ,
23
22
'left-end' ,
24
- ] ;
23
+ ] as const ;
25
24
26
25
const PopoverWrapper = styled ( Box ) `
27
26
box-shadow: ${ ( { theme } ) => theme . shadows . filterShadow } ;
@@ -30,41 +29,24 @@ const PopoverWrapper = styled(Box)`
30
29
background: ${ ( { theme } ) => theme . colors . neutral0 } ;
31
30
` ;
32
31
33
- const PopoverScrollable = styled ( Box ) `
34
- // 16 is base base size, 3 is the factor to get closer to 40px and 5 is the number of elements visible in the list
35
- max-height: ${ 3 * 5 } rem;
36
- overflow-y: auto;
37
- overflow-x: hidden;
38
-
39
- &::-webkit-scrollbar {
40
- -webkit-appearance: none;
41
- width: 4px;
42
- }
43
-
44
- &::-webkit-scrollbar-track {
45
- background: ${ ( { theme } ) => theme . colors . neutral0 } ;
46
- }
32
+ interface ContentProps extends BoxProps < HTMLDivElement > {
33
+ source : React . MutableRefObject < HTMLElement > ;
34
+ placement ?: Placement ;
35
+ fullWidth ?: boolean ;
36
+ centered ?: boolean ;
37
+ spacing ?: number ;
38
+ }
47
39
48
- &::-webkit-scrollbar-thumb {
49
- background: ${ ( { theme } ) => theme . colors . neutral150 } ;
50
- border-radius: ${ ( { theme } ) => theme . borderRadius } ;
51
- margin-right: 10px;
52
- }
53
- ` ;
54
-
55
- const PopoverContent = ( {
40
+ export const Content = ( {
56
41
source,
57
42
children,
58
- spacing,
59
- fullWidth,
60
- placement,
61
- onReachEnd,
62
- intersectionId,
63
- centered,
43
+ spacing = 0 ,
44
+ fullWidth = false ,
45
+ placement = 'bottom-start' ,
46
+ centered = false ,
64
47
...props
65
- } ) => {
66
- const popoverRef = React . useRef ( null ) ;
67
- const [ width , setWidth ] = React . useState ( undefined ) ;
48
+ } : ContentProps ) => {
49
+ const [ width , setWidth ] = React . useState < number | undefined > ( undefined ) ;
68
50
const { x, y, reference, floating, strategy } = useFloating ( {
69
51
strategy : 'fixed' ,
70
52
placement : centered ? 'bottom' : placement ,
@@ -88,11 +70,6 @@ const PopoverContent = ({
88
70
}
89
71
} , [ fullWidth , source ] ) ;
90
72
91
- useIntersection ( popoverRef , onReachEnd , {
92
- selectorToWatch : `#${ intersectionId } ` ,
93
- skipWhen : ! intersectionId || ! onReachEnd ,
94
- } ) ;
95
-
96
73
return (
97
74
< PopoverWrapper
98
75
ref = { floating }
@@ -105,64 +82,64 @@ const PopoverContent = ({
105
82
hasRadius
106
83
background = "neutral0"
107
84
padding = { 1 }
85
+ { ...props }
108
86
>
109
- < PopoverScrollable ref = { popoverRef } { ...props } >
110
- { children }
111
- { intersectionId && onReachEnd && < Box id = { intersectionId } width = "100%" height = "1px" /> }
112
- </ PopoverScrollable >
87
+ { children }
113
88
</ PopoverWrapper >
114
89
) ;
115
90
} ;
116
91
117
- export const Popover = ( props ) => {
92
+ export interface ScrollingProps extends BoxProps < HTMLDivElement > {
93
+ intersectionId ?: string ;
94
+ onReachEnd ?: ( entry : IntersectionObserverEntry ) => void ;
95
+ }
96
+
97
+ export const Scrolling = ( { children, intersectionId, onReachEnd, ...props } : ScrollingProps ) => {
98
+ const popoverRef = React . useRef < HTMLDivElement > ( null ! ) ;
99
+
100
+ useIntersection ( popoverRef , onReachEnd ?? ( ( ) => { } ) , {
101
+ selectorToWatch : `#${ intersectionId } ` ,
102
+ skipWhen : ! intersectionId || ! onReachEnd ,
103
+ } ) ;
104
+
118
105
return (
119
- < Portal >
120
- < PopoverContent { ...props } />
121
- </ Portal >
106
+ < PopoverScrollable ref = { popoverRef } { ...props } >
107
+ { children }
108
+ { intersectionId && onReachEnd && < Box id = { intersectionId } width = "100%" height = "1px" /> }
109
+ </ PopoverScrollable >
122
110
) ;
123
111
} ;
124
112
125
- const popoverDefaultProps = {
126
- fullWidth : false ,
127
- intersectionId : undefined ,
128
- onReachEnd : undefined ,
129
- centered : false ,
130
- placement : 'bottom-start' ,
131
- spacing : 0 ,
132
- } ;
113
+ const PopoverScrollable = styled ( Box ) `
114
+ // 16 is base base size, 3 is the factor to get closer to 40px and 5 is the number of elements visible in the list
115
+ max-height: ${ 3 * 5 } rem;
116
+ overflow-y: auto;
117
+ overflow-x: hidden;
133
118
134
- const popoverProps = {
135
- /**
136
- * Horizontally center the popover
137
- */
138
- centered : PropTypes . bool ,
139
- children : PropTypes . node . isRequired ,
140
- /**
141
- * Display full width popover
142
- */
143
- fullWidth : PropTypes . bool ,
144
- /**
145
- * Element id to watch for the onReachEnd event
146
- */
147
- intersectionId : PropTypes . string ,
148
- /**
149
- * The callback invoked after a scroll to the bottom of the popover content.
150
- */
151
- onReachEnd : PropTypes . func ,
152
- /**
153
- * The popover position
154
- */
155
- placement : PropTypes . oneOf ( POPOVER_PLACEMENTS ) ,
156
- /**
157
- * A React ref. Used to defined the position of the popover.
158
- */
159
- source : PropTypes . shape ( {
160
- current : ( typeof Element === 'undefined' ? PropTypes . any : PropTypes . instanceOf ( Element ) ) . isRequired ,
161
- } ) . isRequired ,
162
- spacing : PropTypes . number ,
163
- } ;
119
+ &::-webkit-scrollbar {
120
+ -webkit-appearance: none;
121
+ width: 4px;
122
+ }
123
+
124
+ &::-webkit-scrollbar-track {
125
+ background: ${ ( { theme } ) => theme . colors . neutral0 } ;
126
+ }
164
127
165
- PopoverContent . propTypes = popoverProps ;
166
- PopoverContent . defaultProps = popoverDefaultProps ;
167
- Popover . propTypes = popoverProps ;
168
- Popover . defaultProps = popoverDefaultProps ;
128
+ &::-webkit-scrollbar-thumb {
129
+ background: ${ ( { theme } ) => theme . colors . neutral150 } ;
130
+ border-radius: ${ ( { theme } ) => theme . borderRadius } ;
131
+ margin-right: 10px;
132
+ }
133
+ ` ;
134
+
135
+ type PopoverProps = ScrollingProps & Pick < ContentProps , 'source' | 'spacing' | 'fullWidth' | 'placement' | 'centered' > ;
136
+
137
+ export const Popover = ( { children, source, spacing, fullWidth, placement, centered, ...restProps } : PopoverProps ) => {
138
+ return (
139
+ < Portal >
140
+ < Content source = { source } spacing = { spacing } fullWidth = { fullWidth } placement = { placement } centered = { centered } >
141
+ < Scrolling { ...restProps } > { children } </ Scrolling >
142
+ </ Content >
143
+ </ Portal >
144
+ ) ;
145
+ } ;
0 commit comments