@tamagui/popper 1.0.1-beta.59 → 1.0.1-beta.60

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/popper",
3
- "version": "1.0.1-beta.59",
3
+ "version": "1.0.1-beta.60",
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.59",
24
- "@tamagui/core": "^1.0.1-beta.59",
25
- "@tamagui/create-context": "^1.0.1-beta.59",
26
- "@tamagui/stacks": "^1.0.1-beta.59",
27
- "@tamagui/use-controllable-state": "^1.0.1-beta.59"
24
+ "@tamagui/compose-refs": "^1.0.1-beta.60",
25
+ "@tamagui/core": "^1.0.1-beta.60",
26
+ "@tamagui/create-context": "^1.0.1-beta.60",
27
+ "@tamagui/stacks": "^1.0.1-beta.60",
28
+ "@tamagui/use-controllable-state": "^1.0.1-beta.60"
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.59",
36
+ "@tamagui/build": "^1.0.1-beta.60",
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'
@@ -0,0 +1 @@
1
+ export * from '@floating-ui/react-dom'
package/src/index.tsx ADDED
@@ -0,0 +1,2 @@
1
+ export { FloatingOverrideContext, type UseFloatingFn } from './useFloating'
2
+ export * from './Popper'
@@ -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
+ }