@buoy-gg/core 1.7.2
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/README.md +43 -0
- package/lib/commonjs/floatingMenu/AppHost.js +410 -0
- package/lib/commonjs/floatingMenu/AppHostLogic.js +44 -0
- package/lib/commonjs/floatingMenu/DefaultConfigContext.js +45 -0
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +2274 -0
- package/lib/commonjs/floatingMenu/DevToolsVisibilityContext.js +49 -0
- package/lib/commonjs/floatingMenu/DraggableHeader.js +114 -0
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +254 -0
- package/lib/commonjs/floatingMenu/FloatingMenu.js +364 -0
- package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +247 -0
- package/lib/commonjs/floatingMenu/MinimizedToolsStack.js +206 -0
- package/lib/commonjs/floatingMenu/ToggleStateManager.js +36 -0
- package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +241 -0
- package/lib/commonjs/floatingMenu/defaultConfig.js +160 -0
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +835 -0
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +246 -0
- package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +249 -0
- package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +70 -0
- package/lib/commonjs/floatingMenu/floatingTools.js +771 -0
- package/lib/commonjs/floatingMenu/settingsBus.js +23 -0
- package/lib/commonjs/floatingMenu/types.js +5 -0
- package/lib/commonjs/index.js +240 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/floatingMenu/AppHost.js +402 -0
- package/lib/module/floatingMenu/AppHostLogic.js +39 -0
- package/lib/module/floatingMenu/DefaultConfigContext.js +39 -0
- package/lib/module/floatingMenu/DevToolsSettingsModal.js +2273 -0
- package/lib/module/floatingMenu/DevToolsVisibilityContext.js +44 -0
- package/lib/module/floatingMenu/DraggableHeader.js +110 -0
- package/lib/module/floatingMenu/FloatingDevTools.js +249 -0
- package/lib/module/floatingMenu/FloatingMenu.js +358 -0
- package/lib/module/floatingMenu/MinimizedToolsContext.js +239 -0
- package/lib/module/floatingMenu/MinimizedToolsStack.js +202 -0
- package/lib/module/floatingMenu/ToggleStateManager.js +32 -0
- package/lib/module/floatingMenu/autoDiscoverPresets.js +236 -0
- package/lib/module/floatingMenu/defaultConfig.js +151 -0
- package/lib/module/floatingMenu/dial/DialDevTools.js +829 -0
- package/lib/module/floatingMenu/dial/DialIcon.js +241 -0
- package/lib/module/floatingMenu/dial/OnboardingTooltip.js +244 -0
- package/lib/module/floatingMenu/dial/onboardingConstants.js +64 -0
- package/lib/module/floatingMenu/floatingTools.js +767 -0
- package/lib/module/floatingMenu/settingsBus.js +19 -0
- package/lib/module/floatingMenu/types.js +3 -0
- package/lib/module/index.js +29 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts +39 -0
- package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts +37 -0
- package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts +27 -0
- package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +57 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts +25 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/DraggableHeader.d.ts +30 -0
- package/lib/typescript/commonjs/floatingMenu/DraggableHeader.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +226 -0
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts +39 -0
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts +95 -0
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts +10 -0
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts +21 -0
- package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts +75 -0
- package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +120 -0
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +35 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts +14 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts +12 -0
- package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts +30 -0
- package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts +56 -0
- package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts +10 -0
- package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/types.d.ts +56 -0
- package/lib/typescript/commonjs/floatingMenu/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +18 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/module/floatingMenu/AppHost.d.ts +39 -0
- package/lib/typescript/module/floatingMenu/AppHost.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts +37 -0
- package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts +27 -0
- package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +57 -0
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts +25 -0
- package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DraggableHeader.d.ts +30 -0
- package/lib/typescript/module/floatingMenu/DraggableHeader.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +226 -0
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts +39 -0
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts +95 -0
- package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts +10 -0
- package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts +21 -0
- package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts +75 -0
- package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +120 -0
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +35 -0
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts +14 -0
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts +12 -0
- package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts +30 -0
- package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/floatingTools.d.ts +56 -0
- package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/settingsBus.d.ts +10 -0
- package/lib/typescript/module/floatingMenu/settingsBus.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/types.d.ts +56 -0
- package/lib/typescript/module/floatingMenu/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +18 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState, useContext, createContext, useCallback, Children } from "react";
|
|
4
|
+
import { Animated, Dimensions, View, Text, TouchableOpacity } from "react-native";
|
|
5
|
+
import { getSafeAreaInsets, safeGetItem, safeSetItem, buoyColors } from "@buoy-gg/shared-ui";
|
|
6
|
+
import { DraggableHeader } from "./DraggableHeader.js";
|
|
7
|
+
import { useSafeAreaInsets } from "@buoy-gg/shared-ui";
|
|
8
|
+
import { calculateTargetPosition } from "./dial/onboardingConstants.js";
|
|
9
|
+
import { MinimizedToolsStack } from "./MinimizedToolsStack.js";
|
|
10
|
+
import { useMinimizedTools } from "./MinimizedToolsContext.js";
|
|
11
|
+
import { useAppHost } from "./AppHost.js";
|
|
12
|
+
import { EnvironmentSelectorInline } from "@buoy-gg/shared-ui";
|
|
13
|
+
|
|
14
|
+
// Shared utilities from floating-tools-core
|
|
15
|
+
import { getUserStatusConfig as getStatusConfig, getGripIconLayout, filterValidChildren, floatingToolsColors } from "@buoy-gg/floating-tools-core";
|
|
16
|
+
|
|
17
|
+
// Re-export UserRole type for consumers
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
// Using Views to render grip dots; no react-native-svg dependency
|
|
20
|
+
|
|
21
|
+
// =============================
|
|
22
|
+
// Icons (using shared layout calculation)
|
|
23
|
+
// =============================
|
|
24
|
+
/**
|
|
25
|
+
* Grip icon component for draggable areas
|
|
26
|
+
*
|
|
27
|
+
* Renders a vertical grip pattern using View components to avoid SVG dependencies.
|
|
28
|
+
* Uses shared getGripIconLayout for consistent sizing across platforms.
|
|
29
|
+
*/
|
|
30
|
+
function GripVerticalIcon({
|
|
31
|
+
size = 24,
|
|
32
|
+
color = buoyColors.textSecondary + "CC"
|
|
33
|
+
}) {
|
|
34
|
+
// Use shared layout calculation from floating-tools-core
|
|
35
|
+
const layout = getGripIconLayout(size);
|
|
36
|
+
const containerStyle = {
|
|
37
|
+
width: size,
|
|
38
|
+
height: size,
|
|
39
|
+
flexDirection: "row",
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
justifyContent: "center"
|
|
42
|
+
};
|
|
43
|
+
const columnStyle = {
|
|
44
|
+
flexDirection: "column",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
marginHorizontal: layout.columnGap / 2
|
|
48
|
+
};
|
|
49
|
+
const dotStyle = {
|
|
50
|
+
width: layout.dotSize,
|
|
51
|
+
height: layout.dotSize,
|
|
52
|
+
borderRadius: layout.dotSize / 2,
|
|
53
|
+
backgroundColor: color,
|
|
54
|
+
marginVertical: layout.rowGap / 2
|
|
55
|
+
};
|
|
56
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
57
|
+
style: containerStyle,
|
|
58
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
59
|
+
style: columnStyle,
|
|
60
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
61
|
+
style: dotStyle
|
|
62
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
63
|
+
style: dotStyle
|
|
64
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
65
|
+
style: dotStyle
|
|
66
|
+
})]
|
|
67
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
68
|
+
style: columnStyle,
|
|
69
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
70
|
+
style: dotStyle
|
|
71
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
72
|
+
style: dotStyle
|
|
73
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
74
|
+
style: dotStyle
|
|
75
|
+
})]
|
|
76
|
+
})]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const STORAGE_KEYS = {
|
|
80
|
+
BUBBLE_POSITION_X: "@react_buoy_bubble_position_x",
|
|
81
|
+
BUBBLE_POSITION_Y: "@react_buoy_bubble_position_y"
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Debug logging removed for production
|
|
85
|
+
|
|
86
|
+
// =============================
|
|
87
|
+
// Position persistence hook
|
|
88
|
+
// Extracted logic dedicated to state/IO
|
|
89
|
+
// =============================
|
|
90
|
+
/**
|
|
91
|
+
* Custom hook for managing floating tools position persistence
|
|
92
|
+
*
|
|
93
|
+
* Handles loading, saving, and validating the position of the floating tools bubble
|
|
94
|
+
* with automatic boundary checking and storage management.
|
|
95
|
+
*
|
|
96
|
+
* @param props - Configuration for position management
|
|
97
|
+
* @param props.animatedPosition - Animated.ValueXY for position updates
|
|
98
|
+
* @param props.bubbleWidth - Width of the bubble for boundary calculations
|
|
99
|
+
* @param props.bubbleHeight - Height of the bubble for boundary calculations
|
|
100
|
+
* @param props.enabled - Whether position persistence is enabled
|
|
101
|
+
* @param props.visibleHandleWidth - Width of visible handle when bubble is hidden
|
|
102
|
+
* @param props.listenersSuspended - Pause automatic listeners without disabling manual saves
|
|
103
|
+
*
|
|
104
|
+
* @returns Object containing position management functions
|
|
105
|
+
*
|
|
106
|
+
* @performance Uses debounced saving to avoid excessive storage operations
|
|
107
|
+
* @performance Validates positions against screen boundaries and safe areas
|
|
108
|
+
*/
|
|
109
|
+
function useFloatingToolsPosition({
|
|
110
|
+
animatedPosition,
|
|
111
|
+
bubbleWidth = 100,
|
|
112
|
+
bubbleHeight = 32,
|
|
113
|
+
enabled = true,
|
|
114
|
+
visibleHandleWidth = 32,
|
|
115
|
+
listenersSuspended = false
|
|
116
|
+
}) {
|
|
117
|
+
const isInitialized = useRef(false);
|
|
118
|
+
const saveTimeoutRef = useRef(undefined);
|
|
119
|
+
const savePosition = useCallback(async (x, y) => {
|
|
120
|
+
if (!enabled) return;
|
|
121
|
+
try {
|
|
122
|
+
await Promise.all([safeSetItem(STORAGE_KEYS.BUBBLE_POSITION_X, x.toString()), safeSetItem(STORAGE_KEYS.BUBBLE_POSITION_Y, y.toString())]);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Failed to save position - continue without persistence
|
|
125
|
+
}
|
|
126
|
+
}, [enabled]);
|
|
127
|
+
const debouncedSavePosition = useCallback((x, y) => {
|
|
128
|
+
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
|
|
129
|
+
saveTimeoutRef.current = setTimeout(() => savePosition(x, y), 500);
|
|
130
|
+
}, [savePosition]);
|
|
131
|
+
const loadPosition = useCallback(async () => {
|
|
132
|
+
if (!enabled) return null;
|
|
133
|
+
try {
|
|
134
|
+
const [xStr, yStr] = await Promise.all([safeGetItem(STORAGE_KEYS.BUBBLE_POSITION_X), safeGetItem(STORAGE_KEYS.BUBBLE_POSITION_Y)]);
|
|
135
|
+
if (xStr !== null && yStr !== null) {
|
|
136
|
+
const x = parseFloat(xStr);
|
|
137
|
+
const y = parseFloat(yStr);
|
|
138
|
+
if (!Number.isNaN(x) && !Number.isNaN(y)) return {
|
|
139
|
+
x,
|
|
140
|
+
y
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Failed to load position - use default
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}, [enabled]);
|
|
148
|
+
const validatePosition = useCallback(position => {
|
|
149
|
+
const {
|
|
150
|
+
width: screenWidth,
|
|
151
|
+
height: screenHeight
|
|
152
|
+
} = Dimensions.get("window");
|
|
153
|
+
const safeArea = getSafeAreaInsets();
|
|
154
|
+
// Prevent going off left, top, and bottom edges with safe area
|
|
155
|
+
// Allow pushing off-screen to the right so only the grab handle remains visible
|
|
156
|
+
const minX = safeArea.left; // Respect safe area left
|
|
157
|
+
const maxX = screenWidth - visibleHandleWidth; // no right padding, ensure handle is visible
|
|
158
|
+
// Add small padding below the safe area top to ensure bubble doesn't go behind notch
|
|
159
|
+
const minY = safeArea.top + 20; // Ensure bubble is below safe area
|
|
160
|
+
const maxY = screenHeight - bubbleHeight - safeArea.bottom; // Respect safe area bottom
|
|
161
|
+
const clamped = {
|
|
162
|
+
x: Math.max(minX, Math.min(position.x, maxX)),
|
|
163
|
+
y: Math.max(minY, Math.min(position.y, maxY))
|
|
164
|
+
};
|
|
165
|
+
return clamped;
|
|
166
|
+
}, [visibleHandleWidth, bubbleHeight]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!enabled || isInitialized.current) return;
|
|
169
|
+
const restore = async () => {
|
|
170
|
+
const saved = await loadPosition();
|
|
171
|
+
if (saved) {
|
|
172
|
+
const validated = validatePosition(saved);
|
|
173
|
+
// Check if the saved position is out of bounds
|
|
174
|
+
const wasOutOfBounds = Math.abs(saved.x - validated.x) > 5 || Math.abs(saved.y - validated.y) > 5;
|
|
175
|
+
if (wasOutOfBounds) {
|
|
176
|
+
// Save the corrected position
|
|
177
|
+
await savePosition(validated.x, validated.y);
|
|
178
|
+
}
|
|
179
|
+
animatedPosition.setValue(validated);
|
|
180
|
+
} else {
|
|
181
|
+
const {
|
|
182
|
+
width: screenWidth,
|
|
183
|
+
height: screenHeight
|
|
184
|
+
} = Dimensions.get("window");
|
|
185
|
+
const safeArea = getSafeAreaInsets();
|
|
186
|
+
const defaultY = Math.max(safeArea.top + 20, Math.min(100, screenHeight - bubbleHeight - safeArea.bottom));
|
|
187
|
+
animatedPosition.setValue({
|
|
188
|
+
x: screenWidth - bubbleWidth - 20,
|
|
189
|
+
y: defaultY // Ensure it's within safe area bounds
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
isInitialized.current = true;
|
|
193
|
+
};
|
|
194
|
+
restore();
|
|
195
|
+
}, [enabled, animatedPosition, loadPosition, validatePosition, savePosition, bubbleWidth, bubbleHeight]);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!enabled || !isInitialized.current || listenersSuspended) return;
|
|
198
|
+
const listener = animatedPosition.addListener(value => {
|
|
199
|
+
debouncedSavePosition(value.x, value.y);
|
|
200
|
+
});
|
|
201
|
+
return () => {
|
|
202
|
+
animatedPosition.removeListener(listener);
|
|
203
|
+
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
|
|
204
|
+
};
|
|
205
|
+
}, [enabled, listenersSuspended, animatedPosition, debouncedSavePosition]);
|
|
206
|
+
return {
|
|
207
|
+
savePosition,
|
|
208
|
+
loadPosition,
|
|
209
|
+
isInitialized: isInitialized.current
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// =============================
|
|
214
|
+
// UI-only leaf components
|
|
215
|
+
// =============================
|
|
216
|
+
export function Divider() {
|
|
217
|
+
const dividerStyle = {
|
|
218
|
+
width: 1,
|
|
219
|
+
height: 12,
|
|
220
|
+
backgroundColor: buoyColors.textMuted + "66",
|
|
221
|
+
flexShrink: 0
|
|
222
|
+
};
|
|
223
|
+
return /*#__PURE__*/_jsx(View, {
|
|
224
|
+
style: dividerStyle
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Use shared getUserStatusConfig from floating-tools-core
|
|
229
|
+
// Wraps the core function to use our buoyColors (mapped to gameUIColors format)
|
|
230
|
+
const gameUIColorsCompat = {
|
|
231
|
+
success: "#20C997",
|
|
232
|
+
// teal
|
|
233
|
+
warning: "#FFA94D",
|
|
234
|
+
error: "#EF4444",
|
|
235
|
+
muted: buoyColors.textMuted,
|
|
236
|
+
secondary: buoyColors.textSecondary,
|
|
237
|
+
info: "#20C997",
|
|
238
|
+
// teal
|
|
239
|
+
primary: "#20C997",
|
|
240
|
+
optional: buoyColors.textSecondary // optional status color
|
|
241
|
+
};
|
|
242
|
+
function getUserStatusConfig(userRole) {
|
|
243
|
+
return getStatusConfig(userRole, gameUIColorsCompat);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Context to avoid brittle prop threading and keep API composable
|
|
247
|
+
const FloatingToolsContext = /*#__PURE__*/createContext({
|
|
248
|
+
isDragging: false
|
|
249
|
+
});
|
|
250
|
+
export function UserStatus({
|
|
251
|
+
userRole,
|
|
252
|
+
onPress
|
|
253
|
+
}) {
|
|
254
|
+
const {
|
|
255
|
+
isDragging
|
|
256
|
+
} = useContext(FloatingToolsContext);
|
|
257
|
+
const config = getUserStatusConfig(userRole);
|
|
258
|
+
const containerStyle = {
|
|
259
|
+
flexDirection: "row",
|
|
260
|
+
alignItems: "center",
|
|
261
|
+
paddingVertical: 6,
|
|
262
|
+
paddingHorizontal: 8,
|
|
263
|
+
flexShrink: 0
|
|
264
|
+
};
|
|
265
|
+
const dotStyle = {
|
|
266
|
+
width: 6,
|
|
267
|
+
height: 6,
|
|
268
|
+
borderRadius: 3,
|
|
269
|
+
backgroundColor: config.dotColor,
|
|
270
|
+
marginRight: 4
|
|
271
|
+
};
|
|
272
|
+
const textStyle = {
|
|
273
|
+
fontSize: 10,
|
|
274
|
+
fontWeight: "500",
|
|
275
|
+
color: config.textColor,
|
|
276
|
+
letterSpacing: 0.3
|
|
277
|
+
};
|
|
278
|
+
if (!onPress) {
|
|
279
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
280
|
+
style: containerStyle,
|
|
281
|
+
accessibilityLabel: `User status: ${config.label}`,
|
|
282
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
283
|
+
style: dotStyle
|
|
284
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
285
|
+
style: textStyle,
|
|
286
|
+
children: config.label
|
|
287
|
+
})]
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
291
|
+
accessibilityRole: "button",
|
|
292
|
+
accessibilityLabel: "Open Dev Tools Menu",
|
|
293
|
+
testID: "open-dev-tools-button",
|
|
294
|
+
onPress: onPress,
|
|
295
|
+
hitSlop: {
|
|
296
|
+
top: 10,
|
|
297
|
+
bottom: 10,
|
|
298
|
+
left: 0,
|
|
299
|
+
right: 10
|
|
300
|
+
},
|
|
301
|
+
disabled: isDragging,
|
|
302
|
+
activeOpacity: 0.85,
|
|
303
|
+
style: containerStyle,
|
|
304
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
305
|
+
style: dotStyle
|
|
306
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
307
|
+
style: textStyle,
|
|
308
|
+
children: config.label
|
|
309
|
+
})]
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// =============================
|
|
314
|
+
// Helpers (using shared filterValidChildren from core)
|
|
315
|
+
// =============================
|
|
316
|
+
function interleaveWithDividers(childrenArray) {
|
|
317
|
+
const validChildren = filterValidChildren(childrenArray);
|
|
318
|
+
const result = [];
|
|
319
|
+
validChildren.forEach((child, index) => {
|
|
320
|
+
result.push(child);
|
|
321
|
+
if (index < validChildren.length - 1) result.push(/*#__PURE__*/_jsx(Divider, {}, `divider-${index}`));
|
|
322
|
+
});
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// =============================
|
|
327
|
+
// Main Component (presentation only)
|
|
328
|
+
// =============================
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* FloatingTools - A draggable, resizable bubble for development tools
|
|
332
|
+
*
|
|
333
|
+
* This component provides a floating bubble interface that can contain various
|
|
334
|
+
* development tools and controls. It features:
|
|
335
|
+
* - Drag and drop positioning with boundary constraints
|
|
336
|
+
* - Hide/show functionality by dragging to screen edge
|
|
337
|
+
* - Position persistence across app restarts
|
|
338
|
+
* - Safe area aware positioning
|
|
339
|
+
* - Automatic divider insertion between child components
|
|
340
|
+
*
|
|
341
|
+
* @param props - Configuration for the floating tools
|
|
342
|
+
* @param props.enablePositionPersistence - Whether to save/restore position (default: true)
|
|
343
|
+
* @param props.children - Child components to render in the bubble
|
|
344
|
+
*
|
|
345
|
+
* @returns JSX.Element representing the floating tools bubble
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* <FloatingTools enablePositionPersistence={true}>
|
|
350
|
+
* <UserStatus userRole="admin" onPress={handleUserPress} />
|
|
351
|
+
* <ToolButton onPress={openSettings} />
|
|
352
|
+
* </FloatingTools>
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* @performance Uses native driver animations for smooth positioning
|
|
356
|
+
* @performance Implements efficient boundary checking and position validation
|
|
357
|
+
* @performance Includes debounced position saving for optimal storage performance
|
|
358
|
+
*/
|
|
359
|
+
export function FloatingTools({
|
|
360
|
+
enablePositionPersistence = true,
|
|
361
|
+
pushToSide = false,
|
|
362
|
+
centerOnboarding = false,
|
|
363
|
+
children,
|
|
364
|
+
environment,
|
|
365
|
+
availableEnvironments,
|
|
366
|
+
onEnvironmentSwitch,
|
|
367
|
+
showEnvironmentSelector = false
|
|
368
|
+
}) {
|
|
369
|
+
// Animated position and drag state
|
|
370
|
+
const animatedPosition = useRef(new Animated.ValueXY()).current;
|
|
371
|
+
const saveTimeoutRef = useRef(null);
|
|
372
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
373
|
+
const [bubbleSize, setBubbleSize] = useState({
|
|
374
|
+
width: 100,
|
|
375
|
+
height: 32
|
|
376
|
+
});
|
|
377
|
+
const [isHidden, setIsHidden] = useState(false);
|
|
378
|
+
|
|
379
|
+
// Store the position before hiding to restore when showing
|
|
380
|
+
const savedPositionRef = useRef(null);
|
|
381
|
+
|
|
382
|
+
// Track if the hide state was triggered by pushToSide prop vs user toggle
|
|
383
|
+
const isPushedBySideRef = useRef(false);
|
|
384
|
+
|
|
385
|
+
// Track if user has explicitly chosen to show the menu (overriding pushToSide)
|
|
386
|
+
const userWantsVisibleRef = useRef(false);
|
|
387
|
+
|
|
388
|
+
// Track previous pushToSide value to detect transitions
|
|
389
|
+
const prevPushToSideRef = useRef(pushToSide);
|
|
390
|
+
const safeAreaInsets = useSafeAreaInsets();
|
|
391
|
+
const {
|
|
392
|
+
width: screenWidth,
|
|
393
|
+
height: screenHeight
|
|
394
|
+
} = Dimensions.get("window");
|
|
395
|
+
|
|
396
|
+
// Position persistence (state/IO extracted to hook)
|
|
397
|
+
const {
|
|
398
|
+
savePosition
|
|
399
|
+
} = useFloatingToolsPosition({
|
|
400
|
+
animatedPosition,
|
|
401
|
+
bubbleWidth: bubbleSize.width,
|
|
402
|
+
bubbleHeight: bubbleSize.height,
|
|
403
|
+
enabled: enablePositionPersistence,
|
|
404
|
+
visibleHandleWidth: 32,
|
|
405
|
+
listenersSuspended: isDragging
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Effect to handle pushToSide prop changes
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
// Reset user override when pushToSide becomes true (dial/modal opens)
|
|
411
|
+
// This allows auto-hide to work after user manually showed the menu
|
|
412
|
+
if (!prevPushToSideRef.current && pushToSide) {
|
|
413
|
+
userWantsVisibleRef.current = false;
|
|
414
|
+
}
|
|
415
|
+
prevPushToSideRef.current = pushToSide;
|
|
416
|
+
|
|
417
|
+
// Only push to side if:
|
|
418
|
+
// 1. pushToSide is true
|
|
419
|
+
// 2. Not currently hidden
|
|
420
|
+
// 3. Not being dragged
|
|
421
|
+
// 4. User hasn't manually restored (userWantsVisibleRef)
|
|
422
|
+
if (pushToSide && !isHidden && !isDragging && !userWantsVisibleRef.current) {
|
|
423
|
+
// Push to side
|
|
424
|
+
const currentX = animatedPosition.x.__getValue();
|
|
425
|
+
const currentY = animatedPosition.y.__getValue();
|
|
426
|
+
|
|
427
|
+
// Save current position
|
|
428
|
+
savedPositionRef.current = {
|
|
429
|
+
x: currentX,
|
|
430
|
+
y: currentY
|
|
431
|
+
};
|
|
432
|
+
const hiddenX = screenWidth - 32; // Show only the grabber
|
|
433
|
+
isPushedBySideRef.current = true;
|
|
434
|
+
setIsHidden(true);
|
|
435
|
+
Animated.timing(animatedPosition, {
|
|
436
|
+
toValue: {
|
|
437
|
+
x: hiddenX,
|
|
438
|
+
y: currentY
|
|
439
|
+
},
|
|
440
|
+
duration: 200,
|
|
441
|
+
useNativeDriver: false
|
|
442
|
+
}).start(() => {
|
|
443
|
+
savePosition(hiddenX, currentY);
|
|
444
|
+
});
|
|
445
|
+
} else if (!pushToSide && isHidden && isPushedBySideRef.current) {
|
|
446
|
+
// Restore from side when pushToSide becomes false
|
|
447
|
+
if (savedPositionRef.current) {
|
|
448
|
+
const {
|
|
449
|
+
x: targetX,
|
|
450
|
+
y: targetY
|
|
451
|
+
} = savedPositionRef.current;
|
|
452
|
+
isPushedBySideRef.current = false;
|
|
453
|
+
userWantsVisibleRef.current = false; // Reset user override when tools close
|
|
454
|
+
setIsHidden(false);
|
|
455
|
+
Animated.timing(animatedPosition, {
|
|
456
|
+
toValue: {
|
|
457
|
+
x: targetX,
|
|
458
|
+
y: targetY
|
|
459
|
+
},
|
|
460
|
+
duration: 200,
|
|
461
|
+
useNativeDriver: false
|
|
462
|
+
}).start(() => {
|
|
463
|
+
savePosition(targetX, targetY);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}, [pushToSide, isHidden, isDragging, screenWidth, animatedPosition, savePosition]);
|
|
468
|
+
|
|
469
|
+
// Check if bubble is in hidden position on load
|
|
470
|
+
useEffect(() => {
|
|
471
|
+
if (!enablePositionPersistence) return;
|
|
472
|
+
const checkHiddenState = () => {
|
|
473
|
+
const currentX = animatedPosition.x.__getValue();
|
|
474
|
+
// Check if bubble is at the hidden position (showing only grabber)
|
|
475
|
+
if (currentX >= screenWidth - 32 - 5) {
|
|
476
|
+
setIsHidden(true);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
// Delay check to ensure position is loaded
|
|
480
|
+
const timer = setTimeout(checkHiddenState, 100);
|
|
481
|
+
return () => clearTimeout(timer);
|
|
482
|
+
}, [enablePositionPersistence, animatedPosition, screenWidth]);
|
|
483
|
+
|
|
484
|
+
// Default position when persistence disabled or during onboarding
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
if (!enablePositionPersistence) {
|
|
487
|
+
if (centerOnboarding) {
|
|
488
|
+
// Center the bubble for onboarding - position under tooltip arrow
|
|
489
|
+
// Use shared calculation to ensure perfect alignment across all screens
|
|
490
|
+
const bottomOffset = calculateTargetPosition();
|
|
491
|
+
const centerX = (screenWidth - bubbleSize.width) / 2;
|
|
492
|
+
const centerY = screenHeight - bubbleSize.height - bottomOffset;
|
|
493
|
+
animatedPosition.setValue({
|
|
494
|
+
x: centerX,
|
|
495
|
+
y: centerY
|
|
496
|
+
});
|
|
497
|
+
} else {
|
|
498
|
+
// Default right-side position
|
|
499
|
+
const defaultY = Math.max(safeAreaInsets.top + 20, Math.min(100, screenHeight - bubbleSize.height - safeAreaInsets.bottom));
|
|
500
|
+
animatedPosition.setValue({
|
|
501
|
+
x: screenWidth - bubbleSize.width - 20,
|
|
502
|
+
y: defaultY
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}, [enablePositionPersistence, centerOnboarding, animatedPosition, bubbleSize.width, bubbleSize.height, safeAreaInsets.top, safeAreaInsets.bottom, screenWidth, screenHeight]);
|
|
507
|
+
|
|
508
|
+
// Cleanup timeout on component unmount
|
|
509
|
+
useEffect(() => {
|
|
510
|
+
return () => {
|
|
511
|
+
if (saveTimeoutRef.current) {
|
|
512
|
+
clearTimeout(saveTimeoutRef.current);
|
|
513
|
+
saveTimeoutRef.current = null;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}, []);
|
|
517
|
+
|
|
518
|
+
// Toggle hide/show function
|
|
519
|
+
const toggleHideShow = useCallback(() => {
|
|
520
|
+
const currentX = animatedPosition.x.__getValue();
|
|
521
|
+
const currentY = animatedPosition.y.__getValue();
|
|
522
|
+
|
|
523
|
+
// Check if the menu is visually off-screen (more than half hidden to the right)
|
|
524
|
+
// This handles edge cases where isHidden state might be out of sync with actual position
|
|
525
|
+
const isVisuallyOffScreen = currentX > screenWidth - bubbleSize.width / 2;
|
|
526
|
+
if (isHidden || isVisuallyOffScreen) {
|
|
527
|
+
// Show the bubble - restore to saved position or default visible position
|
|
528
|
+
let targetX;
|
|
529
|
+
let targetY;
|
|
530
|
+
if (savedPositionRef.current && savedPositionRef.current.x < screenWidth - bubbleSize.width / 2) {
|
|
531
|
+
// Restore to the saved position only if it's a valid visible position
|
|
532
|
+
targetX = savedPositionRef.current.x;
|
|
533
|
+
targetY = savedPositionRef.current.y;
|
|
534
|
+
} else {
|
|
535
|
+
// Default visible position if no saved position or saved position is off-screen
|
|
536
|
+
targetX = screenWidth - bubbleSize.width - 20;
|
|
537
|
+
targetY = currentY;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// User explicitly wants the menu visible (overrides pushToSide)
|
|
541
|
+
isPushedBySideRef.current = false;
|
|
542
|
+
userWantsVisibleRef.current = true;
|
|
543
|
+
setIsHidden(false);
|
|
544
|
+
Animated.timing(animatedPosition, {
|
|
545
|
+
toValue: {
|
|
546
|
+
x: targetX,
|
|
547
|
+
y: targetY
|
|
548
|
+
},
|
|
549
|
+
duration: 200,
|
|
550
|
+
useNativeDriver: false
|
|
551
|
+
}).start(() => {
|
|
552
|
+
savePosition(targetX, targetY);
|
|
553
|
+
});
|
|
554
|
+
} else {
|
|
555
|
+
// Hide the bubble - save current position before hiding
|
|
556
|
+
savedPositionRef.current = {
|
|
557
|
+
x: currentX,
|
|
558
|
+
y: currentY
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// User explicitly wants the menu hidden
|
|
562
|
+
userWantsVisibleRef.current = false;
|
|
563
|
+
const hiddenX = screenWidth - 32; // Only show the 32px grabber
|
|
564
|
+
setIsHidden(true);
|
|
565
|
+
Animated.timing(animatedPosition, {
|
|
566
|
+
toValue: {
|
|
567
|
+
x: hiddenX,
|
|
568
|
+
y: currentY
|
|
569
|
+
},
|
|
570
|
+
duration: 200,
|
|
571
|
+
useNativeDriver: false
|
|
572
|
+
}).start(() => {
|
|
573
|
+
savePosition(hiddenX, currentY);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}, [animatedPosition, isHidden, bubbleSize.width, savePosition, screenWidth]);
|
|
577
|
+
const handleDragStart = useCallback(() => {
|
|
578
|
+
setIsDragging(true);
|
|
579
|
+
}, []);
|
|
580
|
+
const handleDragEnd = useCallback(finalPosition => {
|
|
581
|
+
let {
|
|
582
|
+
x: currentX,
|
|
583
|
+
y: currentY
|
|
584
|
+
} = finalPosition;
|
|
585
|
+
|
|
586
|
+
// Check if bubble is more than 50% over the right edge
|
|
587
|
+
const bubbleMidpoint = currentX + bubbleSize.width / 2;
|
|
588
|
+
const shouldHide = bubbleMidpoint > screenWidth;
|
|
589
|
+
if (shouldHide) {
|
|
590
|
+
// Animate to hidden position (only grabber visible)
|
|
591
|
+
const hiddenX = screenWidth - 32; // Only show the 32px grabber
|
|
592
|
+
setIsHidden(true);
|
|
593
|
+
Animated.timing(animatedPosition, {
|
|
594
|
+
toValue: {
|
|
595
|
+
x: hiddenX,
|
|
596
|
+
y: currentY
|
|
597
|
+
},
|
|
598
|
+
duration: 200,
|
|
599
|
+
useNativeDriver: false
|
|
600
|
+
}).start(() => {
|
|
601
|
+
savePosition(hiddenX, currentY);
|
|
602
|
+
});
|
|
603
|
+
} else {
|
|
604
|
+
// Check if we're in hidden state and user is pulling it back
|
|
605
|
+
if (isHidden && currentX < screenWidth - 32 - 10) {
|
|
606
|
+
setIsHidden(false);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Update saved position if bubble is in visible area (not hidden)
|
|
610
|
+
if (currentX < screenWidth - bubbleSize.width / 2) {
|
|
611
|
+
savedPositionRef.current = {
|
|
612
|
+
x: currentX,
|
|
613
|
+
y: currentY
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
savePosition(currentX, currentY);
|
|
617
|
+
}
|
|
618
|
+
setIsDragging(false);
|
|
619
|
+
}, [animatedPosition, bubbleSize.width, isHidden, savePosition, screenWidth]);
|
|
620
|
+
|
|
621
|
+
// Stable styles
|
|
622
|
+
const bubbleStyle = useMemo(() => ({
|
|
623
|
+
position: "absolute",
|
|
624
|
+
zIndex: 1001,
|
|
625
|
+
transform: animatedPosition.getTranslateTransform()
|
|
626
|
+
}), [animatedPosition]);
|
|
627
|
+
|
|
628
|
+
// Get minimized tools context and app host for restoration
|
|
629
|
+
const {
|
|
630
|
+
minimizedTools
|
|
631
|
+
} = useMinimizedTools();
|
|
632
|
+
const {
|
|
633
|
+
restore: restoreInAppHost
|
|
634
|
+
} = useAppHost();
|
|
635
|
+
|
|
636
|
+
// Check if minimized tools are showing (for seamless connection)
|
|
637
|
+
const hasMinimizedTools = minimizedTools.length > 0;
|
|
638
|
+
const containerStyle = {
|
|
639
|
+
flexDirection: "row",
|
|
640
|
+
alignItems: "center",
|
|
641
|
+
backgroundColor: buoyColors.card,
|
|
642
|
+
// When minimized tools showing, remove top-left radius so they connect seamlessly
|
|
643
|
+
borderTopLeftRadius: hasMinimizedTools ? 0 : 6,
|
|
644
|
+
borderTopRightRadius: 6,
|
|
645
|
+
borderBottomLeftRadius: 6,
|
|
646
|
+
borderBottomRightRadius: 6,
|
|
647
|
+
// Use individual border widths when minimized tools are showing
|
|
648
|
+
// Always show top border when dragging for visual feedback
|
|
649
|
+
borderLeftWidth: isDragging ? 2 : 1,
|
|
650
|
+
borderRightWidth: isDragging ? 2 : 1,
|
|
651
|
+
borderBottomWidth: isDragging ? 2 : 1,
|
|
652
|
+
borderTopWidth: isDragging ? 2 : hasMinimizedTools ? 0 : 1,
|
|
653
|
+
borderColor: isDragging ? floatingToolsColors.dragActive : buoyColors.textMuted + "66",
|
|
654
|
+
overflow: "visible",
|
|
655
|
+
elevation: 8,
|
|
656
|
+
shadowColor: isDragging ? floatingToolsColors.dragActive + "99" : "#000",
|
|
657
|
+
shadowOffset: {
|
|
658
|
+
width: 0,
|
|
659
|
+
height: isDragging ? 6 : 4
|
|
660
|
+
},
|
|
661
|
+
shadowOpacity: isDragging ? 0.6 : 0.3,
|
|
662
|
+
shadowRadius: isDragging ? 12 : 8
|
|
663
|
+
};
|
|
664
|
+
const dragHandleStyle = {
|
|
665
|
+
paddingHorizontal: 6,
|
|
666
|
+
paddingVertical: 6,
|
|
667
|
+
backgroundColor: buoyColors.textMuted + "1A",
|
|
668
|
+
alignItems: "center",
|
|
669
|
+
justifyContent: "center",
|
|
670
|
+
width: 32,
|
|
671
|
+
borderRightWidth: 1,
|
|
672
|
+
borderRightColor: buoyColors.textMuted + "66",
|
|
673
|
+
// Remove top-left radius when minimized tools are showing
|
|
674
|
+
borderTopLeftRadius: hasMinimizedTools ? 0 : undefined
|
|
675
|
+
};
|
|
676
|
+
const contentStyle = {
|
|
677
|
+
flexDirection: "row",
|
|
678
|
+
alignItems: "center",
|
|
679
|
+
gap: 6,
|
|
680
|
+
paddingRight: 8
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// Compose actions row with automatic dividers
|
|
684
|
+
const actions = useMemo(() => interleaveWithDividers(Children.toArray(children)), [children]);
|
|
685
|
+
|
|
686
|
+
// Handle restore from minimized tools stack
|
|
687
|
+
const handleMinimizedRestore = useCallback(tool => {
|
|
688
|
+
restoreInAppHost(tool.instanceId, tool.modalState);
|
|
689
|
+
}, [restoreInAppHost]);
|
|
690
|
+
|
|
691
|
+
// Width for the minimized tools stack - match the drag handle width
|
|
692
|
+
const minimizedStackWidth = 32;
|
|
693
|
+
return /*#__PURE__*/_jsx(Animated.View, {
|
|
694
|
+
style: bubbleStyle,
|
|
695
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
696
|
+
style: {
|
|
697
|
+
overflow: "visible"
|
|
698
|
+
},
|
|
699
|
+
children: [minimizedTools.length > 0 && /*#__PURE__*/_jsx(View, {
|
|
700
|
+
style: {
|
|
701
|
+
position: "absolute",
|
|
702
|
+
bottom: "100%",
|
|
703
|
+
left: 0,
|
|
704
|
+
width: minimizedStackWidth,
|
|
705
|
+
overflow: "visible",
|
|
706
|
+
zIndex: 1001
|
|
707
|
+
},
|
|
708
|
+
children: /*#__PURE__*/_jsx(MinimizedToolsStack, {
|
|
709
|
+
onRestore: handleMinimizedRestore,
|
|
710
|
+
containerWidth: minimizedStackWidth
|
|
711
|
+
})
|
|
712
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
713
|
+
hitSlop: {
|
|
714
|
+
top: 10,
|
|
715
|
+
bottom: 10,
|
|
716
|
+
left: 10,
|
|
717
|
+
right: 10
|
|
718
|
+
},
|
|
719
|
+
style: containerStyle,
|
|
720
|
+
onLayout: event => {
|
|
721
|
+
const {
|
|
722
|
+
width,
|
|
723
|
+
height
|
|
724
|
+
} = event.nativeEvent.layout;
|
|
725
|
+
setBubbleSize({
|
|
726
|
+
width,
|
|
727
|
+
height
|
|
728
|
+
});
|
|
729
|
+
},
|
|
730
|
+
children: [/*#__PURE__*/_jsx(DraggableHeader, {
|
|
731
|
+
position: animatedPosition,
|
|
732
|
+
onDragStart: handleDragStart,
|
|
733
|
+
onDragEnd: handleDragEnd,
|
|
734
|
+
onTap: toggleHideShow,
|
|
735
|
+
containerBounds: {
|
|
736
|
+
width: screenWidth,
|
|
737
|
+
height: screenHeight
|
|
738
|
+
},
|
|
739
|
+
elementSize: bubbleSize,
|
|
740
|
+
minPosition: {
|
|
741
|
+
x: safeAreaInsets.left,
|
|
742
|
+
y: safeAreaInsets.top + 20
|
|
743
|
+
},
|
|
744
|
+
style: dragHandleStyle,
|
|
745
|
+
enabled: true,
|
|
746
|
+
maxOverflowX: bubbleSize.width,
|
|
747
|
+
children: /*#__PURE__*/_jsx(GripVerticalIcon, {
|
|
748
|
+
size: 12,
|
|
749
|
+
color: buoyColors.textSecondary + "CC"
|
|
750
|
+
})
|
|
751
|
+
}), /*#__PURE__*/_jsx(FloatingToolsContext.Provider, {
|
|
752
|
+
value: {
|
|
753
|
+
isDragging
|
|
754
|
+
},
|
|
755
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
756
|
+
style: contentStyle,
|
|
757
|
+
children: [showEnvironmentSelector && environment && availableEnvironments && onEnvironmentSwitch && /*#__PURE__*/_jsx(EnvironmentSelectorInline, {
|
|
758
|
+
currentEnvironment: environment,
|
|
759
|
+
availableEnvironments: availableEnvironments,
|
|
760
|
+
onSelect: onEnvironmentSwitch
|
|
761
|
+
}), actions]
|
|
762
|
+
})
|
|
763
|
+
})]
|
|
764
|
+
})]
|
|
765
|
+
})
|
|
766
|
+
});
|
|
767
|
+
}
|