@cdx-ui/primitives 0.0.1-beta.144 → 0.0.1-beta.146

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.
Files changed (64) hide show
  1. package/README.md +3 -1
  2. package/lib/commonjs/index.js +12 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/overlay/useAnchorPosition.js +13 -23
  5. package/lib/commonjs/overlay/useAnchorPosition.js.map +1 -1
  6. package/lib/commonjs/tooltip/TooltipContent.js +106 -0
  7. package/lib/commonjs/tooltip/TooltipContent.js.map +1 -0
  8. package/lib/commonjs/tooltip/TooltipRoot.js +128 -0
  9. package/lib/commonjs/tooltip/TooltipRoot.js.map +1 -0
  10. package/lib/commonjs/tooltip/TooltipTrigger.js +132 -0
  11. package/lib/commonjs/tooltip/TooltipTrigger.js.map +1 -0
  12. package/lib/commonjs/tooltip/context.js +11 -0
  13. package/lib/commonjs/tooltip/context.js.map +1 -0
  14. package/lib/commonjs/tooltip/getTooltipPosition.js +124 -0
  15. package/lib/commonjs/tooltip/getTooltipPosition.js.map +1 -0
  16. package/lib/commonjs/tooltip/index.js +53 -0
  17. package/lib/commonjs/tooltip/index.js.map +1 -0
  18. package/lib/commonjs/tooltip/types.js +6 -0
  19. package/lib/commonjs/tooltip/types.js.map +1 -0
  20. package/lib/module/index.js +1 -0
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/overlay/useAnchorPosition.js +13 -23
  23. package/lib/module/overlay/useAnchorPosition.js.map +1 -1
  24. package/lib/module/tooltip/TooltipContent.js +102 -0
  25. package/lib/module/tooltip/TooltipContent.js.map +1 -0
  26. package/lib/module/tooltip/TooltipRoot.js +124 -0
  27. package/lib/module/tooltip/TooltipRoot.js.map +1 -0
  28. package/lib/module/tooltip/TooltipTrigger.js +128 -0
  29. package/lib/module/tooltip/TooltipTrigger.js.map +1 -0
  30. package/lib/module/tooltip/context.js +5 -0
  31. package/lib/module/tooltip/context.js.map +1 -0
  32. package/lib/module/tooltip/getTooltipPosition.js +119 -0
  33. package/lib/module/tooltip/getTooltipPosition.js.map +1 -0
  34. package/lib/module/tooltip/index.js +8 -0
  35. package/lib/module/tooltip/index.js.map +1 -0
  36. package/lib/module/tooltip/types.js +4 -0
  37. package/lib/module/tooltip/types.js.map +1 -0
  38. package/lib/typescript/index.d.ts +1 -0
  39. package/lib/typescript/index.d.ts.map +1 -1
  40. package/lib/typescript/overlay/useAnchorPosition.d.ts.map +1 -1
  41. package/lib/typescript/tooltip/TooltipContent.d.ts +6 -0
  42. package/lib/typescript/tooltip/TooltipContent.d.ts.map +1 -0
  43. package/lib/typescript/tooltip/TooltipRoot.d.ts +7 -0
  44. package/lib/typescript/tooltip/TooltipRoot.d.ts.map +1 -0
  45. package/lib/typescript/tooltip/TooltipTrigger.d.ts +5 -0
  46. package/lib/typescript/tooltip/TooltipTrigger.d.ts.map +1 -0
  47. package/lib/typescript/tooltip/context.d.ts +6 -0
  48. package/lib/typescript/tooltip/context.d.ts.map +1 -0
  49. package/lib/typescript/tooltip/getTooltipPosition.d.ts +20 -0
  50. package/lib/typescript/tooltip/getTooltipPosition.d.ts.map +1 -0
  51. package/lib/typescript/tooltip/index.d.ts +7 -0
  52. package/lib/typescript/tooltip/index.d.ts.map +1 -0
  53. package/lib/typescript/tooltip/types.d.ts +78 -0
  54. package/lib/typescript/tooltip/types.d.ts.map +1 -0
  55. package/package.json +3 -3
  56. package/src/index.ts +1 -0
  57. package/src/overlay/useAnchorPosition.ts +15 -20
  58. package/src/tooltip/TooltipContent.tsx +119 -0
  59. package/src/tooltip/TooltipRoot.tsx +187 -0
  60. package/src/tooltip/TooltipTrigger.tsx +160 -0
  61. package/src/tooltip/context.tsx +5 -0
  62. package/src/tooltip/getTooltipPosition.ts +147 -0
  63. package/src/tooltip/index.ts +19 -0
  64. package/src/tooltip/types.ts +85 -0
@@ -0,0 +1,119 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ useRef,
7
+ type ComponentRef,
8
+ } from 'react';
9
+ import { Platform, StyleSheet, View, useWindowDimensions } from 'react-native';
10
+ import { mergeRefs } from '@cdx-ui/utils';
11
+ import { Portal } from '../portal';
12
+ import { useAnchorPosition, useDismissOverlay, useEscapeKey } from '../overlay';
13
+ import { createPortalFn } from '../utils/createPortal';
14
+ import { Slot } from '../slot';
15
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
16
+ import { TooltipProvider, useTooltipContext } from './context';
17
+ import { getTooltipAnchorStyle } from './getTooltipPosition';
18
+ import type { ITooltipContentProps } from './types';
19
+
20
+ export interface TooltipContentProps extends ITooltipContentProps {}
21
+
22
+ export const TooltipContent = forwardRef<ComponentRef<typeof View>, TooltipContentProps>(
23
+ ({ asChild, children, ...props }, ref) => {
24
+ const contextValue = useTooltipContext();
25
+ const {
26
+ open,
27
+ setOpen,
28
+ isDisabled,
29
+ placement,
30
+ offset,
31
+ trigger,
32
+ triggerRef,
33
+ contentId,
34
+ setResolvedPlacement,
35
+ } = contextValue;
36
+
37
+ const contentRef = useRef<ComponentRef<typeof View>>(null);
38
+ const mergedRef = mergeRefs(ref, contentRef);
39
+
40
+ const handleDismiss = useCallback(() => {
41
+ setOpen(false);
42
+ }, [setOpen]);
43
+
44
+ const shouldDismissOnInteraction =
45
+ trigger === 'click' && Platform.OS === 'web' && open && !isDisabled;
46
+
47
+ useDismissOverlay(shouldDismissOnInteraction, contentRef, [triggerRef], handleDismiss);
48
+ useEscapeKey(shouldDismissOnInteraction, handleDismiss);
49
+
50
+ const { width, height } = useWindowDimensions();
51
+ const anchorLayout = useAnchorPosition(triggerRef, open && !isDisabled);
52
+
53
+ const position = useMemo(() => {
54
+ if (!anchorLayout) {
55
+ return null;
56
+ }
57
+
58
+ return getTooltipAnchorStyle(anchorLayout, placement, offset, { width, height });
59
+ }, [anchorLayout, height, offset, placement, width]);
60
+
61
+ useLayoutEffect(() => {
62
+ if (position) {
63
+ setResolvedPlacement(position.resolved);
64
+ }
65
+ }, [position, setResolvedPlacement]);
66
+
67
+ if (!open || isDisabled || !position) {
68
+ return null;
69
+ }
70
+
71
+ const Comp = asChild ? Slot : View;
72
+ const webProps =
73
+ Platform.OS === 'web'
74
+ ? ({
75
+ id: contentId,
76
+ role: 'tooltip',
77
+ } as const)
78
+ : ({
79
+ accessibilityRole: 'text' as const,
80
+ accessibilityLiveRegion: 'polite' as const,
81
+ } as const);
82
+
83
+ const popup = (
84
+ <TooltipProvider value={contextValue}>
85
+ <View pointerEvents="none" style={position.style}>
86
+ <Comp
87
+ ref={mergedRef}
88
+ {...props}
89
+ {...webProps}
90
+ {...mergeDataAttributes(
91
+ props,
92
+ dataAttributes({
93
+ side: position.resolved.side,
94
+ align: position.resolved.align,
95
+ state: 'open',
96
+ }),
97
+ )}
98
+ >
99
+ {children}
100
+ </Comp>
101
+ </View>
102
+ </TooltipProvider>
103
+ );
104
+
105
+ if (Platform.OS === 'web' && createPortalFn && typeof document !== 'undefined') {
106
+ return createPortalFn(popup, document.body);
107
+ }
108
+
109
+ return (
110
+ <Portal name={contentId}>
111
+ <View pointerEvents="box-none" style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
112
+ {popup}
113
+ </View>
114
+ </Portal>
115
+ );
116
+ },
117
+ );
118
+
119
+ TooltipContent.displayName = 'TooltipContent';
@@ -0,0 +1,187 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useId,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ type ComponentRef,
10
+ } from 'react';
11
+ import { View, type ViewProps } from 'react-native';
12
+ import { useControllableState } from '@cdx-ui/utils';
13
+ import { Slot } from '../slot';
14
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
15
+ import { TooltipProvider } from './context';
16
+ import type { ResolvedTooltipPlacement } from './getTooltipPosition';
17
+ import type { ITooltipProps } from './types';
18
+
19
+ export interface TooltipRootProps extends ITooltipProps {
20
+ asChild?: boolean;
21
+ }
22
+
23
+ export const TooltipRoot = forwardRef<ComponentRef<typeof View>, TooltipRootProps>(
24
+ (
25
+ {
26
+ asChild,
27
+ children,
28
+ isDisabled = false,
29
+ enterDelay = 0,
30
+ leaveDelay = 0,
31
+ isOpen: isOpenProp,
32
+ defaultOpen = false,
33
+ onOpen,
34
+ onClose,
35
+ placement = 'top',
36
+ offset = 8,
37
+ trigger = 'hover',
38
+ ...props
39
+ },
40
+ ref,
41
+ ) => {
42
+ const [open, setOpenState] = useControllableState<boolean>({
43
+ prop: isOpenProp,
44
+ defaultProp: defaultOpen,
45
+ onChange: (next) => {
46
+ if (next) {
47
+ onOpen?.();
48
+ } else {
49
+ onClose?.();
50
+ }
51
+ },
52
+ });
53
+
54
+ const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
55
+ const leaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
56
+ const triggerRef = useRef<ComponentRef<typeof View> | null>(null);
57
+ const [resolvedPlacement, setResolvedPlacement] = useState<ResolvedTooltipPlacement | null>(
58
+ null,
59
+ );
60
+
61
+ const clearTimers = useCallback(() => {
62
+ if (enterTimerRef.current) {
63
+ clearTimeout(enterTimerRef.current);
64
+ enterTimerRef.current = null;
65
+ }
66
+ if (leaveTimerRef.current) {
67
+ clearTimeout(leaveTimerRef.current);
68
+ leaveTimerRef.current = null;
69
+ }
70
+ }, []);
71
+
72
+ const setOpen = useCallback(
73
+ (nextOpen: boolean) => {
74
+ if (isDisabled && nextOpen) {
75
+ return;
76
+ }
77
+ setOpenState(nextOpen);
78
+ },
79
+ [isDisabled, setOpenState],
80
+ );
81
+
82
+ const requestOpen = useCallback(() => {
83
+ if (isDisabled) {
84
+ return;
85
+ }
86
+
87
+ clearTimers();
88
+
89
+ if (leaveTimerRef.current) {
90
+ clearTimeout(leaveTimerRef.current);
91
+ leaveTimerRef.current = null;
92
+ }
93
+
94
+ if (enterDelay <= 0) {
95
+ setOpen(true);
96
+ return;
97
+ }
98
+
99
+ enterTimerRef.current = setTimeout(() => {
100
+ setOpen(true);
101
+ }, enterDelay);
102
+ }, [clearTimers, enterDelay, isDisabled, setOpen]);
103
+
104
+ const requestClose = useCallback(() => {
105
+ clearTimers();
106
+
107
+ if (enterTimerRef.current) {
108
+ clearTimeout(enterTimerRef.current);
109
+ enterTimerRef.current = null;
110
+ }
111
+
112
+ if (leaveDelay <= 0) {
113
+ setOpen(false);
114
+ return;
115
+ }
116
+
117
+ leaveTimerRef.current = setTimeout(() => {
118
+ setOpen(false);
119
+ }, leaveDelay);
120
+ }, [clearTimers, leaveDelay, setOpen]);
121
+
122
+ useEffect(() => clearTimers, [clearTimers]);
123
+
124
+ const reactId = useId();
125
+ const triggerId = `tooltip-trigger-${reactId}`;
126
+ const contentId = `tooltip-content-${reactId}`;
127
+
128
+ const contextValue = useMemo(
129
+ () => ({
130
+ open: open ?? false,
131
+ setOpen,
132
+ requestOpen,
133
+ requestClose,
134
+ clearTimers,
135
+ isDisabled,
136
+ enterDelay,
137
+ leaveDelay,
138
+ placement,
139
+ offset,
140
+ trigger,
141
+ triggerRef,
142
+ contentId,
143
+ triggerId,
144
+ resolvedPlacement,
145
+ setResolvedPlacement,
146
+ }),
147
+ [
148
+ clearTimers,
149
+ contentId,
150
+ enterDelay,
151
+ isDisabled,
152
+ leaveDelay,
153
+ offset,
154
+ open,
155
+ placement,
156
+ requestClose,
157
+ requestOpen,
158
+ resolvedPlacement,
159
+ setOpen,
160
+ trigger,
161
+ triggerId,
162
+ ],
163
+ );
164
+
165
+ const Comp = asChild ? Slot : View;
166
+
167
+ return (
168
+ <TooltipProvider value={contextValue}>
169
+ <Comp
170
+ ref={ref}
171
+ {...(props as ViewProps)}
172
+ {...mergeDataAttributes(
173
+ props,
174
+ dataAttributes({
175
+ state: open ? 'open' : 'closed',
176
+ disabled: isDisabled,
177
+ }),
178
+ )}
179
+ >
180
+ {children}
181
+ </Comp>
182
+ </TooltipProvider>
183
+ );
184
+ },
185
+ );
186
+
187
+ TooltipRoot.displayName = 'TooltipRoot';
@@ -0,0 +1,160 @@
1
+ import { forwardRef, useCallback, type ComponentRef } from 'react';
2
+ import { Platform, Pressable, type PressableProps } from 'react-native';
3
+ import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
4
+ import { useFocus } from '@react-native-aria/focus';
5
+ import { useHover } from '@react-native-aria/interactions';
6
+ import { Slot } from '../slot';
7
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
8
+ import { useTooltipContext } from './context';
9
+ import type { ITooltipTriggerProps } from './types';
10
+
11
+ export interface TooltipTriggerProps extends ITooltipTriggerProps {}
12
+
13
+ export const TooltipTrigger = forwardRef<ComponentRef<typeof Pressable>, TooltipTriggerProps>(
14
+ (
15
+ {
16
+ asChild = true,
17
+ children,
18
+ onHoverIn,
19
+ onHoverOut,
20
+ onFocus,
21
+ onBlur,
22
+ onLongPress,
23
+ onPress,
24
+ onLayout,
25
+ ...props
26
+ },
27
+ ref,
28
+ ) => {
29
+ const {
30
+ open,
31
+ setOpen,
32
+ requestOpen,
33
+ requestClose,
34
+ isDisabled,
35
+ trigger,
36
+ triggerRef,
37
+ contentId,
38
+ triggerId,
39
+ } = useTooltipContext();
40
+
41
+ const mergedRef = mergeRefs(ref, triggerRef);
42
+ const { hoverProps, isHovered } = useHover({}, mergedRef);
43
+ const { focusProps, isFocused } = useFocus();
44
+
45
+ const handleOpen = useCallback(() => {
46
+ if (isDisabled) {
47
+ return;
48
+ }
49
+ requestOpen();
50
+ }, [isDisabled, requestOpen]);
51
+
52
+ const handleClose = useCallback(() => {
53
+ requestClose();
54
+ }, [requestClose]);
55
+
56
+ const isWeb = Platform.OS === 'web';
57
+ const usesLongPressOnNative = !isWeb && (trigger === 'hover' || trigger === 'focus');
58
+ const usesClickToggle = trigger === 'click';
59
+
60
+ const handlePress = composeEventHandlers(onPress, () => {
61
+ if (!usesClickToggle || isDisabled) {
62
+ return;
63
+ }
64
+ setOpen(!open);
65
+ });
66
+
67
+ const handleHoverIn = composeEventHandlers(
68
+ composeEventHandlers(onHoverIn, hoverProps.onHoverIn),
69
+ () => {
70
+ if (isWeb && trigger === 'hover') {
71
+ handleOpen();
72
+ }
73
+ },
74
+ );
75
+
76
+ const handleHoverOut = composeEventHandlers(
77
+ composeEventHandlers(onHoverOut, hoverProps.onHoverOut),
78
+ () => {
79
+ if (isWeb && trigger === 'hover') {
80
+ handleClose();
81
+ }
82
+ },
83
+ );
84
+
85
+ const handleFocus = composeEventHandlers(
86
+ composeEventHandlers(onFocus, focusProps.onFocus),
87
+ () => {
88
+ if (!isWeb) {
89
+ return;
90
+ }
91
+ if (trigger === 'focus' || trigger === 'hover') {
92
+ handleOpen();
93
+ }
94
+ },
95
+ );
96
+
97
+ const handleBlur = composeEventHandlers(composeEventHandlers(onBlur, focusProps.onBlur), () => {
98
+ if (!isWeb) {
99
+ return;
100
+ }
101
+ if (trigger === 'focus' || trigger === 'hover') {
102
+ handleClose();
103
+ }
104
+ });
105
+
106
+ const handleLongPress = composeEventHandlers(onLongPress, () => {
107
+ if (usesLongPressOnNative) {
108
+ handleOpen();
109
+ }
110
+ });
111
+
112
+ const webHoverProps =
113
+ isWeb && trigger === 'hover'
114
+ ? ({
115
+ onPointerEnter: handleHoverIn,
116
+ onPointerLeave: handleHoverOut,
117
+ } as const)
118
+ : {};
119
+
120
+ const webA11yProps = isWeb
121
+ ? ({
122
+ id: triggerId,
123
+ 'aria-describedby': open ? contentId : undefined,
124
+ ...(trigger === 'click' ? { 'aria-expanded': open } : {}),
125
+ } as const)
126
+ : {};
127
+
128
+ const Comp = asChild ? Slot : Pressable;
129
+
130
+ return (
131
+ <Comp
132
+ ref={mergedRef}
133
+ disabled={isDisabled}
134
+ {...(props as PressableProps)}
135
+ {...webA11yProps}
136
+ {...focusProps}
137
+ {...webHoverProps}
138
+ onLayout={onLayout}
139
+ {...mergeDataAttributes(
140
+ props,
141
+ dataAttributes({
142
+ triggerHover: isHovered,
143
+ triggerFocus: isFocused,
144
+ disabled: isDisabled,
145
+ }),
146
+ )}
147
+ onHoverIn={handleHoverIn}
148
+ onHoverOut={handleHoverOut}
149
+ onFocus={handleFocus}
150
+ onBlur={handleBlur}
151
+ onLongPress={handleLongPress}
152
+ onPress={handlePress}
153
+ >
154
+ {children}
155
+ </Comp>
156
+ );
157
+ },
158
+ );
159
+
160
+ TooltipTrigger.displayName = 'TooltipTrigger';
@@ -0,0 +1,5 @@
1
+ import { createContext } from '@cdx-ui/utils';
2
+ import type { TooltipContextValue } from './types';
3
+
4
+ export const [TooltipProvider, useTooltipContext] =
5
+ createContext<TooltipContextValue>('TooltipContext');
@@ -0,0 +1,147 @@
1
+ import { Platform, type ViewStyle } from 'react-native';
2
+ import type { AnchorLayout } from '../overlay/useAnchorPosition';
3
+
4
+ export const TOOLTIP_PLACEMENTS = [
5
+ 'auto',
6
+ 'auto-start',
7
+ 'auto-end',
8
+ 'top',
9
+ 'top-start',
10
+ 'top-end',
11
+ 'bottom',
12
+ 'bottom-start',
13
+ 'bottom-end',
14
+ 'left',
15
+ 'left-start',
16
+ 'left-end',
17
+ 'right',
18
+ 'right-start',
19
+ 'right-end',
20
+ ] as const;
21
+
22
+ export type TooltipPlacement = (typeof TOOLTIP_PLACEMENTS)[number];
23
+ export type TooltipTriggerMode = 'hover' | 'focus' | 'click';
24
+ export type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
25
+ export type TooltipAlign = 'start' | 'center' | 'end';
26
+
27
+ export interface ResolvedTooltipPlacement {
28
+ readonly side: TooltipSide;
29
+ readonly align: TooltipAlign;
30
+ }
31
+
32
+ export const TOOLTIP_Z_INDEX = 9999;
33
+ const SCREEN_PADDING = 8;
34
+
35
+ function parsePlacement(placement: TooltipPlacement): ResolvedTooltipPlacement | 'auto' {
36
+ if (placement === 'auto' || placement === 'auto-start' || placement === 'auto-end') {
37
+ return 'auto';
38
+ }
39
+
40
+ const segments = placement.split('-');
41
+ const side = segments[0] as TooltipSide;
42
+
43
+ if (segments.length === 1) {
44
+ return { side, align: 'center' };
45
+ }
46
+
47
+ return { side, align: segments[1] as TooltipAlign };
48
+ }
49
+
50
+ function resolveAutoPlacement(
51
+ anchor: AnchorLayout,
52
+ viewport: { width: number; height: number },
53
+ alignPreference: TooltipAlign,
54
+ ): ResolvedTooltipPlacement {
55
+ const spaceTop = anchor.y - SCREEN_PADDING;
56
+ const spaceBottom = viewport.height - anchor.y - anchor.height - SCREEN_PADDING;
57
+ const spaceLeft = anchor.x - SCREEN_PADDING;
58
+ const spaceRight = viewport.width - anchor.x - anchor.width - SCREEN_PADDING;
59
+
60
+ const spaces: { side: TooltipSide; value: number }[] = [
61
+ { side: 'top', value: spaceTop },
62
+ { side: 'bottom', value: spaceBottom },
63
+ { side: 'left', value: spaceLeft },
64
+ { side: 'right', value: spaceRight },
65
+ ];
66
+
67
+ const best = spaces.reduce((current, next) => (next.value > current.value ? next : current));
68
+
69
+ return { side: best.side, align: alignPreference };
70
+ }
71
+
72
+ function getAlignOffset(
73
+ anchor: AnchorLayout,
74
+ align: TooltipAlign,
75
+ axis: 'horizontal' | 'vertical',
76
+ ): number {
77
+ if (align === 'start') {
78
+ return axis === 'horizontal' ? anchor.x : anchor.y;
79
+ }
80
+
81
+ if (align === 'end') {
82
+ return axis === 'horizontal' ? anchor.x + anchor.width : anchor.y + anchor.height;
83
+ }
84
+
85
+ return axis === 'horizontal' ? anchor.x + anchor.width / 2 : anchor.y + anchor.height / 2;
86
+ }
87
+
88
+ export function getTooltipAnchorStyle(
89
+ anchor: AnchorLayout,
90
+ placement: TooltipPlacement,
91
+ offset: number,
92
+ viewport: { width: number; height: number },
93
+ ): { style: ViewStyle; resolved: ResolvedTooltipPlacement } {
94
+ const parsed = parsePlacement(placement);
95
+ const resolved =
96
+ parsed === 'auto'
97
+ ? resolveAutoPlacement(
98
+ anchor,
99
+ viewport,
100
+ placement === 'auto-start' ? 'start' : placement === 'auto-end' ? 'end' : 'center',
101
+ )
102
+ : parsed;
103
+
104
+ const position = (Platform.OS === 'web' ? 'fixed' : 'absolute') as ViewStyle['position'];
105
+ const style: ViewStyle = { position, zIndex: TOOLTIP_Z_INDEX };
106
+ const { side, align } = resolved;
107
+
108
+ if (side === 'top') {
109
+ style.top = anchor.y - offset;
110
+ style.left = getAlignOffset(anchor, align, 'horizontal');
111
+ style.transform =
112
+ align === 'center'
113
+ ? ([{ translateX: '-50%' }, { translateY: '-100%' }] as ViewStyle['transform'])
114
+ : align === 'end'
115
+ ? ([{ translateX: '-100%' }, { translateY: '-100%' }] as ViewStyle['transform'])
116
+ : ([{ translateY: '-100%' }] as ViewStyle['transform']);
117
+ } else if (side === 'bottom') {
118
+ style.top = anchor.y + anchor.height + offset;
119
+ style.left = getAlignOffset(anchor, align, 'horizontal');
120
+ style.transform =
121
+ align === 'center'
122
+ ? ([{ translateX: '-50%' }] as ViewStyle['transform'])
123
+ : align === 'end'
124
+ ? ([{ translateX: '-100%' }] as ViewStyle['transform'])
125
+ : undefined;
126
+ } else if (side === 'left') {
127
+ style.left = anchor.x - offset;
128
+ style.top = getAlignOffset(anchor, align, 'vertical');
129
+ style.transform =
130
+ align === 'center'
131
+ ? ([{ translateX: '-100%' }, { translateY: '-50%' }] as ViewStyle['transform'])
132
+ : align === 'end'
133
+ ? ([{ translateX: '-100%' }, { translateY: '-100%' }] as ViewStyle['transform'])
134
+ : ([{ translateX: '-100%' }] as ViewStyle['transform']);
135
+ } else {
136
+ style.left = anchor.x + anchor.width + offset;
137
+ style.top = getAlignOffset(anchor, align, 'vertical');
138
+ style.transform =
139
+ align === 'center'
140
+ ? ([{ translateY: '-50%' }] as ViewStyle['transform'])
141
+ : align === 'end'
142
+ ? ([{ translateY: '-100%' }] as ViewStyle['transform'])
143
+ : undefined;
144
+ }
145
+
146
+ return { style, resolved };
147
+ }
@@ -0,0 +1,19 @@
1
+ export { TooltipProvider, useTooltipContext } from './context';
2
+ export { TooltipRoot, type TooltipRootProps } from './TooltipRoot';
3
+ export { TooltipTrigger, type TooltipTriggerProps } from './TooltipTrigger';
4
+ export { TooltipContent, type TooltipContentProps } from './TooltipContent';
5
+ export {
6
+ TOOLTIP_PLACEMENTS,
7
+ getTooltipAnchorStyle,
8
+ type TooltipPlacement,
9
+ type TooltipTriggerMode,
10
+ type TooltipSide,
11
+ type TooltipAlign,
12
+ type ResolvedTooltipPlacement,
13
+ } from './getTooltipPosition';
14
+ export type {
15
+ ITooltipProps,
16
+ ITooltipTriggerProps,
17
+ ITooltipContentProps,
18
+ TooltipContextValue,
19
+ } from './types';