@tamagui/popper 1.0.1-beta.59 → 1.0.1-beta.62
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/package.json +8 -7
- package/src/Popper.tsx +308 -0
- package/src/floating.native.tsx +1 -0
- package/src/floating.tsx +1 -0
- package/src/index.tsx +2 -0
- package/src/useFloating.tsx +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/popper",
|
|
3
|
-
"version": "1.0.1-beta.
|
|
3
|
+
"version": "1.0.1-beta.62",
|
|
4
4
|
"sideEffects": true,
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"types": "./types/index.d.ts",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"module": "dist/esm",
|
|
9
9
|
"module:jsx": "dist/jsx",
|
|
10
10
|
"files": [
|
|
11
|
+
"src",
|
|
11
12
|
"types",
|
|
12
13
|
"dist"
|
|
13
14
|
],
|
|
@@ -20,11 +21,11 @@
|
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@floating-ui/react-dom": "0.7.0",
|
|
22
23
|
"@floating-ui/react-native": "^0.7.0",
|
|
23
|
-
"@tamagui/compose-refs": "^1.0.1-beta.
|
|
24
|
-
"@tamagui/core": "^1.0.1-beta.
|
|
25
|
-
"@tamagui/create-context": "^1.0.1-beta.
|
|
26
|
-
"@tamagui/stacks": "^1.0.1-beta.
|
|
27
|
-
"@tamagui/use-controllable-state": "^1.0.1-beta.
|
|
24
|
+
"@tamagui/compose-refs": "^1.0.1-beta.62",
|
|
25
|
+
"@tamagui/core": "^1.0.1-beta.62",
|
|
26
|
+
"@tamagui/create-context": "^1.0.1-beta.62",
|
|
27
|
+
"@tamagui/stacks": "^1.0.1-beta.62",
|
|
28
|
+
"@tamagui/use-controllable-state": "^1.0.1-beta.62"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"react": "*",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"react-native": "*"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@tamagui/build": "^1.0.1-beta.
|
|
36
|
+
"@tamagui/build": "^1.0.1-beta.62",
|
|
36
37
|
"@types/react-native": "^0.67.3",
|
|
37
38
|
"react": "*",
|
|
38
39
|
"react-dom": "*",
|
package/src/Popper.tsx
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// adapted from radix-ui popper
|
|
2
|
+
|
|
3
|
+
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
4
|
+
import {
|
|
5
|
+
SizeTokens,
|
|
6
|
+
StackProps,
|
|
7
|
+
getTokens,
|
|
8
|
+
getVariableValue,
|
|
9
|
+
stepTokenUpOrDown,
|
|
10
|
+
styled,
|
|
11
|
+
useIsomorphicLayoutEffect,
|
|
12
|
+
} from '@tamagui/core'
|
|
13
|
+
import { Scope, createContextScope } from '@tamagui/create-context'
|
|
14
|
+
import { SizableStack, SizableStackProps, YStack, YStackProps } from '@tamagui/stacks'
|
|
15
|
+
import * as React from 'react'
|
|
16
|
+
import { View } from 'react-native'
|
|
17
|
+
|
|
18
|
+
import { Coords, Placement, arrow, autoUpdate, flip, offset, shift } from './floating'
|
|
19
|
+
import { UseFloatingResult, useFloating } from './useFloating'
|
|
20
|
+
|
|
21
|
+
type ShiftProps = typeof shift extends (options: infer Opts) => void ? Opts : never
|
|
22
|
+
type FlipProps = typeof flip extends (options: infer Opts) => void ? Opts : never
|
|
23
|
+
|
|
24
|
+
/* -------------------------------------------------------------------------------------------------
|
|
25
|
+
* Popper
|
|
26
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
27
|
+
|
|
28
|
+
const POPPER_NAME = 'Popper'
|
|
29
|
+
|
|
30
|
+
type ScopedProps<P> = P & { __scopePopper?: Scope }
|
|
31
|
+
const [createPopperContext, createScope] = createContextScope(POPPER_NAME)
|
|
32
|
+
|
|
33
|
+
export const createPopperScope = createScope
|
|
34
|
+
|
|
35
|
+
type PopperContextValue = UseFloatingResult & {
|
|
36
|
+
isMounted: boolean
|
|
37
|
+
anchorRef: any
|
|
38
|
+
size?: SizeTokens
|
|
39
|
+
placement?: Placement
|
|
40
|
+
arrowRef: any
|
|
41
|
+
onArrowSize?: (val: number) => void
|
|
42
|
+
arrowStyle?: Partial<Coords> & {
|
|
43
|
+
centerOffset: number
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const [PopperProvider, usePopperContext] = createPopperContext<PopperContextValue>(POPPER_NAME)
|
|
47
|
+
|
|
48
|
+
export type PopperProps = {
|
|
49
|
+
size?: SizeTokens
|
|
50
|
+
children?: React.ReactNode
|
|
51
|
+
placement?: Placement
|
|
52
|
+
stayInFrame?: ShiftProps
|
|
53
|
+
allowFlip?: FlipProps
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Popper: React.FC<PopperProps> = (props: ScopedProps<PopperProps>) => {
|
|
57
|
+
const { __scopePopper, children, size, placement = 'bottom', stayInFrame, allowFlip } = props
|
|
58
|
+
|
|
59
|
+
const [isMounted, setIsMounted] = React.useState(false)
|
|
60
|
+
useIsomorphicLayoutEffect(() => {
|
|
61
|
+
setIsMounted(true)
|
|
62
|
+
}, [])
|
|
63
|
+
|
|
64
|
+
const anchorRef = React.useRef<any>()
|
|
65
|
+
const [arrowEl, setArrow] = React.useState<HTMLSpanElement | null>(null)
|
|
66
|
+
const [arrowSize, setArrowSize] = React.useState(0)
|
|
67
|
+
const arrowRef = React.useRef()
|
|
68
|
+
|
|
69
|
+
const floating = useFloating({
|
|
70
|
+
placement,
|
|
71
|
+
middleware: [
|
|
72
|
+
stayInFrame ? shift(stayInFrame) : (null as any),
|
|
73
|
+
allowFlip ? flip(allowFlip) : (null as any),
|
|
74
|
+
arrowEl ? arrow({ element: arrowEl }) : (null as any),
|
|
75
|
+
arrowSize ? offset(arrowSize) : (null as any),
|
|
76
|
+
].filter(Boolean),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const { refs, middlewareData } = floating
|
|
80
|
+
|
|
81
|
+
const composedArrowRefs = useComposedRefs<any>(arrowRef, setArrow)
|
|
82
|
+
|
|
83
|
+
useIsomorphicLayoutEffect(() => {
|
|
84
|
+
floating.reference(anchorRef.current)
|
|
85
|
+
}, [anchorRef])
|
|
86
|
+
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
if (!refs.reference.current || !refs.floating.current) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
// Only call this when the floating element is rendered
|
|
92
|
+
return autoUpdate(refs.reference.current, refs.floating.current, floating.update)
|
|
93
|
+
}, [refs.floating.current, refs.reference.current])
|
|
94
|
+
|
|
95
|
+
const arrowStyle = React.useMemo(() => {
|
|
96
|
+
return middlewareData.arrow
|
|
97
|
+
}, [JSON.stringify(middlewareData.arrow || {})])
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<PopperProvider
|
|
101
|
+
scope={__scopePopper}
|
|
102
|
+
anchorRef={anchorRef}
|
|
103
|
+
size={size}
|
|
104
|
+
arrowRef={composedArrowRefs}
|
|
105
|
+
arrowStyle={arrowStyle}
|
|
106
|
+
onArrowSize={setArrowSize}
|
|
107
|
+
isMounted={isMounted}
|
|
108
|
+
{...floating}
|
|
109
|
+
>
|
|
110
|
+
{children}
|
|
111
|
+
</PopperProvider>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Popper.displayName = POPPER_NAME
|
|
116
|
+
|
|
117
|
+
/* -------------------------------------------------------------------------------------------------
|
|
118
|
+
* PopperAnchor
|
|
119
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
120
|
+
|
|
121
|
+
const ANCHOR_NAME = 'PopperAnchor'
|
|
122
|
+
|
|
123
|
+
type PopperAnchorRef = HTMLElement | View
|
|
124
|
+
|
|
125
|
+
export type PopperAnchorProps = YStackProps & {
|
|
126
|
+
virtualRef?: React.RefObject<any>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const PopperAnchor = React.forwardRef<PopperAnchorRef, PopperAnchorProps>(
|
|
130
|
+
(props: ScopedProps<PopperAnchorProps>, forwardedRef) => {
|
|
131
|
+
const { __scopePopper, virtualRef, ...anchorProps } = props
|
|
132
|
+
const { anchorRef, getReferenceProps } = usePopperContext(ANCHOR_NAME, __scopePopper)
|
|
133
|
+
const ref = React.useRef<PopperAnchorRef>(null)
|
|
134
|
+
const composedRefs = useComposedRefs(forwardedRef, ref, anchorRef)
|
|
135
|
+
if (virtualRef) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
const stackProps = {
|
|
139
|
+
ref: composedRefs,
|
|
140
|
+
...anchorProps,
|
|
141
|
+
}
|
|
142
|
+
return <YStack {...(getReferenceProps ? getReferenceProps(stackProps) : stackProps)} />
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
PopperAnchor.displayName = ANCHOR_NAME
|
|
147
|
+
|
|
148
|
+
/* -------------------------------------------------------------------------------------------------
|
|
149
|
+
* PopperContent
|
|
150
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
151
|
+
|
|
152
|
+
const CONTENT_NAME = 'PopperContent'
|
|
153
|
+
|
|
154
|
+
type PopperContentElement = HTMLElement | View
|
|
155
|
+
|
|
156
|
+
export type PopperContentProps = SizableStackProps
|
|
157
|
+
|
|
158
|
+
const PopperContentFrame = styled(SizableStack, {
|
|
159
|
+
name: 'PopperContent',
|
|
160
|
+
backgroundColor: '$background',
|
|
161
|
+
alignItems: 'center',
|
|
162
|
+
|
|
163
|
+
defaultVariants: {
|
|
164
|
+
size: '$4',
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
export const PopperContent = PopperContentFrame.extractable(
|
|
169
|
+
React.forwardRef<PopperContentElement, PopperContentProps>(
|
|
170
|
+
(props: ScopedProps<PopperContentProps>, forwardedRef) => {
|
|
171
|
+
const { __scopePopper, ...contentProps } = props
|
|
172
|
+
const { strategy, placement, floating, x, y, getFloatingProps, size, isMounted } =
|
|
173
|
+
usePopperContext(CONTENT_NAME, __scopePopper)
|
|
174
|
+
const contentRefs = useComposedRefs<any>(floating, forwardedRef)
|
|
175
|
+
|
|
176
|
+
const contents = React.useMemo(() => {
|
|
177
|
+
return (
|
|
178
|
+
<PopperContentFrame
|
|
179
|
+
key="popper-content-frame"
|
|
180
|
+
data-placement={placement}
|
|
181
|
+
data-strategy={strategy}
|
|
182
|
+
{...contentProps}
|
|
183
|
+
size={contentProps.size ?? size}
|
|
184
|
+
/>
|
|
185
|
+
)
|
|
186
|
+
}, [placement, strategy, props])
|
|
187
|
+
|
|
188
|
+
// all poppers hidden on ssr by default
|
|
189
|
+
if (!isMounted) {
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const frameProps = {
|
|
194
|
+
ref: contentRefs,
|
|
195
|
+
x: (x as any) || 0,
|
|
196
|
+
y: (y as any) || 0,
|
|
197
|
+
position: 'absolute',
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// outer frame because we explicitly dont want animation to apply to this
|
|
201
|
+
return (
|
|
202
|
+
<YStack {...(getFloatingProps ? getFloatingProps(frameProps) : frameProps)}>
|
|
203
|
+
{contents}
|
|
204
|
+
</YStack>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
PopperContent.displayName = CONTENT_NAME
|
|
211
|
+
|
|
212
|
+
/* -------------------------------------------------------------------------------------------------
|
|
213
|
+
* PopperArrow
|
|
214
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
215
|
+
|
|
216
|
+
const ARROW_NAME = 'PopperArrow'
|
|
217
|
+
|
|
218
|
+
type PopperArrowElement = HTMLElement | View
|
|
219
|
+
|
|
220
|
+
export type PopperArrowProps = YStackProps & {
|
|
221
|
+
offset?: number
|
|
222
|
+
size?: SizeTokens
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const PopperArrowFrame = styled(YStack, {
|
|
226
|
+
name: 'PopperArrow',
|
|
227
|
+
borderColor: '$borderColor',
|
|
228
|
+
backgroundColor: '$background',
|
|
229
|
+
position: 'absolute',
|
|
230
|
+
zIndex: 0,
|
|
231
|
+
pointerEvents: 'none',
|
|
232
|
+
// TODO bug not applying
|
|
233
|
+
// rotate: '45deg',
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const opposites = {
|
|
237
|
+
top: 'bottom',
|
|
238
|
+
right: 'left',
|
|
239
|
+
bottom: 'top',
|
|
240
|
+
left: 'right',
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const PopperArrow = PopperArrowFrame.extractable(
|
|
244
|
+
React.forwardRef<PopperArrowElement, PopperArrowProps>(function PopperArrow(
|
|
245
|
+
props: ScopedProps<PopperArrowProps>,
|
|
246
|
+
forwardedRef
|
|
247
|
+
) {
|
|
248
|
+
const { __scopePopper, offset, size: sizeProp, borderWidth = 0, ...arrowProps } = props
|
|
249
|
+
const context = usePopperContext(ARROW_NAME, __scopePopper)
|
|
250
|
+
const tokens = getTokens()
|
|
251
|
+
const sizeVal = sizeProp ?? context.size
|
|
252
|
+
const sizeValResolved = getVariableValue(stepTokenUpOrDown(tokens.space, sizeVal, 0))
|
|
253
|
+
const size = +sizeValResolved
|
|
254
|
+
const { placement } = context
|
|
255
|
+
const { x, y } = context.arrowStyle || { x: 0, y: 0 }
|
|
256
|
+
const refs = useComposedRefs(context.arrowRef, forwardedRef)
|
|
257
|
+
|
|
258
|
+
const primaryPlacement = placement ? placement.split('-')[0] : 'top'
|
|
259
|
+
|
|
260
|
+
let arrowStyle: StackProps = { x, y, width: size, height: size }
|
|
261
|
+
if (primaryPlacement) {
|
|
262
|
+
const oppSide = opposites[primaryPlacement]
|
|
263
|
+
if (oppSide) {
|
|
264
|
+
arrowStyle[oppSide] = -size / 2
|
|
265
|
+
}
|
|
266
|
+
if (oppSide === 'top' || oppSide === 'bottom') {
|
|
267
|
+
arrowStyle.left = 0
|
|
268
|
+
}
|
|
269
|
+
if (oppSide === 'left' || oppSide === 'right') {
|
|
270
|
+
arrowStyle.top = 0
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// send the Arrow's offset up to Popper
|
|
275
|
+
useIsomorphicLayoutEffect(() => {
|
|
276
|
+
context.onArrowSize?.(size)
|
|
277
|
+
}, [size, context.onArrowSize])
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<PopperArrowFrame
|
|
281
|
+
{...arrowProps}
|
|
282
|
+
ref={refs}
|
|
283
|
+
{...arrowStyle}
|
|
284
|
+
rotate="45deg"
|
|
285
|
+
{...(primaryPlacement === 'top' && {
|
|
286
|
+
borderBottomWidth: borderWidth,
|
|
287
|
+
borderRightWidth: borderWidth,
|
|
288
|
+
})}
|
|
289
|
+
{...(primaryPlacement === 'bottom' && {
|
|
290
|
+
borderTopWidth: borderWidth,
|
|
291
|
+
borderLeftWidth: borderWidth,
|
|
292
|
+
})}
|
|
293
|
+
{...(primaryPlacement === 'left' && {
|
|
294
|
+
borderTopWidth: borderWidth,
|
|
295
|
+
borderRightWidth: borderWidth,
|
|
296
|
+
})}
|
|
297
|
+
{...(primaryPlacement === 'right' && {
|
|
298
|
+
borderBottomWidth: borderWidth,
|
|
299
|
+
borderLeftWidth: borderWidth,
|
|
300
|
+
})}
|
|
301
|
+
/>
|
|
302
|
+
)
|
|
303
|
+
})
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
PopperArrow.displayName = ARROW_NAME
|
|
307
|
+
|
|
308
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@floating-ui/react-native'
|
package/src/floating.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@floating-ui/react-dom'
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
import * as Floating from './floating'
|
|
4
|
+
|
|
5
|
+
export type UseFloatingFn = typeof Floating.useFloating
|
|
6
|
+
|
|
7
|
+
type UseFloatingProps = UseFloatingFn extends (props: infer Props) => any ? Props : never
|
|
8
|
+
|
|
9
|
+
export type UseFloatingResult = ReturnType<UseFloatingFn> & {
|
|
10
|
+
getFloatingProps?: (props: { ref: any; [key: string]: any }) => any
|
|
11
|
+
getReferenceProps?: (props: { ref: any; [key: string]: any }) => any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const FloatingOverrideContext = createContext<UseFloatingFn | null>(null)
|
|
15
|
+
|
|
16
|
+
export const useFloating = (props: UseFloatingProps): UseFloatingResult => {
|
|
17
|
+
const context = useContext(FloatingOverrideContext)
|
|
18
|
+
return (context || Floating.useFloating)?.(props)
|
|
19
|
+
}
|