@brand-map/primitives 0.0.0-broken.0
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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.oxfmtrc.json +35 -0
- package/.oxlintrc.json +166 -0
- package/README.md +78 -0
- package/bun.lock +904 -0
- package/mise.toml +3 -0
- package/package.json +61 -0
- package/src/accordion/accordion.tsx +189 -0
- package/src/accordion/accordion.web.tsx +282 -0
- package/src/accordion/index.ts +2 -0
- package/src/accordion/types.ts +44 -0
- package/src/alert-dialog/alert-dialog.tsx +238 -0
- package/src/alert-dialog/alert-dialog.web.tsx +260 -0
- package/src/alert-dialog/index.ts +2 -0
- package/src/alert-dialog/types.ts +81 -0
- package/src/aspect-ratio/aspect-ratio.tsx +27 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/avatar/avatar.tsx +122 -0
- package/src/avatar/index.ts +2 -0
- package/src/avatar/types.ts +20 -0
- package/src/checkbox/checkbox.tsx +95 -0
- package/src/checkbox/checkbox.web.tsx +111 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox/types.ts +14 -0
- package/src/collapsible/collapsible.tsx +98 -0
- package/src/collapsible/collapsible.web.tsx +149 -0
- package/src/collapsible/index.ts +2 -0
- package/src/collapsible/types.ts +23 -0
- package/src/context-menu/context-menu.tsx +616 -0
- package/src/context-menu/context-menu.web.tsx +560 -0
- package/src/context-menu/index.ts +2 -0
- package/src/context-menu/types.ts +136 -0
- package/src/dialog/dialog.tsx +286 -0
- package/src/dialog/dialog.web.tsx +215 -0
- package/src/dialog/index.ts +2 -0
- package/src/dialog/types.ts +92 -0
- package/src/dropdown-menu/dropdown-menu.tsx +575 -0
- package/src/dropdown-menu/dropdown-menu.web.tsx +565 -0
- package/src/dropdown-menu/index.ts +2 -0
- package/src/dropdown-menu/types.ts +121 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-Isomorphic-layout-effect.tsx +12 -0
- package/src/hooks/use-augmented-ref.tsx +25 -0
- package/src/hooks/use-controllable-state.tsx +70 -0
- package/src/hooks/use-relative-position.tsx +175 -0
- package/src/hover-card/hover-card.tsx +255 -0
- package/src/hover-card/hover-card.web.tsx +161 -0
- package/src/hover-card/index.ts +2 -0
- package/src/hover-card/types.ts +56 -0
- package/src/label/index.ts +2 -0
- package/src/label/label.tsx +36 -0
- package/src/label/label.web.tsx +38 -0
- package/src/label/types.ts +24 -0
- package/src/menubar/index.ts +2 -0
- package/src/menubar/menubar.tsx +602 -0
- package/src/menubar/menubar.web.tsx +575 -0
- package/src/menubar/types.ts +126 -0
- package/src/navigation-menu/index.ts +2 -0
- package/src/navigation-menu/navigation-menu.tsx +302 -0
- package/src/navigation-menu/navigation-menu.web.tsx +259 -0
- package/src/navigation-menu/types.ts +85 -0
- package/src/popover/index.ts +2 -0
- package/src/popover/popover.tsx +279 -0
- package/src/popover/popover.web.tsx +217 -0
- package/src/popover/types.ts +44 -0
- package/src/portal/index.ts +1 -0
- package/src/portal/portal.tsx +56 -0
- package/src/progress/index.ts +2 -0
- package/src/progress/progress.tsx +59 -0
- package/src/progress/progress.web.tsx +46 -0
- package/src/progress/types.ts +14 -0
- package/src/radio-group/index.ts +2 -0
- package/src/radio-group/radio-group.tsx +106 -0
- package/src/radio-group/radio-group.web.tsx +85 -0
- package/src/radio-group/types.ts +24 -0
- package/src/select/index.ts +2 -0
- package/src/select/select.tsx +447 -0
- package/src/select/select.web.tsx +368 -0
- package/src/select/types.ts +145 -0
- package/src/separator/index.ts +2 -0
- package/src/separator/separator.tsx +21 -0
- package/src/separator/types.ts +10 -0
- package/src/slider/index.ts +2 -0
- package/src/slider/slider.tsx +77 -0
- package/src/slider/slider.web.tsx +75 -0
- package/src/slider/types.ts +39 -0
- package/src/slot/index.ts +1 -0
- package/src/slot/slot.tsx +224 -0
- package/src/switch/index.ts +2 -0
- package/src/switch/switch.tsx +49 -0
- package/src/switch/switch.web.tsx +60 -0
- package/src/switch/types.ts +19 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +121 -0
- package/src/tabs/index.ts +2 -0
- package/src/tabs/tabs.tsx +120 -0
- package/src/tabs/tabs.web.tsx +106 -0
- package/src/tabs/types.ts +37 -0
- package/src/toast/index.ts +2 -0
- package/src/toast/toast.tsx +124 -0
- package/src/toast/types.ts +20 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.tsx +35 -0
- package/src/toggle/toggle.web.tsx +36 -0
- package/src/toggle/types.ts +11 -0
- package/src/toggle-group/index.ts +2 -0
- package/src/toggle-group/toggle-group.tsx +100 -0
- package/src/toggle-group/toggle-group.web.tsx +103 -0
- package/src/toggle-group/types.ts +46 -0
- package/src/toolbar/index.ts +2 -0
- package/src/toolbar/toolbar.tsx +141 -0
- package/src/toolbar/toolbar.web.tsx +158 -0
- package/src/toolbar/types.ts +64 -0
- package/src/tooltip/index.ts +2 -0
- package/src/tooltip/tooltip.tsx +261 -0
- package/src/tooltip/tooltip.web.tsx +175 -0
- package/src/tooltip/types.ts +61 -0
- package/src/types/index.ts +141 -0
- package/src/utils/index.ts +69 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useRef, useImperativeHandle } from "react";
|
|
2
|
+
|
|
3
|
+
interface AugmentRefProps<T> {
|
|
4
|
+
ref: React.Ref<T>;
|
|
5
|
+
methods?: Record<string, (...args: any[]) => any>;
|
|
6
|
+
deps?: any[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useAugmentedRef<T>({ ref, methods, deps = [] }: AugmentRefProps<T>) {
|
|
10
|
+
const augmentedRef = useRef<T>(null);
|
|
11
|
+
|
|
12
|
+
useImperativeHandle(
|
|
13
|
+
ref,
|
|
14
|
+
() => {
|
|
15
|
+
if (typeof augmentedRef === "function" || !augmentedRef?.current) {
|
|
16
|
+
return {} as T;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { ...augmentedRef.current, ...methods };
|
|
20
|
+
},
|
|
21
|
+
deps,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return augmentedRef;
|
|
25
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// This project uses code from WorkOS/Radix Primitives.
|
|
2
|
+
// The code is licensed under the MIT License.
|
|
3
|
+
// https://github.com/radix-ui/primitives/tree/main
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
|
|
7
|
+
type UseControllableStateParams<T> = {
|
|
8
|
+
prop?: T | undefined;
|
|
9
|
+
defaultProp?: T | undefined;
|
|
10
|
+
onChange?: (state: T) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SetStateFn<T> = (prevState?: T) => T;
|
|
14
|
+
|
|
15
|
+
function useControllableState<T>({ prop, defaultProp, onChange = () => {} }: UseControllableStateParams<T>) {
|
|
16
|
+
const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({ defaultProp, onChange });
|
|
17
|
+
const isControlled = prop !== undefined;
|
|
18
|
+
const value = isControlled ? prop : uncontrolledProp;
|
|
19
|
+
const handleChange = useCallbackRef(onChange);
|
|
20
|
+
|
|
21
|
+
const setValue: React.Dispatch<React.SetStateAction<T | undefined>> = useCallback(
|
|
22
|
+
(nextValue) => {
|
|
23
|
+
if (!isControlled) {
|
|
24
|
+
setUncontrolledProp(nextValue);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isControlled) {
|
|
28
|
+
const setter = nextValue as SetStateFn<T>;
|
|
29
|
+
const value = typeof nextValue === "function" ? setter(prop) : nextValue;
|
|
30
|
+
if (value !== prop) handleChange(value as T);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[isControlled, prop, setUncontrolledProp, handleChange],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return [value, setValue] as const;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function useUncontrolledState<T>({ defaultProp, onChange }: Omit<UseControllableStateParams<T>, "prop">) {
|
|
40
|
+
const uncontrolledState = useState<T | undefined>(defaultProp);
|
|
41
|
+
const [value] = uncontrolledState;
|
|
42
|
+
const prevValueRef = useRef(value);
|
|
43
|
+
const handleChange = useCallbackRef(onChange);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (prevValueRef.current !== value) {
|
|
47
|
+
handleChange(value as T);
|
|
48
|
+
prevValueRef.current = value;
|
|
49
|
+
}
|
|
50
|
+
}, [value, prevValueRef, handleChange]);
|
|
51
|
+
|
|
52
|
+
return uncontrolledState;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
|
|
57
|
+
* prop or avoid re-executing effects when passed as a dependency
|
|
58
|
+
*/
|
|
59
|
+
function useCallbackRef<T extends (...args: any[]) => any>(callback: T | undefined): T {
|
|
60
|
+
const callbackRef = useRef(callback);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
callbackRef.current = callback;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// https://github.com/facebook/react/issues/19240
|
|
67
|
+
return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { useControllableState };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Dimensions, type LayoutRectangle, type ScaledSize } from "react-native";
|
|
3
|
+
|
|
4
|
+
import type { Insets } from "../types";
|
|
5
|
+
|
|
6
|
+
type UseRelativePositionArgs = Omit<GetContentStyleArgs, "triggerPosition" | "contentLayout" | "dimensions"> & {
|
|
7
|
+
triggerPosition: LayoutPosition | null;
|
|
8
|
+
contentLayout: LayoutRectangle | null;
|
|
9
|
+
disablePositioningStyle?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useRelativePosition({
|
|
13
|
+
align,
|
|
14
|
+
avoidCollisions,
|
|
15
|
+
triggerPosition,
|
|
16
|
+
contentLayout,
|
|
17
|
+
alignOffset,
|
|
18
|
+
insets,
|
|
19
|
+
sideOffset,
|
|
20
|
+
side,
|
|
21
|
+
disablePositioningStyle,
|
|
22
|
+
}: UseRelativePositionArgs) {
|
|
23
|
+
const dimensions = Dimensions.get("screen");
|
|
24
|
+
|
|
25
|
+
return React.useMemo(() => {
|
|
26
|
+
if (disablePositioningStyle) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
if (!triggerPosition || !contentLayout) {
|
|
30
|
+
return {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
opacity: 0,
|
|
33
|
+
top: dimensions.height,
|
|
34
|
+
zIndex: -9999999,
|
|
35
|
+
} as const;
|
|
36
|
+
}
|
|
37
|
+
return getContentStyle({
|
|
38
|
+
align,
|
|
39
|
+
avoidCollisions,
|
|
40
|
+
contentLayout,
|
|
41
|
+
side,
|
|
42
|
+
triggerPosition,
|
|
43
|
+
alignOffset,
|
|
44
|
+
insets,
|
|
45
|
+
sideOffset,
|
|
46
|
+
dimensions,
|
|
47
|
+
});
|
|
48
|
+
}, [align, avoidCollisions, side, alignOffset, insets, triggerPosition, contentLayout, dimensions.width, dimensions.height]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LayoutPosition {
|
|
52
|
+
pageY: number;
|
|
53
|
+
pageX: number;
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface GetPositionArgs {
|
|
59
|
+
dimensions: ScaledSize;
|
|
60
|
+
avoidCollisions: boolean;
|
|
61
|
+
triggerPosition: LayoutPosition;
|
|
62
|
+
contentLayout: LayoutRectangle;
|
|
63
|
+
insets?: Insets;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface GetSidePositionArgs extends GetPositionArgs {
|
|
67
|
+
side: "top" | "bottom";
|
|
68
|
+
sideOffset: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getSidePosition({ side, triggerPosition, contentLayout, sideOffset, insets, avoidCollisions, dimensions }: GetSidePositionArgs) {
|
|
72
|
+
const insetTop = insets?.top ?? 0;
|
|
73
|
+
const insetBottom = insets?.bottom ?? 0;
|
|
74
|
+
const positionTop = triggerPosition?.pageY - sideOffset - contentLayout.height;
|
|
75
|
+
const positionBottom = triggerPosition.pageY + triggerPosition.height + sideOffset;
|
|
76
|
+
|
|
77
|
+
if (!avoidCollisions) {
|
|
78
|
+
return {
|
|
79
|
+
top: side === "top" ? positionTop : positionBottom,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (side === "top") {
|
|
84
|
+
return {
|
|
85
|
+
top: Math.min(Math.max(insetTop, positionTop), dimensions.height - insetBottom - contentLayout.height),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
top: Math.min(dimensions.height - insetBottom - contentLayout.height, positionBottom),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface GetAlignPositionArgs extends GetPositionArgs {
|
|
95
|
+
align: "start" | "center" | "end";
|
|
96
|
+
alignOffset: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getAlignPosition({ align, avoidCollisions, contentLayout, triggerPosition, alignOffset, insets, dimensions }: GetAlignPositionArgs) {
|
|
100
|
+
const insetLeft = insets?.left ?? 0;
|
|
101
|
+
const insetRight = insets?.right ?? 0;
|
|
102
|
+
const maxContentWidth = dimensions.width - insetLeft - insetRight;
|
|
103
|
+
|
|
104
|
+
const contentWidth = Math.min(contentLayout.width, maxContentWidth);
|
|
105
|
+
|
|
106
|
+
let left = getLeftPosition(align, triggerPosition.pageX, triggerPosition.width, contentWidth, alignOffset, insetLeft, insetRight, dimensions);
|
|
107
|
+
|
|
108
|
+
if (avoidCollisions) {
|
|
109
|
+
const doesCollide = left < insetLeft || left + contentWidth > dimensions.width - insetRight;
|
|
110
|
+
if (doesCollide) {
|
|
111
|
+
const spaceLeft = left - insetLeft;
|
|
112
|
+
const spaceRight = dimensions.width - insetRight - (left + contentWidth);
|
|
113
|
+
|
|
114
|
+
if (spaceLeft > spaceRight && spaceLeft >= contentWidth) {
|
|
115
|
+
left = insetLeft;
|
|
116
|
+
} else if (spaceRight >= contentWidth) {
|
|
117
|
+
left = dimensions.width - insetRight - contentWidth;
|
|
118
|
+
} else {
|
|
119
|
+
const centeredPosition = Math.max(insetLeft, (dimensions.width - contentWidth - insetRight) / 2);
|
|
120
|
+
left = centeredPosition;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { left, maxWidth: maxContentWidth };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getLeftPosition(
|
|
129
|
+
align: "start" | "center" | "end",
|
|
130
|
+
triggerPageX: number,
|
|
131
|
+
triggerWidth: number,
|
|
132
|
+
contentWidth: number,
|
|
133
|
+
alignOffset: number,
|
|
134
|
+
insetLeft: number,
|
|
135
|
+
insetRight: number,
|
|
136
|
+
dimensions: ScaledSize,
|
|
137
|
+
) {
|
|
138
|
+
let left = 0;
|
|
139
|
+
if (align === "start") {
|
|
140
|
+
left = triggerPageX;
|
|
141
|
+
}
|
|
142
|
+
if (align === "center") {
|
|
143
|
+
left = triggerPageX + triggerWidth / 2 - contentWidth / 2;
|
|
144
|
+
}
|
|
145
|
+
if (align === "end") {
|
|
146
|
+
left = triggerPageX + triggerWidth - contentWidth;
|
|
147
|
+
}
|
|
148
|
+
return Math.max(insetLeft, Math.min(left + alignOffset, dimensions.width - contentWidth - insetRight));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type GetContentStyleArgs = GetPositionArgs & GetSidePositionArgs & GetAlignPositionArgs;
|
|
152
|
+
|
|
153
|
+
function getContentStyle({ align, avoidCollisions, contentLayout, side, triggerPosition, alignOffset, insets, sideOffset, dimensions }: GetContentStyleArgs) {
|
|
154
|
+
return Object.assign(
|
|
155
|
+
{ position: "absolute" } as const,
|
|
156
|
+
getSidePosition({
|
|
157
|
+
side,
|
|
158
|
+
triggerPosition,
|
|
159
|
+
contentLayout,
|
|
160
|
+
sideOffset,
|
|
161
|
+
insets,
|
|
162
|
+
avoidCollisions,
|
|
163
|
+
dimensions,
|
|
164
|
+
}),
|
|
165
|
+
getAlignPosition({
|
|
166
|
+
align,
|
|
167
|
+
avoidCollisions,
|
|
168
|
+
triggerPosition,
|
|
169
|
+
contentLayout,
|
|
170
|
+
alignOffset,
|
|
171
|
+
insets,
|
|
172
|
+
dimensions,
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { BackHandler, Pressable, View, type GestureResponderEvent, type LayoutChangeEvent, type LayoutRectangle } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { useAugmentedRef, useRelativePosition, type LayoutPosition } from "../hooks";
|
|
5
|
+
import { Portal as RNPPortal } from "../portal";
|
|
6
|
+
import * as Slot from "../slot";
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ContentProps,
|
|
10
|
+
ContentRef,
|
|
11
|
+
BackdropProps,
|
|
12
|
+
BackdropRef,
|
|
13
|
+
PortalProps,
|
|
14
|
+
SharedRootContext,
|
|
15
|
+
RootProps,
|
|
16
|
+
RootRef,
|
|
17
|
+
TriggerProps,
|
|
18
|
+
TriggerRef,
|
|
19
|
+
} from "./types";
|
|
20
|
+
|
|
21
|
+
interface RootContextInterface extends SharedRootContext {
|
|
22
|
+
open: boolean;
|
|
23
|
+
onOpenChange: (open: boolean) => void;
|
|
24
|
+
triggerPosition: LayoutPosition | null;
|
|
25
|
+
setTriggerPosition: (triggerPosition: LayoutPosition | null) => void;
|
|
26
|
+
contentLayout: LayoutRectangle | null;
|
|
27
|
+
setContentLayout: (contentLayout: LayoutRectangle | null) => void;
|
|
28
|
+
nativeID: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RootContext = React.createContext<RootContextInterface | null>(null);
|
|
32
|
+
|
|
33
|
+
const Root = React.forwardRef<RootRef, RootProps>(
|
|
34
|
+
({ render, openDelay: _openDelay, closeDelay: _closeDelay, onOpenChange: onOpenChangeProp, ...viewProps }, ref) => {
|
|
35
|
+
const nativeID = React.useId();
|
|
36
|
+
const [triggerPosition, setTriggerPosition] = React.useState<LayoutPosition | null>(null);
|
|
37
|
+
const [contentLayout, setContentLayout] = React.useState<LayoutRectangle | null>(null);
|
|
38
|
+
const [open, setOpen] = React.useState(false);
|
|
39
|
+
|
|
40
|
+
function onOpenChange(value: boolean) {
|
|
41
|
+
setOpen(value);
|
|
42
|
+
onOpenChangeProp?.(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<RootContext.Provider
|
|
47
|
+
value={{
|
|
48
|
+
open,
|
|
49
|
+
onOpenChange,
|
|
50
|
+
contentLayout,
|
|
51
|
+
nativeID,
|
|
52
|
+
setContentLayout,
|
|
53
|
+
setTriggerPosition,
|
|
54
|
+
triggerPosition,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<Component
|
|
58
|
+
ref={ref}
|
|
59
|
+
{...viewProps}
|
|
60
|
+
/>
|
|
61
|
+
</RootContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
Root.displayName = "RootNativeHoverCard";
|
|
67
|
+
|
|
68
|
+
function useRootContext() {
|
|
69
|
+
const context = React.useContext(RootContext);
|
|
70
|
+
if (!context) {
|
|
71
|
+
throw new Error("HoverCard compound components cannot be rendered outside the HoverCard component");
|
|
72
|
+
}
|
|
73
|
+
return context;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const Trigger = React.forwardRef<TriggerRef, TriggerProps>(({ onPress: onPressProp, disabled = false, ...props }, ref) => {
|
|
77
|
+
const { open, onOpenChange, setTriggerPosition } = useRootContext();
|
|
78
|
+
|
|
79
|
+
const augmentedRef = useAugmentedRef({
|
|
80
|
+
ref,
|
|
81
|
+
methods: {
|
|
82
|
+
open: () => {
|
|
83
|
+
onOpenChange(true);
|
|
84
|
+
augmentedRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
|
85
|
+
setTriggerPosition({ width, pageX, pageY: pageY, height });
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
close: () => {
|
|
89
|
+
setTriggerPosition(null);
|
|
90
|
+
onOpenChange(false);
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function onPress(ev: GestureResponderEvent) {
|
|
96
|
+
if (disabled) return;
|
|
97
|
+
augmentedRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
|
98
|
+
setTriggerPosition({ width, pageX, pageY: pageY, height });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
onOpenChange(!open);
|
|
102
|
+
onPressProp?.(ev);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Component
|
|
107
|
+
ref={augmentedRef}
|
|
108
|
+
aria-disabled={disabled ?? undefined}
|
|
109
|
+
role="button"
|
|
110
|
+
onPress={onPress}
|
|
111
|
+
disabled={disabled ?? undefined}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
Trigger.displayName = "TriggerNativeHoverCard";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @warning when using a custom `<PortalHost />`, you might have to adjust the Content's sideOffset to account for nav elements like headers.
|
|
121
|
+
*/
|
|
122
|
+
function Portal({ keepMounted, hostName, children }: PortalProps) {
|
|
123
|
+
const value = useRootContext();
|
|
124
|
+
|
|
125
|
+
if (!value.triggerPosition) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!keepMounted) {
|
|
130
|
+
if (!value.open) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<RNPPortal
|
|
137
|
+
hostName={hostName}
|
|
138
|
+
name={`${value.nativeID}_portal`}
|
|
139
|
+
>
|
|
140
|
+
<RootContext.Provider value={value}>{children}</RootContext.Provider>
|
|
141
|
+
</RNPPortal>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const Backdrop = React.forwardRef<BackdropRef, BackdropProps>(({ keepMounted, onPress: OnPressProp, closeOnPress = true, ...props }, ref) => {
|
|
146
|
+
const { open, onOpenChange, setTriggerPosition, setContentLayout } = useRootContext();
|
|
147
|
+
|
|
148
|
+
function onPress(ev: GestureResponderEvent) {
|
|
149
|
+
if (closeOnPress) {
|
|
150
|
+
setTriggerPosition(null);
|
|
151
|
+
setContentLayout(null);
|
|
152
|
+
onOpenChange(false);
|
|
153
|
+
}
|
|
154
|
+
OnPressProp?.(ev);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!keepMounted) {
|
|
158
|
+
if (!open) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Component
|
|
165
|
+
ref={ref}
|
|
166
|
+
onPress={onPress}
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
Backdrop.displayName = "BackdropNativeHoverCard";
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @info `position`, `top`, `left`, and `maxWidth` style properties are controlled internally. Opt out of this behavior by setting `disablePositioningStyle` to `true`.
|
|
176
|
+
*/
|
|
177
|
+
const Content = React.forwardRef<ContentRef, ContentProps>(
|
|
178
|
+
(
|
|
179
|
+
{
|
|
180
|
+
render = false,
|
|
181
|
+
keepMounted,
|
|
182
|
+
align = "start",
|
|
183
|
+
side = "bottom",
|
|
184
|
+
sideOffset = 0,
|
|
185
|
+
alignOffset = 0,
|
|
186
|
+
avoidCollisions = true,
|
|
187
|
+
onLayout: onLayoutProp,
|
|
188
|
+
insets,
|
|
189
|
+
style,
|
|
190
|
+
disablePositioningStyle,
|
|
191
|
+
...props
|
|
192
|
+
},
|
|
193
|
+
ref,
|
|
194
|
+
) => {
|
|
195
|
+
const { open, onOpenChange, contentLayout, nativeID, setContentLayout, setTriggerPosition, triggerPosition } = useRootContext();
|
|
196
|
+
|
|
197
|
+
React.useEffect(() => {
|
|
198
|
+
const backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
|
|
199
|
+
setTriggerPosition(null);
|
|
200
|
+
setContentLayout(null);
|
|
201
|
+
onOpenChange(false);
|
|
202
|
+
return true;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return () => {
|
|
206
|
+
setContentLayout(null);
|
|
207
|
+
backHandler.remove();
|
|
208
|
+
};
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const positionStyle = useRelativePosition({
|
|
212
|
+
align,
|
|
213
|
+
avoidCollisions,
|
|
214
|
+
triggerPosition,
|
|
215
|
+
contentLayout,
|
|
216
|
+
alignOffset,
|
|
217
|
+
insets,
|
|
218
|
+
sideOffset,
|
|
219
|
+
side,
|
|
220
|
+
disablePositioningStyle,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
function onLayout(event: LayoutChangeEvent) {
|
|
224
|
+
setContentLayout(event.nativeEvent.layout);
|
|
225
|
+
onLayoutProp?.(event);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!keepMounted) {
|
|
229
|
+
if (!open) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<Component
|
|
236
|
+
ref={ref}
|
|
237
|
+
role="dialog"
|
|
238
|
+
nativeID={nativeID}
|
|
239
|
+
aria-modal={true}
|
|
240
|
+
style={[positionStyle, style]}
|
|
241
|
+
onLayout={onLayout}
|
|
242
|
+
onStartShouldSetResponder={onStartShouldSetResponder}
|
|
243
|
+
{...props}
|
|
244
|
+
/>
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
Content.displayName = "ContentNativeHoverCard";
|
|
250
|
+
|
|
251
|
+
export { Content, Backdrop, Portal, Root, Trigger, useRootContext };
|
|
252
|
+
|
|
253
|
+
function onStartShouldSetResponder() {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// import * as HoverCard from "@radix-ui/react-hover-card";
|
|
2
|
+
// import * as React from "react";
|
|
3
|
+
// import { Pressable, View } from "react-native";
|
|
4
|
+
|
|
5
|
+
// import { useAugmentedRef } from "../hooks";
|
|
6
|
+
// import * as Slot from "../slot";
|
|
7
|
+
|
|
8
|
+
// import type {
|
|
9
|
+
// ContentProps,
|
|
10
|
+
// ContentRef,
|
|
11
|
+
// BackdropProps,
|
|
12
|
+
// BackdropRef,
|
|
13
|
+
// PortalProps,
|
|
14
|
+
// SharedRootContext,
|
|
15
|
+
// RootProps,
|
|
16
|
+
// RootRef,
|
|
17
|
+
// TriggerProps,
|
|
18
|
+
// TriggerRef,
|
|
19
|
+
// } from "./types";
|
|
20
|
+
|
|
21
|
+
// const HoverCardContext = React.createContext<SharedRootContext | null>(null);
|
|
22
|
+
|
|
23
|
+
// const Root = React.forwardRef<RootRef, RootProps>(({ openDelay, closeDelay, onOpenChange: onOpenChangeProp, ...viewProps }, ref) => {
|
|
24
|
+
// const [open, setOpen] = React.useState(false);
|
|
25
|
+
|
|
26
|
+
// function onOpenChange(value: boolean) {
|
|
27
|
+
// setOpen(value);
|
|
28
|
+
// onOpenChangeProp?.(value);
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
//
|
|
32
|
+
// return (
|
|
33
|
+
// <HoverCardContext.Provider value={{ open, onOpenChange }}>
|
|
34
|
+
// <HoverCard.Root
|
|
35
|
+
// open={open}
|
|
36
|
+
// onOpenChange={onOpenChange}
|
|
37
|
+
// openDelay={openDelay}
|
|
38
|
+
// closeDelay={closeDelay}
|
|
39
|
+
// >
|
|
40
|
+
// <Component
|
|
41
|
+
// ref={ref}
|
|
42
|
+
// {...viewProps}
|
|
43
|
+
// />
|
|
44
|
+
// </HoverCard.Root>
|
|
45
|
+
// </HoverCardContext.Provider>
|
|
46
|
+
// );
|
|
47
|
+
// });
|
|
48
|
+
|
|
49
|
+
// Root.displayName = "RootWebHoverCard";
|
|
50
|
+
|
|
51
|
+
// function useRootContext() {
|
|
52
|
+
// const context = React.useContext(HoverCardContext);
|
|
53
|
+
// if (!context) {
|
|
54
|
+
// throw new Error("HoverCard compound components cannot be rendered outside the HoverCard component");
|
|
55
|
+
// }
|
|
56
|
+
// return context;
|
|
57
|
+
// }
|
|
58
|
+
|
|
59
|
+
// const Trigger = React.forwardRef<TriggerRef, TriggerProps>(({ ...props }, ref) => {
|
|
60
|
+
// const { onOpenChange } = useRootContext();
|
|
61
|
+
// const augmentedRef = useAugmentedRef({
|
|
62
|
+
// ref,
|
|
63
|
+
// methods: {
|
|
64
|
+
// open() {
|
|
65
|
+
// onOpenChange(true);
|
|
66
|
+
// },
|
|
67
|
+
// close() {
|
|
68
|
+
// onOpenChange(false);
|
|
69
|
+
// },
|
|
70
|
+
// },
|
|
71
|
+
// });
|
|
72
|
+
|
|
73
|
+
//
|
|
74
|
+
// return (
|
|
75
|
+
// <HoverCard.Trigger render>
|
|
76
|
+
// <Component
|
|
77
|
+
// ref={augmentedRef}
|
|
78
|
+
// {...props}
|
|
79
|
+
// />
|
|
80
|
+
// </HoverCard.Trigger>
|
|
81
|
+
// );
|
|
82
|
+
// });
|
|
83
|
+
|
|
84
|
+
// Trigger.displayName = "TriggerWebHoverCard";
|
|
85
|
+
|
|
86
|
+
// function Portal({ keepMounted, container, children }: PortalProps) {
|
|
87
|
+
// return (
|
|
88
|
+
// <HoverCard.Portal
|
|
89
|
+
// keepMounted={keepMounted}
|
|
90
|
+
// container={container}
|
|
91
|
+
// children={children}
|
|
92
|
+
// />
|
|
93
|
+
// );
|
|
94
|
+
// }
|
|
95
|
+
|
|
96
|
+
// const Backdrop = React.forwardRef<BackdropRef, BackdropProps>(({ ...props }, ref) => {
|
|
97
|
+
//
|
|
98
|
+
// return (
|
|
99
|
+
// <Component
|
|
100
|
+
// ref={ref}
|
|
101
|
+
// {...props}
|
|
102
|
+
// />
|
|
103
|
+
// );
|
|
104
|
+
// });
|
|
105
|
+
|
|
106
|
+
// Backdrop.displayName = "BackdropWebHoverCard";
|
|
107
|
+
|
|
108
|
+
// const Content = React.forwardRef<ContentRef, ContentProps>(
|
|
109
|
+
// (
|
|
110
|
+
// {
|
|
111
|
+
// render = false,
|
|
112
|
+
// keepMounted,
|
|
113
|
+
// align,
|
|
114
|
+
// side,
|
|
115
|
+
// sideOffset,
|
|
116
|
+
// alignOffset = 0,
|
|
117
|
+
// avoidCollisions = true,
|
|
118
|
+
// insets,
|
|
119
|
+
// loop: _loop,
|
|
120
|
+
// onCloseAutoFocus: _onCloseAutoFocus,
|
|
121
|
+
// onEscapeKeyDown,
|
|
122
|
+
// onPointerDownOutside,
|
|
123
|
+
// onFocusOutside,
|
|
124
|
+
// onInteractOutside,
|
|
125
|
+
// collisionBoundary,
|
|
126
|
+
// sticky,
|
|
127
|
+
// hideWhenDetached,
|
|
128
|
+
// ...props
|
|
129
|
+
// },
|
|
130
|
+
// ref,
|
|
131
|
+
// ) => {
|
|
132
|
+
//
|
|
133
|
+
// return (
|
|
134
|
+
// <HoverCard.Content
|
|
135
|
+
// keepMounted={keepMounted}
|
|
136
|
+
// alignOffset={alignOffset}
|
|
137
|
+
// avoidCollisions={avoidCollisions}
|
|
138
|
+
// collisionPadding={insets}
|
|
139
|
+
// onEscapeKeyDown={onEscapeKeyDown}
|
|
140
|
+
// onPointerDownOutside={onPointerDownOutside}
|
|
141
|
+
// onFocusOutside={onFocusOutside}
|
|
142
|
+
// onInteractOutside={onInteractOutside}
|
|
143
|
+
// collisionBoundary={collisionBoundary}
|
|
144
|
+
// sticky={sticky}
|
|
145
|
+
// hideWhenDetached={hideWhenDetached}
|
|
146
|
+
// align={align}
|
|
147
|
+
// side={side}
|
|
148
|
+
// sideOffset={sideOffset}
|
|
149
|
+
// >
|
|
150
|
+
// <Component
|
|
151
|
+
// ref={ref}
|
|
152
|
+
// {...props}
|
|
153
|
+
// />
|
|
154
|
+
// </HoverCard.Content>
|
|
155
|
+
// );
|
|
156
|
+
// },
|
|
157
|
+
// );
|
|
158
|
+
|
|
159
|
+
// Content.displayName = "ContentWebHoverCard";
|
|
160
|
+
|
|
161
|
+
// export { Content, Backdrop, Portal, Root, Trigger, useRootContext };
|