@tamagui/popover 1.0.1-beta.21 → 1.0.1-beta.211
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Popover.js +312 -236
- package/dist/cjs/Popover.js.map +3 -3
- package/dist/cjs/floating.js +37 -0
- package/dist/cjs/floating.js.map +7 -0
- package/dist/cjs/floating.native.js +59 -0
- package/dist/cjs/floating.native.js.map +7 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/Popover.js +318 -216
- package/dist/esm/Popover.js.map +3 -3
- package/dist/esm/floating.js +15 -0
- package/dist/esm/floating.js.map +7 -0
- package/dist/esm/floating.native.js +31 -0
- package/dist/esm/floating.native.js.map +7 -0
- package/dist/esm/index.js +0 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/jsx/Popover.js +216 -149
- package/dist/jsx/Popover.js.map +7 -0
- package/dist/jsx/floating.js +15 -0
- package/dist/jsx/floating.js.map +7 -0
- package/dist/jsx/floating.native.js +31 -0
- package/dist/jsx/floating.native.js.map +7 -0
- package/dist/jsx/index.js +1 -0
- package/dist/jsx/index.js.map +7 -0
- package/package.json +25 -15
- package/src/Popover.tsx +544 -0
- package/src/floating.native.ts +18 -0
- package/src/floating.ts +8 -0
- package/src/index.tsx +1 -0
- package/types/Popover.d.ts +177 -103
- package/types/floating.d.ts +2 -0
- package/types/floating.native.d.ts +15 -0
- package/types/Popover.d.ts.map +0 -1
- package/types/index.d.ts.map +0 -1
package/src/Popover.tsx
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
// adapted from radix-ui popover
|
|
2
|
+
|
|
3
|
+
import '@tamagui/polyfill-dev'
|
|
4
|
+
|
|
5
|
+
import { AnimatePresence } from '@tamagui/animate-presence'
|
|
6
|
+
import { hideOthers } from '@tamagui/aria-hidden'
|
|
7
|
+
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
8
|
+
import {
|
|
9
|
+
MediaQueryKey,
|
|
10
|
+
SizeTokens,
|
|
11
|
+
Theme,
|
|
12
|
+
composeEventHandlers,
|
|
13
|
+
isWeb,
|
|
14
|
+
useEvent,
|
|
15
|
+
useGet,
|
|
16
|
+
useId,
|
|
17
|
+
useMedia,
|
|
18
|
+
useThemeName,
|
|
19
|
+
withStaticProperties,
|
|
20
|
+
} from '@tamagui/core'
|
|
21
|
+
import type { Scope } from '@tamagui/create-context'
|
|
22
|
+
import { createContextScope } from '@tamagui/create-context'
|
|
23
|
+
import { DismissableProps } from '@tamagui/dismissable'
|
|
24
|
+
import { FocusScope, FocusScopeProps } from '@tamagui/focus-scope'
|
|
25
|
+
import {
|
|
26
|
+
FloatingOverrideContext,
|
|
27
|
+
Popper,
|
|
28
|
+
PopperAnchor,
|
|
29
|
+
PopperArrow,
|
|
30
|
+
PopperArrowProps,
|
|
31
|
+
PopperContent,
|
|
32
|
+
PopperContentProps,
|
|
33
|
+
PopperProps,
|
|
34
|
+
createPopperScope,
|
|
35
|
+
} from '@tamagui/popper'
|
|
36
|
+
import { Portal, PortalHost, PortalItem } from '@tamagui/portal'
|
|
37
|
+
import { RemoveScroll, RemoveScrollProps } from '@tamagui/remove-scroll'
|
|
38
|
+
import { ControlledSheet, SheetController } from '@tamagui/sheet'
|
|
39
|
+
import { YStack, YStackProps } from '@tamagui/stacks'
|
|
40
|
+
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
41
|
+
import * as React from 'react'
|
|
42
|
+
import { ScrollView, ScrollViewProps, View } from 'react-native'
|
|
43
|
+
|
|
44
|
+
import type { UseFloatingProps } from './floating'
|
|
45
|
+
import { useDismiss, useFloating, useFocus, useInteractions, useRole } from './floating'
|
|
46
|
+
|
|
47
|
+
const POPOVER_NAME = 'Popover'
|
|
48
|
+
|
|
49
|
+
type ScopedProps<P> = P & { __scopePopover?: Scope }
|
|
50
|
+
type NonNull<A> = Exclude<A, void | null>
|
|
51
|
+
|
|
52
|
+
export type PopoverProps = PopperProps & {
|
|
53
|
+
open?: boolean
|
|
54
|
+
defaultOpen?: boolean
|
|
55
|
+
onOpenChange?: (open: boolean) => void
|
|
56
|
+
sheetBreakpoint?: MediaQueryKey | false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type PopoverContextValue = {
|
|
60
|
+
triggerRef: React.RefObject<HTMLButtonElement>
|
|
61
|
+
contentId?: string
|
|
62
|
+
open: boolean
|
|
63
|
+
onOpenChange(open: boolean): void
|
|
64
|
+
onOpenToggle(): void
|
|
65
|
+
hasCustomAnchor: boolean
|
|
66
|
+
onCustomAnchorAdd(): void
|
|
67
|
+
onCustomAnchorRemove(): void
|
|
68
|
+
size?: SizeTokens
|
|
69
|
+
sheetBreakpoint: NonNull<PopoverProps['sheetBreakpoint']>
|
|
70
|
+
scopeKey: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [createPopoverContext, createPopoverScopeInternal] = createContextScope(POPOVER_NAME, [
|
|
74
|
+
createPopperScope,
|
|
75
|
+
])
|
|
76
|
+
export const usePopoverScope = createPopperScope()
|
|
77
|
+
export const createPopoverScope = createPopoverScopeInternal
|
|
78
|
+
|
|
79
|
+
const [PopoverProviderInternal, usePopoverInternalContext] =
|
|
80
|
+
createPopoverContext<PopoverContextValue>(POPOVER_NAME)
|
|
81
|
+
|
|
82
|
+
export const __PopoverProviderInternal = PopoverProviderInternal
|
|
83
|
+
|
|
84
|
+
/* -------------------------------------------------------------------------------------------------
|
|
85
|
+
* PopoverAnchor
|
|
86
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
87
|
+
|
|
88
|
+
const ANCHOR_NAME = 'PopoverAnchor'
|
|
89
|
+
|
|
90
|
+
type PopoverAnchorElement = HTMLElement | View
|
|
91
|
+
export type PopoverAnchorProps = YStackProps
|
|
92
|
+
|
|
93
|
+
export const PopoverAnchor = React.forwardRef<PopoverAnchorElement, PopoverAnchorProps>(
|
|
94
|
+
(props: ScopedProps<PopoverAnchorProps>, forwardedRef) => {
|
|
95
|
+
const { __scopePopover, ...anchorProps } = props
|
|
96
|
+
const context = usePopoverInternalContext(ANCHOR_NAME, __scopePopover)
|
|
97
|
+
const popperScope = usePopoverScope(__scopePopover)
|
|
98
|
+
const { onCustomAnchorAdd, onCustomAnchorRemove } = context
|
|
99
|
+
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
onCustomAnchorAdd()
|
|
102
|
+
return () => onCustomAnchorRemove()
|
|
103
|
+
}, [onCustomAnchorAdd, onCustomAnchorRemove])
|
|
104
|
+
|
|
105
|
+
return <PopperAnchor {...popperScope} {...anchorProps} ref={forwardedRef} />
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
PopoverAnchor.displayName = ANCHOR_NAME
|
|
110
|
+
|
|
111
|
+
/* -------------------------------------------------------------------------------------------------
|
|
112
|
+
* PopoverTrigger
|
|
113
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
114
|
+
|
|
115
|
+
const TRIGGER_NAME = 'PopoverTrigger'
|
|
116
|
+
|
|
117
|
+
type PopoverTriggerElement = HTMLElement | View
|
|
118
|
+
export type PopoverTriggerProps = YStackProps
|
|
119
|
+
|
|
120
|
+
export const PopoverTrigger = React.forwardRef<PopoverTriggerElement, PopoverTriggerProps>(
|
|
121
|
+
(props: ScopedProps<PopoverTriggerProps>, forwardedRef) => {
|
|
122
|
+
const { __scopePopover, ...triggerProps } = props
|
|
123
|
+
const context = usePopoverInternalContext(TRIGGER_NAME, __scopePopover)
|
|
124
|
+
const popperScope = usePopoverScope(__scopePopover)
|
|
125
|
+
const composedTriggerRef = useComposedRefs(forwardedRef, context.triggerRef)
|
|
126
|
+
|
|
127
|
+
const trigger = (
|
|
128
|
+
<YStack
|
|
129
|
+
aria-haspopup="dialog"
|
|
130
|
+
aria-expanded={context.open}
|
|
131
|
+
// TODO not matching
|
|
132
|
+
// aria-controls={context.contentId}
|
|
133
|
+
data-state={getState(context.open)}
|
|
134
|
+
{...triggerProps}
|
|
135
|
+
ref={composedTriggerRef}
|
|
136
|
+
onPress={composeEventHandlers(props.onPress, context.onOpenToggle)}
|
|
137
|
+
/>
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return context.hasCustomAnchor ? (
|
|
141
|
+
trigger
|
|
142
|
+
) : (
|
|
143
|
+
<PopperAnchor asChild {...popperScope}>
|
|
144
|
+
{trigger}
|
|
145
|
+
</PopperAnchor>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
PopoverTrigger.displayName = TRIGGER_NAME
|
|
151
|
+
|
|
152
|
+
/* -------------------------------------------------------------------------------------------------
|
|
153
|
+
* PopoverContent
|
|
154
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
155
|
+
|
|
156
|
+
const CONTENT_NAME = 'PopoverContent'
|
|
157
|
+
|
|
158
|
+
export type PopoverContentProps = PopoverContentTypeProps
|
|
159
|
+
|
|
160
|
+
type PopoverContentTypeElement = PopoverContentImplElement
|
|
161
|
+
|
|
162
|
+
export interface PopoverContentTypeProps
|
|
163
|
+
extends Omit<PopoverContentImplProps, 'disableOutsidePointerEvents'> {
|
|
164
|
+
/**
|
|
165
|
+
* @see https://github.com/theKashey/react-remove-scroll#usage
|
|
166
|
+
*/
|
|
167
|
+
allowPinchZoom?: RemoveScrollProps['allowPinchZoom']
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const PopoverContent = React.forwardRef<PopoverContentTypeElement, PopoverContentTypeProps>(
|
|
171
|
+
(props: ScopedProps<PopoverContentTypeProps>, forwardedRef) => {
|
|
172
|
+
const { allowPinchZoom, trapFocus, disableRemoveScroll = true, ...contentModalProps } = props
|
|
173
|
+
const context = usePopoverInternalContext(CONTENT_NAME, props.__scopePopover)
|
|
174
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
175
|
+
const composedRefs = useComposedRefs(forwardedRef, contentRef)
|
|
176
|
+
const isRightClickOutsideRef = React.useRef(false)
|
|
177
|
+
const themeName = useThemeName()
|
|
178
|
+
|
|
179
|
+
// aria-hide everything except the content (better supported equivalent to setting aria-modal)
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
if (!context.open) return
|
|
182
|
+
const content = contentRef.current
|
|
183
|
+
if (content) return hideOthers(content)
|
|
184
|
+
}, [context.open])
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<Portal zIndex={props.zIndex ?? 1000}>
|
|
188
|
+
<Theme name={themeName}>
|
|
189
|
+
<PopoverContentImpl
|
|
190
|
+
{...contentModalProps}
|
|
191
|
+
disableRemoveScroll={disableRemoveScroll}
|
|
192
|
+
ref={composedRefs}
|
|
193
|
+
// we make sure we're not trapping once it's been closed
|
|
194
|
+
// (closed !== unmounted when animating out)
|
|
195
|
+
trapFocus={trapFocus ?? context.open}
|
|
196
|
+
disableOutsidePointerEvents
|
|
197
|
+
onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
|
|
198
|
+
event.preventDefault()
|
|
199
|
+
if (!isRightClickOutsideRef.current) context.triggerRef.current?.focus()
|
|
200
|
+
})}
|
|
201
|
+
onPointerDownOutside={composeEventHandlers(
|
|
202
|
+
props.onPointerDownOutside,
|
|
203
|
+
(event) => {
|
|
204
|
+
const originalEvent = event.detail.originalEvent
|
|
205
|
+
const ctrlLeftClick = originalEvent.button === 0 && originalEvent.ctrlKey === true
|
|
206
|
+
const isRightClick = originalEvent.button === 2 || ctrlLeftClick
|
|
207
|
+
isRightClickOutsideRef.current = isRightClick
|
|
208
|
+
},
|
|
209
|
+
{ checkDefaultPrevented: false }
|
|
210
|
+
)}
|
|
211
|
+
// When focus is trapped, a `focusout` event may still happen.
|
|
212
|
+
// We make sure we don't trigger our `onDismiss` in such case.
|
|
213
|
+
onFocusOutside={composeEventHandlers(
|
|
214
|
+
props.onFocusOutside,
|
|
215
|
+
(event) => event.preventDefault(),
|
|
216
|
+
{ checkDefaultPrevented: false }
|
|
217
|
+
)}
|
|
218
|
+
/>
|
|
219
|
+
</Theme>
|
|
220
|
+
</Portal>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
226
|
+
|
|
227
|
+
type PopoverContentImplElement = React.ElementRef<typeof PopperContent>
|
|
228
|
+
|
|
229
|
+
export interface PopoverContentImplProps
|
|
230
|
+
extends PopperContentProps,
|
|
231
|
+
Omit<DismissableProps, 'onDismiss' | 'children'> {
|
|
232
|
+
/**
|
|
233
|
+
* Whether focus should be trapped within the `Popover`
|
|
234
|
+
* (default: false)
|
|
235
|
+
*/
|
|
236
|
+
trapFocus?: FocusScopeProps['trapped']
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Event handler called when auto-focusing on open.
|
|
240
|
+
* Can be prevented.
|
|
241
|
+
*/
|
|
242
|
+
onOpenAutoFocus?: FocusScopeProps['onMountAutoFocus']
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Event handler called when auto-focusing on close.
|
|
246
|
+
* Can be prevented.
|
|
247
|
+
*/
|
|
248
|
+
onCloseAutoFocus?: FocusScopeProps['onUnmountAutoFocus']
|
|
249
|
+
|
|
250
|
+
disableRemoveScroll?: boolean
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const PopoverContentImpl = React.forwardRef<PopoverContentImplElement, PopoverContentImplProps>(
|
|
254
|
+
(props: ScopedProps<PopoverContentImplProps>, forwardedRef) => {
|
|
255
|
+
const {
|
|
256
|
+
__scopePopover,
|
|
257
|
+
trapFocus,
|
|
258
|
+
onOpenAutoFocus,
|
|
259
|
+
onCloseAutoFocus,
|
|
260
|
+
disableOutsidePointerEvents,
|
|
261
|
+
onEscapeKeyDown,
|
|
262
|
+
onPointerDownOutside,
|
|
263
|
+
onFocusOutside,
|
|
264
|
+
onInteractOutside,
|
|
265
|
+
children,
|
|
266
|
+
disableRemoveScroll,
|
|
267
|
+
...contentProps
|
|
268
|
+
} = props
|
|
269
|
+
const popperScope = usePopoverScope(__scopePopover)
|
|
270
|
+
const context = usePopoverInternalContext(CONTENT_NAME, popperScope.__scopePopover)
|
|
271
|
+
const showSheet = useShowPopoverSheet(context)
|
|
272
|
+
// const popperContext = usePopperContext(CONTENT_NAME, popperScope.__scopePopper)
|
|
273
|
+
|
|
274
|
+
if (showSheet) {
|
|
275
|
+
// unwrap the PopoverScrollView if used, as it will use the SheetScrollView if that exists
|
|
276
|
+
const childrenWithoutScrollView = React.Children.toArray(children).map((child) => {
|
|
277
|
+
if (React.isValidElement(child)) {
|
|
278
|
+
if (child.type === PopoverScrollView) {
|
|
279
|
+
return child.props.children
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return child
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// doesn't show as popover yet on native, must use as sheet
|
|
286
|
+
return (
|
|
287
|
+
<PortalItem hostName={`${context.scopeKey}SheetContents`}>
|
|
288
|
+
{childrenWithoutScrollView}
|
|
289
|
+
</PortalItem>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// const handleDismiss = React.useCallback(() => context.onOpenChange(false), [])
|
|
294
|
+
// <Dismissable
|
|
295
|
+
// disableOutsidePointerEvents={disableOutsidePointerEvents}
|
|
296
|
+
// // onInteractOutside={onInteractOutside}
|
|
297
|
+
// onEscapeKeyDown={onEscapeKeyDown}
|
|
298
|
+
// // onPointerDownOutside={onPointerDownOutside}
|
|
299
|
+
// // onFocusOutside={onFocusOutside}
|
|
300
|
+
// onDismiss={handleDismiss}
|
|
301
|
+
// >
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<AnimatePresence>
|
|
305
|
+
{!!context.open && (
|
|
306
|
+
<PopperContent
|
|
307
|
+
key="popper-content"
|
|
308
|
+
data-state={getState(context.open)}
|
|
309
|
+
id={context.contentId}
|
|
310
|
+
pointerEvents="auto"
|
|
311
|
+
{...popperScope}
|
|
312
|
+
{...contentProps}
|
|
313
|
+
ref={forwardedRef}
|
|
314
|
+
>
|
|
315
|
+
<RemoveScroll
|
|
316
|
+
enabled={disableRemoveScroll ? false : context.open}
|
|
317
|
+
allowPinchZoom
|
|
318
|
+
// causes lots of bugs on touch web on site
|
|
319
|
+
removeScrollBar={false}
|
|
320
|
+
style={{
|
|
321
|
+
display: 'contents',
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
{trapFocus === false ? (
|
|
325
|
+
children
|
|
326
|
+
) : (
|
|
327
|
+
<FocusScope
|
|
328
|
+
loop
|
|
329
|
+
trapped={trapFocus ?? context.open}
|
|
330
|
+
onMountAutoFocus={onOpenAutoFocus}
|
|
331
|
+
onUnmountAutoFocus={onCloseAutoFocus}
|
|
332
|
+
>
|
|
333
|
+
<div style={{ display: 'contents' }}>{children}</div>
|
|
334
|
+
</FocusScope>
|
|
335
|
+
)}
|
|
336
|
+
</RemoveScroll>
|
|
337
|
+
</PopperContent>
|
|
338
|
+
)}
|
|
339
|
+
</AnimatePresence>
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
/* -------------------------------------------------------------------------------------------------
|
|
345
|
+
* PopoverClose
|
|
346
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
347
|
+
|
|
348
|
+
const CLOSE_NAME = 'PopoverClose'
|
|
349
|
+
|
|
350
|
+
type PopoverCloseElement = HTMLElement | View
|
|
351
|
+
export type PopoverCloseProps = YStackProps
|
|
352
|
+
|
|
353
|
+
export const PopoverClose = React.forwardRef<PopoverCloseElement, PopoverCloseProps>(
|
|
354
|
+
(props: ScopedProps<PopoverCloseProps>, forwardedRef) => {
|
|
355
|
+
const { __scopePopover, ...closeProps } = props
|
|
356
|
+
const context = usePopoverInternalContext(CLOSE_NAME, __scopePopover)
|
|
357
|
+
return (
|
|
358
|
+
<YStack
|
|
359
|
+
{...closeProps}
|
|
360
|
+
ref={forwardedRef}
|
|
361
|
+
onPress={composeEventHandlers(props.onPress, () => context.onOpenChange(false))}
|
|
362
|
+
/>
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
PopoverClose.displayName = CLOSE_NAME
|
|
368
|
+
|
|
369
|
+
/* -------------------------------------------------------------------------------------------------
|
|
370
|
+
* PopoverArrow
|
|
371
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
372
|
+
|
|
373
|
+
const ARROW_NAME = 'PopoverArrow'
|
|
374
|
+
|
|
375
|
+
type PopoverArrowElement = HTMLElement | View
|
|
376
|
+
export type PopoverArrowProps = PopperArrowProps
|
|
377
|
+
|
|
378
|
+
export const PopoverArrow = React.forwardRef<PopoverArrowElement, PopoverArrowProps>(
|
|
379
|
+
(props: ScopedProps<PopoverArrowProps>, forwardedRef) => {
|
|
380
|
+
const { __scopePopover, ...arrowProps } = props
|
|
381
|
+
const popperScope = usePopoverScope(__scopePopover)
|
|
382
|
+
return <PopperArrow {...popperScope} {...arrowProps} ref={forwardedRef} />
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
PopoverArrow.displayName = ARROW_NAME
|
|
387
|
+
|
|
388
|
+
/* -------------------------------------------------------------------------------------------------
|
|
389
|
+
* PopoverSheetContents
|
|
390
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
391
|
+
|
|
392
|
+
const SHEET_CONTENTS_NAME = 'PopoverSheetContents'
|
|
393
|
+
|
|
394
|
+
export const PopoverSheetContents = ({ __scopePopover }: ScopedProps<{}>) => {
|
|
395
|
+
const context = usePopoverInternalContext(SHEET_CONTENTS_NAME, __scopePopover)
|
|
396
|
+
return <PortalHost name={`${context.scopeKey}SheetContents`}></PortalHost>
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
PopoverSheetContents.displayName = SHEET_CONTENTS_NAME
|
|
400
|
+
|
|
401
|
+
/* -------------------------------------------------------------------------------------------------
|
|
402
|
+
* PopoverScrollView
|
|
403
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
404
|
+
|
|
405
|
+
const PopoverScrollView = React.forwardRef<ScrollView, ScrollViewProps>((props, ref) => {
|
|
406
|
+
return <ScrollView ref={ref} {...props} />
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
/* -------------------------------------------------------------------------------------------------
|
|
410
|
+
* Popover
|
|
411
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
412
|
+
|
|
413
|
+
export const Popover = withStaticProperties(
|
|
414
|
+
((props: ScopedProps<PopoverProps>) => {
|
|
415
|
+
const {
|
|
416
|
+
__scopePopover,
|
|
417
|
+
children,
|
|
418
|
+
open: openProp,
|
|
419
|
+
defaultOpen,
|
|
420
|
+
onOpenChange,
|
|
421
|
+
sheetBreakpoint = false,
|
|
422
|
+
...restProps
|
|
423
|
+
} = props
|
|
424
|
+
const popperScope = usePopoverScope(__scopePopover)
|
|
425
|
+
const triggerRef = React.useRef<HTMLButtonElement>(null)
|
|
426
|
+
const [hasCustomAnchor, setHasCustomAnchor] = React.useState(false)
|
|
427
|
+
const [open, setOpen] = useControllableState({
|
|
428
|
+
prop: openProp,
|
|
429
|
+
defaultProp: defaultOpen || false,
|
|
430
|
+
onChange: onOpenChange,
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
const breakpointActive = useSheetBreakpointActive(sheetBreakpoint)
|
|
434
|
+
|
|
435
|
+
const useFloatingContext = React.useCallback(
|
|
436
|
+
(props: UseFloatingProps) => {
|
|
437
|
+
const floating = useFloating({
|
|
438
|
+
...props,
|
|
439
|
+
open,
|
|
440
|
+
onOpenChange: setOpen,
|
|
441
|
+
})
|
|
442
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
443
|
+
useFocus(floating.context, {
|
|
444
|
+
enabled: !breakpointActive,
|
|
445
|
+
}),
|
|
446
|
+
useRole(floating.context, { role: 'dialog' }),
|
|
447
|
+
useDismiss(floating.context, {
|
|
448
|
+
enabled: !breakpointActive,
|
|
449
|
+
}),
|
|
450
|
+
])
|
|
451
|
+
return {
|
|
452
|
+
...floating,
|
|
453
|
+
getReferenceProps,
|
|
454
|
+
getFloatingProps,
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
[breakpointActive, open, setOpen]
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<FloatingOverrideContext.Provider value={useFloatingContext as any}>
|
|
462
|
+
<Popper {...popperScope} stayInFrame {...restProps}>
|
|
463
|
+
<PopoverProviderInternal
|
|
464
|
+
scope={__scopePopover}
|
|
465
|
+
scopeKey={__scopePopover ? Object.keys(__scopePopover)[0] : ''}
|
|
466
|
+
sheetBreakpoint={sheetBreakpoint}
|
|
467
|
+
contentId={useId()}
|
|
468
|
+
triggerRef={triggerRef}
|
|
469
|
+
open={open}
|
|
470
|
+
onOpenChange={setOpen}
|
|
471
|
+
onOpenToggle={useEvent(() => {
|
|
472
|
+
if (open && breakpointActive) {
|
|
473
|
+
return
|
|
474
|
+
}
|
|
475
|
+
setOpen(!open)
|
|
476
|
+
})}
|
|
477
|
+
hasCustomAnchor={hasCustomAnchor}
|
|
478
|
+
onCustomAnchorAdd={React.useCallback(() => setHasCustomAnchor(true), [])}
|
|
479
|
+
onCustomAnchorRemove={React.useCallback(() => setHasCustomAnchor(false), [])}
|
|
480
|
+
>
|
|
481
|
+
<PopoverSheetController onOpenChange={setOpen} __scopePopover={__scopePopover}>
|
|
482
|
+
{children}
|
|
483
|
+
</PopoverSheetController>
|
|
484
|
+
</PopoverProviderInternal>
|
|
485
|
+
</Popper>
|
|
486
|
+
</FloatingOverrideContext.Provider>
|
|
487
|
+
)
|
|
488
|
+
}) as React.FC<PopoverProps>,
|
|
489
|
+
{
|
|
490
|
+
Anchor: PopoverAnchor,
|
|
491
|
+
Arrow: PopoverArrow,
|
|
492
|
+
Trigger: PopoverTrigger,
|
|
493
|
+
Content: PopoverContent,
|
|
494
|
+
Close: PopoverClose,
|
|
495
|
+
SheetContents: PopoverSheetContents,
|
|
496
|
+
ScrollView: PopoverScrollView,
|
|
497
|
+
Sheet: ControlledSheet,
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
Popover.displayName = POPOVER_NAME
|
|
502
|
+
|
|
503
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
504
|
+
|
|
505
|
+
function getState(open: boolean) {
|
|
506
|
+
return open ? 'open' : 'closed'
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const PopoverSheetController = (
|
|
510
|
+
props: ScopedProps<{
|
|
511
|
+
children: React.ReactNode
|
|
512
|
+
onOpenChange: React.Dispatch<React.SetStateAction<boolean>>
|
|
513
|
+
}>
|
|
514
|
+
) => {
|
|
515
|
+
const context = usePopoverInternalContext('PopoverSheetController', props.__scopePopover)
|
|
516
|
+
const showSheet = useShowPopoverSheet(context)
|
|
517
|
+
const breakpointActive = useSheetBreakpointActive(context.sheetBreakpoint)
|
|
518
|
+
const getShowSheet = useGet(showSheet)
|
|
519
|
+
return (
|
|
520
|
+
<SheetController
|
|
521
|
+
onOpenChange={(val) => {
|
|
522
|
+
if (getShowSheet()) {
|
|
523
|
+
props.onOpenChange(val)
|
|
524
|
+
}
|
|
525
|
+
}}
|
|
526
|
+
open={context.open}
|
|
527
|
+
hidden={breakpointActive === false}
|
|
528
|
+
>
|
|
529
|
+
{props.children}
|
|
530
|
+
</SheetController>
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const useSheetBreakpointActive = (breakpoint?: MediaQueryKey | false) => {
|
|
535
|
+
const media = useMedia()
|
|
536
|
+
return breakpoint ? media[breakpoint] : false
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const useShowPopoverSheet = (context: PopoverContextValue) => {
|
|
540
|
+
// for now always show as sheet on native
|
|
541
|
+
if (!isWeb) return true
|
|
542
|
+
const breakpointActive = useSheetBreakpointActive(context.sheetBreakpoint)
|
|
543
|
+
return context.open === false ? false : breakpointActive
|
|
544
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const useDismiss = () => {}
|
|
2
|
+
export const useFloating = () => {
|
|
3
|
+
return {
|
|
4
|
+
context: {},
|
|
5
|
+
reference: () => {},
|
|
6
|
+
floating: () => {},
|
|
7
|
+
refs: {
|
|
8
|
+
floating: {},
|
|
9
|
+
reference: {},
|
|
10
|
+
},
|
|
11
|
+
middlewareData: {},
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const useFocus = () => {}
|
|
15
|
+
export const useInteractions = () => {
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
export const useRole = () => {}
|
package/src/floating.ts
ADDED
package/src/index.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Popover'
|