@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.
Files changed (132) hide show
  1. package/README.md +43 -0
  2. package/lib/commonjs/floatingMenu/AppHost.js +410 -0
  3. package/lib/commonjs/floatingMenu/AppHostLogic.js +44 -0
  4. package/lib/commonjs/floatingMenu/DefaultConfigContext.js +45 -0
  5. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +2274 -0
  6. package/lib/commonjs/floatingMenu/DevToolsVisibilityContext.js +49 -0
  7. package/lib/commonjs/floatingMenu/DraggableHeader.js +114 -0
  8. package/lib/commonjs/floatingMenu/FloatingDevTools.js +254 -0
  9. package/lib/commonjs/floatingMenu/FloatingMenu.js +364 -0
  10. package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +247 -0
  11. package/lib/commonjs/floatingMenu/MinimizedToolsStack.js +206 -0
  12. package/lib/commonjs/floatingMenu/ToggleStateManager.js +36 -0
  13. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +241 -0
  14. package/lib/commonjs/floatingMenu/defaultConfig.js +160 -0
  15. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +835 -0
  16. package/lib/commonjs/floatingMenu/dial/DialIcon.js +246 -0
  17. package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +249 -0
  18. package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +70 -0
  19. package/lib/commonjs/floatingMenu/floatingTools.js +771 -0
  20. package/lib/commonjs/floatingMenu/settingsBus.js +23 -0
  21. package/lib/commonjs/floatingMenu/types.js +5 -0
  22. package/lib/commonjs/index.js +240 -0
  23. package/lib/commonjs/package.json +1 -0
  24. package/lib/module/floatingMenu/AppHost.js +402 -0
  25. package/lib/module/floatingMenu/AppHostLogic.js +39 -0
  26. package/lib/module/floatingMenu/DefaultConfigContext.js +39 -0
  27. package/lib/module/floatingMenu/DevToolsSettingsModal.js +2273 -0
  28. package/lib/module/floatingMenu/DevToolsVisibilityContext.js +44 -0
  29. package/lib/module/floatingMenu/DraggableHeader.js +110 -0
  30. package/lib/module/floatingMenu/FloatingDevTools.js +249 -0
  31. package/lib/module/floatingMenu/FloatingMenu.js +358 -0
  32. package/lib/module/floatingMenu/MinimizedToolsContext.js +239 -0
  33. package/lib/module/floatingMenu/MinimizedToolsStack.js +202 -0
  34. package/lib/module/floatingMenu/ToggleStateManager.js +32 -0
  35. package/lib/module/floatingMenu/autoDiscoverPresets.js +236 -0
  36. package/lib/module/floatingMenu/defaultConfig.js +151 -0
  37. package/lib/module/floatingMenu/dial/DialDevTools.js +829 -0
  38. package/lib/module/floatingMenu/dial/DialIcon.js +241 -0
  39. package/lib/module/floatingMenu/dial/OnboardingTooltip.js +244 -0
  40. package/lib/module/floatingMenu/dial/onboardingConstants.js +64 -0
  41. package/lib/module/floatingMenu/floatingTools.js +767 -0
  42. package/lib/module/floatingMenu/settingsBus.js +19 -0
  43. package/lib/module/floatingMenu/types.js +3 -0
  44. package/lib/module/index.js +29 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts +39 -0
  47. package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts +37 -0
  49. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +1 -0
  50. package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts +27 -0
  51. package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts.map +1 -0
  52. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +57 -0
  53. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts +25 -0
  55. package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts.map +1 -0
  56. package/lib/typescript/commonjs/floatingMenu/DraggableHeader.d.ts +30 -0
  57. package/lib/typescript/commonjs/floatingMenu/DraggableHeader.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +226 -0
  59. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts +39 -0
  61. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts +95 -0
  63. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts.map +1 -0
  64. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts +10 -0
  65. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts.map +1 -0
  66. package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts +21 -0
  67. package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts.map +1 -0
  68. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts +75 -0
  69. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +120 -0
  71. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -0
  72. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +35 -0
  73. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts +14 -0
  75. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts +12 -0
  77. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts +30 -0
  79. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts +56 -0
  81. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +1 -0
  82. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts +10 -0
  83. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/floatingMenu/types.d.ts +56 -0
  85. package/lib/typescript/commonjs/floatingMenu/types.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/index.d.ts +18 -0
  87. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/package.json +1 -0
  89. package/lib/typescript/module/floatingMenu/AppHost.d.ts +39 -0
  90. package/lib/typescript/module/floatingMenu/AppHost.d.ts.map +1 -0
  91. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts +37 -0
  92. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +1 -0
  93. package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts +27 -0
  94. package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts.map +1 -0
  95. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +57 -0
  96. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -0
  97. package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts +25 -0
  98. package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts.map +1 -0
  99. package/lib/typescript/module/floatingMenu/DraggableHeader.d.ts +30 -0
  100. package/lib/typescript/module/floatingMenu/DraggableHeader.d.ts.map +1 -0
  101. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +226 -0
  102. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -0
  103. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts +39 -0
  104. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -0
  105. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts +95 -0
  106. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts.map +1 -0
  107. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts +10 -0
  108. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts.map +1 -0
  109. package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts +21 -0
  110. package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts.map +1 -0
  111. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts +75 -0
  112. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -0
  113. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +120 -0
  114. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -0
  115. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +35 -0
  116. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -0
  117. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts +14 -0
  118. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -0
  119. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts +12 -0
  120. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -0
  121. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts +30 -0
  122. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +1 -0
  123. package/lib/typescript/module/floatingMenu/floatingTools.d.ts +56 -0
  124. package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +1 -0
  125. package/lib/typescript/module/floatingMenu/settingsBus.d.ts +10 -0
  126. package/lib/typescript/module/floatingMenu/settingsBus.d.ts.map +1 -0
  127. package/lib/typescript/module/floatingMenu/types.d.ts +56 -0
  128. package/lib/typescript/module/floatingMenu/types.d.ts.map +1 -0
  129. package/lib/typescript/module/index.d.ts +18 -0
  130. package/lib/typescript/module/index.d.ts.map +1 -0
  131. package/lib/typescript/module/package.json +1 -0
  132. package/package.json +79 -0
@@ -0,0 +1,2273 @@
1
+ "use strict";
2
+
3
+ import { useState, useEffect, useCallback, useMemo } from "react";
4
+ import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Dimensions } from "react-native";
5
+ import { settingsBus } from "./settingsBus.js";
6
+ import { SentryBugIcon, WifiCircuitIcon, BenchmarkIcon, RenderCountIcon, Info, Layers, ChevronRightIcon, ChevronDown, safeGetItem, safeSetItem, getStorageBackendType, persistentStorage, Database, Trash2, CheckCircle2, AlertTriangle, Zap, FileText, HardDrive, Copy, FileCode, RefreshCw, Smartphone, Plus, copyToClipboard, LicenseEntryModal, SectionHeader } from "@buoy-gg/shared-ui";
7
+ import { EnvIcon, StorageIcon, RoutesIcon, NetworkIcon, QueryIcon, HighlighterIcon } from "@buoy-gg/floating-tools-core";
8
+ import { useDefaultConfig } from "./DefaultConfigContext.js";
9
+ import { JsModal, devToolsStorageKeys, safeGetItem as sharedSafeGetItem, safeSetItem as sharedSafeSetItem } from "@buoy-gg/shared-ui";
10
+ import { useSafeAreaInsets } from "@buoy-gg/shared-ui";
11
+ import { ModalHeader } from "@buoy-gg/shared-ui";
12
+ import { TabSelector } from "@buoy-gg/shared-ui";
13
+ import { buoyColors } from "@buoy-gg/shared-ui";
14
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
15
+ const STORAGE_KEY = "@react_buoy_dev_tools_settings";
16
+ const MAX_DIAL_SLOTS = 6;
17
+
18
+ // Lazy load license hooks to avoid circular dependencies
19
+ let _useLicense = null;
20
+ let _useSeats = null;
21
+ let _useDevices = null;
22
+ function getUseLicense() {
23
+ if (!_useLicense) {
24
+ try {
25
+ const mod = require("@buoy-gg/license");
26
+ _useLicense = mod.useLicense;
27
+ } catch {
28
+ // License package not available
29
+ }
30
+ }
31
+ return _useLicense;
32
+ }
33
+ function getUseSeats() {
34
+ if (!_useSeats) {
35
+ try {
36
+ const mod = require("@buoy-gg/license");
37
+ _useSeats = mod.useSeats;
38
+ } catch {
39
+ // License package not available
40
+ }
41
+ }
42
+ return _useSeats;
43
+ }
44
+ function getUseDevices() {
45
+ if (!_useDevices) {
46
+ try {
47
+ const mod = require("@buoy-gg/license");
48
+ _useDevices = mod.useDevices;
49
+ } catch {
50
+ // License package not available
51
+ }
52
+ }
53
+ return _useDevices;
54
+ }
55
+ const enforceDialLimit = dialTools => {
56
+ let remaining = MAX_DIAL_SLOTS;
57
+ const limited = {};
58
+ for (const [id, enabled] of Object.entries(dialTools)) {
59
+ if (enabled && remaining > 0) {
60
+ limited[id] = true;
61
+ remaining -= 1;
62
+ } else {
63
+ limited[id] = false;
64
+ }
65
+ }
66
+ return limited;
67
+ };
68
+ const sanitizeFloating = (floating, allowedKeys) => {
69
+ const {
70
+ userStatus,
71
+ environment,
72
+ ...rest
73
+ } = floating;
74
+ const filteredEntries = allowedKeys ? Object.entries(rest).filter(([key]) => allowedKeys.includes(key)) : Object.entries(rest);
75
+ return {
76
+ ...Object.fromEntries(filteredEntries),
77
+ environment: environment ?? false
78
+ };
79
+ };
80
+ const mergeWithDefaults = (defaults, stored, options) => {
81
+ if (!stored) return defaults;
82
+ const combinedDial = {
83
+ ...defaults.dialTools,
84
+ ...(stored.dialTools ?? {})
85
+ };
86
+ const dialEntries = options?.allowedDialKeys ? Object.entries(combinedDial).filter(([key]) => options.allowedDialKeys?.includes(key)) : Object.entries(combinedDial);
87
+ const mergedDial = enforceDialLimit(Object.fromEntries(dialEntries));
88
+ return {
89
+ dialTools: mergedDial,
90
+ floatingTools: sanitizeFloating({
91
+ ...defaults.floatingTools,
92
+ ...(stored.floatingTools ?? {})
93
+ }, options?.allowedFloatingKeys),
94
+ globalSettings: {
95
+ enableSharedModalDimensions: false,
96
+ // Default to false
97
+ ...(stored.globalSettings ?? {})
98
+ }
99
+ };
100
+ };
101
+
102
+ /**
103
+ * Global settings that apply to all dev tools.
104
+ * These settings override individual tool configurations.
105
+ */
106
+
107
+ /**
108
+ * Serialized preferences that power the floating dev tools UI. Values persist across sessions
109
+ * via AsyncStorage so teams can tailor which tools appear in the dial or floating row.
110
+ */
111
+
112
+ /**
113
+ * Generate default settings based on available apps and optional team default configuration.
114
+ *
115
+ * @param availableApps - List of available apps from auto-discovery
116
+ * @param defaultFloatingTools - Optional array of tool IDs to enable by default in floating bubble
117
+ * @param defaultDialTools - Optional array of tool IDs to enable by default in dial menu
118
+ */
119
+ const generateDefaultSettings = (availableApps = [], defaultFloatingTools, defaultDialTools) => {
120
+ const dialDefaults = {};
121
+ const floatingDefaults = {};
122
+
123
+ // Create sets for quick lookup of default-enabled tools
124
+ // Cast to Set<string> to allow comparison with any tool ID (including custom tools)
125
+ const enabledFloatingSet = new Set(defaultFloatingTools ?? []);
126
+ const enabledDialSet = new Set(defaultDialTools ?? []);
127
+ for (const app of availableApps) {
128
+ const {
129
+ id,
130
+ slot = "both"
131
+ } = app;
132
+ if (slot === "dial" || slot === "both") {
133
+ // Enable if in defaultDialTools, otherwise false
134
+ dialDefaults[id] = enabledDialSet.has(id);
135
+ }
136
+ if (slot === "row" || slot === "both") {
137
+ // Enable if in defaultFloatingTools, otherwise false
138
+ floatingDefaults[id] = enabledFloatingSet.has(id);
139
+ }
140
+ }
141
+ return {
142
+ dialTools: enforceDialLimit(dialDefaults),
143
+ floatingTools: {
144
+ ...floatingDefaults,
145
+ // Special: environment badge - check if 'environment' is in the floating defaults
146
+ environment: enabledFloatingSet.has('environment')
147
+ },
148
+ globalSettings: {
149
+ enableSharedModalDimensions: false // Default to false - each modal has its own persistence
150
+ }
151
+ };
152
+ };
153
+
154
+ /**
155
+ * Configurable modal surface that lets engineers pick which dev tools appear in the dial and
156
+ * floating row. Persists preferences and enforces slot limits for a consistent UX.
157
+ */
158
+ export const DevToolsSettingsModal = ({
159
+ visible,
160
+ onClose,
161
+ onSettingsChange,
162
+ initialSettings,
163
+ availableApps = []
164
+ }) => {
165
+ // Get team default configuration from context
166
+ const {
167
+ defaultFloatingTools,
168
+ defaultDialTools
169
+ } = useDefaultConfig();
170
+ const defaultSettings = useMemo(() => generateDefaultSettings(availableApps, defaultFloatingTools, defaultDialTools), [availableApps, defaultFloatingTools, defaultDialTools]);
171
+ const allowedDialKeys = useMemo(() => Object.keys(defaultSettings.dialTools), [defaultSettings]);
172
+ const allowedFloatingKeys = useMemo(() => Object.keys(defaultSettings.floatingTools).filter(key => key !== "environment"), [defaultSettings]);
173
+ const [settings, setSettings] = useState(initialSettings || defaultSettings);
174
+ const [activeTab, setActiveTab] = useState("dial");
175
+ const [activeTabLoaded, setActiveTabLoaded] = useState(false);
176
+ const [expandedSettings, setExpandedSettings] = useState(new Set());
177
+ const [showLicenseModal, setShowLicenseModal] = useState(false);
178
+ const [licenseModalForDeviceRegistration, setLicenseModalForDeviceRegistration] = useState(false);
179
+
180
+ // Load persisted active tab on mount
181
+ useEffect(() => {
182
+ const loadActiveTab = async () => {
183
+ try {
184
+ const savedTab = await sharedSafeGetItem(devToolsStorageKeys.settings.activeTab());
185
+ if (savedTab && ["dial", "floating", "settings", "pro"].includes(savedTab)) {
186
+ setActiveTab(savedTab);
187
+ }
188
+ } catch (error) {
189
+ // Failed to load active tab - use default
190
+ } finally {
191
+ setActiveTabLoaded(true);
192
+ }
193
+ };
194
+ loadActiveTab();
195
+ }, []);
196
+
197
+ // Persist active tab when it changes
198
+ useEffect(() => {
199
+ // Only persist after initial state is loaded to avoid overwriting with default
200
+ if (!activeTabLoaded) return;
201
+ sharedSafeSetItem(devToolsStorageKeys.settings.activeTab(), activeTab).catch(error => {
202
+ // Failed to save active tab - continue without persistence
203
+ console.warn("Failed to save settings active tab:", error);
204
+ });
205
+ }, [activeTab, activeTabLoaded]);
206
+
207
+ // License hooks
208
+ const useLicenseHook = getUseLicense();
209
+ const useSeatsHook = getUseSeats();
210
+ const useDevicesHook = getUseDevices();
211
+ const license = useLicenseHook?.();
212
+ const seats = useSeatsHook?.();
213
+ const devicesData = useDevicesHook?.();
214
+ const isPro = license?.isPro ?? false;
215
+
216
+ // Devices data
217
+ const devices = devicesData?.devices ?? [];
218
+ const devicesLoading = devicesData?.isLoading ?? false;
219
+ const devicesError = devicesData?.error ?? null;
220
+ const refreshDevices = devicesData?.refreshDevices;
221
+ const registerDevice = devicesData?.registerDevice;
222
+ const deactivateDevice = devicesData?.deactivateDevice;
223
+ const isCurrentDeviceRegistered = devicesData?.isCurrentDeviceRegistered ?? false;
224
+ const [deactivatingDeviceId, setDeactivatingDeviceId] = useState(null);
225
+ const [storageBackend, setStorageBackend] = useState(null);
226
+ const [isClearing, setIsClearing] = useState(false);
227
+ const [clearSuccess, setClearSuccess] = useState(false);
228
+ const [isStorageExpanded, setIsStorageExpanded] = useState(false);
229
+ const [savedKeys, setSavedKeys] = useState([]);
230
+ const [savedKeysLoading, setSavedKeysLoading] = useState(false);
231
+ const [copySuccess, setCopySuccess] = useState(false);
232
+ const [isExportExpanded, setIsExportExpanded] = useState(false);
233
+ const insets = useSafeAreaInsets();
234
+
235
+ // Check if in development mode - used to show dev-only features like Export Config
236
+ const isDevelopmentMode = typeof __DEV__ !== "undefined" && __DEV__;
237
+ const screenHeight = Dimensions.get("window").height;
238
+ const screenWidth = Dimensions.get("window").width;
239
+ const modalHeight = Math.floor(screenHeight * 0.33); // 1/3 of screen height
240
+ const modalWidth = Math.min(screenWidth - 32, 400); // Modal width with padding
241
+
242
+ const loadSettings = useCallback(async () => {
243
+ try {
244
+ const savedSettings = await safeGetItem(STORAGE_KEY);
245
+ if (savedSettings) {
246
+ const parsed = JSON.parse(savedSettings);
247
+ const merged = mergeWithDefaults(defaultSettings, parsed, {
248
+ allowedDialKeys,
249
+ allowedFloatingKeys
250
+ });
251
+ setSettings(merged);
252
+ return;
253
+ }
254
+ setSettings(defaultSettings);
255
+ } catch (error) {
256
+ console.error("Failed to load dev tools settings:", error);
257
+ setSettings(defaultSettings);
258
+ }
259
+ }, [defaultSettings, allowedDialKeys, allowedFloatingKeys]);
260
+ useEffect(() => {
261
+ loadSettings();
262
+ }, [loadSettings]);
263
+
264
+ // Load storage backend type
265
+ useEffect(() => {
266
+ getStorageBackendType().then(setStorageBackend);
267
+ }, []);
268
+
269
+ // Load saved keys when storage card is expanded
270
+ useEffect(() => {
271
+ if (isStorageExpanded) {
272
+ setSavedKeysLoading(true);
273
+ persistentStorage.getAllKeys().then(keys => {
274
+ setSavedKeys(keys.sort());
275
+ }).catch(() => {
276
+ setSavedKeys([]);
277
+ }).finally(() => {
278
+ setSavedKeysLoading(false);
279
+ });
280
+ }
281
+ }, [isStorageExpanded]);
282
+ const saveSettings = async newSettings => {
283
+ try {
284
+ const limitedSettings = {
285
+ ...newSettings,
286
+ dialTools: enforceDialLimit(newSettings.dialTools)
287
+ };
288
+ await safeSetItem(STORAGE_KEY, JSON.stringify(limitedSettings));
289
+ setSettings(limitedSettings);
290
+ onSettingsChange?.(limitedSettings);
291
+ // Notify listeners (e.g., floating bubble) to refresh immediately
292
+ settingsBus.emit(limitedSettings);
293
+ } catch (error) {
294
+ console.error("Failed to save dev tools settings:", error);
295
+ }
296
+ };
297
+ const toggleDialTool = tool => {
298
+ const currentEnabled = Object.values(settings.dialTools).filter(v => v).length;
299
+ const isCurrentlyEnabled = settings.dialTools[tool];
300
+
301
+ // If trying to enable and already at 6, don't allow
302
+ if (!isCurrentlyEnabled && currentEnabled >= MAX_DIAL_SLOTS) {
303
+ return; // Could also show a toast/alert here
304
+ }
305
+ const newSettings = {
306
+ ...settings,
307
+ dialTools: {
308
+ ...settings.dialTools,
309
+ [tool]: !settings.dialTools[tool]
310
+ }
311
+ };
312
+ saveSettings(newSettings);
313
+ };
314
+ const toggleFloatingTool = tool => {
315
+ const newSettings = {
316
+ ...settings,
317
+ floatingTools: {
318
+ ...settings.floatingTools,
319
+ [tool]: !settings.floatingTools[tool]
320
+ }
321
+ };
322
+ saveSettings(newSettings);
323
+ };
324
+ const toggleGlobalSetting = setting => {
325
+ const newSettings = {
326
+ ...settings,
327
+ globalSettings: {
328
+ ...settings.globalSettings,
329
+ [setting]: !settings.globalSettings?.[setting]
330
+ }
331
+ };
332
+ saveSettings(newSettings);
333
+ };
334
+ const handleClearStorage = async () => {
335
+ setIsClearing(true);
336
+ setClearSuccess(false);
337
+ try {
338
+ await persistentStorage.clear();
339
+ setClearSuccess(true);
340
+ // Reset settings to defaults after clearing
341
+ setSettings(defaultSettings);
342
+ settingsBus.emit(defaultSettings);
343
+ // Reset success state after 2 seconds
344
+ setTimeout(() => setClearSuccess(false), 2000);
345
+ } catch (error) {
346
+ console.error("Failed to clear storage:", error);
347
+ } finally {
348
+ setIsClearing(false);
349
+ }
350
+ };
351
+
352
+ // Get the enabled tools as arrays for display and copy
353
+ const enabledConfig = useMemo(() => {
354
+ const enabledFloating = Object.entries(settings.floatingTools).filter(([_, enabled]) => enabled).map(([id]) => id);
355
+ const enabledDial = Object.entries(settings.dialTools).filter(([_, enabled]) => enabled).map(([id]) => id);
356
+ return {
357
+ floating: enabledFloating,
358
+ dial: enabledDial
359
+ };
360
+ }, [settings]);
361
+
362
+ // Generate exportable code snippet from current settings
363
+ // Only outputs the defaultFloatingTools and defaultDialTools props
364
+ // so users can add them to their existing FloatingDevTools config
365
+ const generateConfigCode = useCallback(() => {
366
+ const {
367
+ floating,
368
+ dial
369
+ } = enabledConfig;
370
+
371
+ // Build only the default config props (not the full component)
372
+ const props = [];
373
+ if (floating.length > 0) {
374
+ const floatingStr = floating.map(id => `'${id}'`).join(', ');
375
+ props.push(`defaultFloatingTools={[${floatingStr}]}`);
376
+ }
377
+ if (dial.length > 0) {
378
+ const dialStr = dial.map(id => `'${id}'`).join(', ');
379
+ props.push(`defaultDialTools={[${dialStr}]}`);
380
+ }
381
+ if (props.length === 0) {
382
+ return `// No tools enabled`;
383
+ }
384
+ return props.join('\n');
385
+ }, [enabledConfig]);
386
+ const handleCopyConfig = async () => {
387
+ const code = generateConfigCode();
388
+ const success = await copyToClipboard(code);
389
+ if (success) {
390
+ setCopySuccess(true);
391
+ setTimeout(() => setCopySuccess(false), 2000);
392
+ }
393
+ };
394
+ const handleRemoveDevice = async (deviceId, deviceName, isCurrentDevice) => {
395
+ if (!deactivateDevice) return;
396
+ const message = isCurrentDevice ? "This will remove your current device. You'll need to re-register to use Pro features on this device." : `Remove "${deviceName}" from your license?`;
397
+ const {
398
+ Alert
399
+ } = require("react-native");
400
+ const doRemove = await new Promise(resolve => {
401
+ Alert.alert("Remove Device", message, [{
402
+ text: "Cancel",
403
+ style: "cancel",
404
+ onPress: () => resolve(false)
405
+ }, {
406
+ text: "Remove",
407
+ style: "destructive",
408
+ onPress: () => resolve(true)
409
+ }]);
410
+ });
411
+ if (!doRemove) return;
412
+ setDeactivatingDeviceId(deviceId);
413
+ try {
414
+ await deactivateDevice(deviceId);
415
+ } catch (error) {
416
+ console.error("Failed to remove device:", error);
417
+ } finally {
418
+ setDeactivatingDeviceId(null);
419
+ }
420
+ };
421
+ const handleRegisterDevice = () => {
422
+ // Open the license modal in device registration mode (skip license key entry)
423
+ setLicenseModalForDeviceRegistration(true);
424
+ setShowLicenseModal(true);
425
+ };
426
+ const formatDeviceDate = date => {
427
+ const now = new Date();
428
+ const diffMs = now.getTime() - date.getTime();
429
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
430
+ if (diffDays === 0) return "Today";
431
+ if (diffDays === 1) return "Yesterday";
432
+ if (diffDays < 7) return `${diffDays} days ago`;
433
+ return date.toLocaleDateString(undefined, {
434
+ month: "short",
435
+ day: "numeric",
436
+ year: date.getFullYear() !== now.getFullYear() ? "numeric" : undefined
437
+ });
438
+ };
439
+
440
+ // Modal is fixed to bottom sheet mode
441
+ const handleModeChange = useCallback(_mode => {
442
+ // Mode changes handled by JsModal
443
+ }, []);
444
+ const getToolDescription = tool => {
445
+ // Get description from availableApps
446
+ const app = availableApps.find(a => a.id === tool);
447
+ if (app?.description) {
448
+ return app.description;
449
+ }
450
+ if (tool === "environment") {
451
+ return "Environment badge.";
452
+ }
453
+ return "";
454
+ };
455
+ const getToolLabel = tool => {
456
+ if (tool === "environment") {
457
+ return "ENV BADGE";
458
+ }
459
+ const app = availableApps.find(a => a.id === tool);
460
+ return app?.name ?? tool.toUpperCase().replace(/_/g, " ");
461
+ };
462
+
463
+ // Clean tool card renderer - unified Buoy theme
464
+ const renderToolCard = (keyName, value, disabled, onToggle) => {
465
+ const getToolIcon = tool => {
466
+ switch (tool) {
467
+ case "query":
468
+ return /*#__PURE__*/_jsx(QueryIcon, {
469
+ size: 16
470
+ });
471
+ case "env":
472
+ return /*#__PURE__*/_jsx(EnvIcon, {
473
+ size: 16
474
+ });
475
+ case "sentry":
476
+ return /*#__PURE__*/_jsx(SentryBugIcon, {
477
+ size: 16,
478
+ colorPreset: "red",
479
+ noBackground: true
480
+ });
481
+ case "storage":
482
+ return /*#__PURE__*/_jsx(StorageIcon, {
483
+ size: 16
484
+ });
485
+ case "wifi":
486
+ case "query-wifi-toggle":
487
+ // Support both IDs for wifi toggle
488
+ return /*#__PURE__*/_jsx(WifiCircuitIcon, {
489
+ size: 16,
490
+ colorPreset: "green",
491
+ strength: 4,
492
+ noBackground: true
493
+ });
494
+ case "route-events":
495
+ return /*#__PURE__*/_jsx(RoutesIcon, {
496
+ size: 16
497
+ });
498
+ case "network":
499
+ return /*#__PURE__*/_jsx(NetworkIcon, {
500
+ size: 16
501
+ });
502
+ case "environment":
503
+ return /*#__PURE__*/_jsx(EnvIcon, {
504
+ size: 16
505
+ });
506
+ case "debug-borders":
507
+ return /*#__PURE__*/_jsx(Layers, {
508
+ size: 16,
509
+ color: buoyColors.primary
510
+ });
511
+ case "highlight-updates":
512
+ return /*#__PURE__*/_jsx(RenderCountIcon, {
513
+ size: 16,
514
+ color: buoyColors.primary
515
+ });
516
+ case "highlight-updates-modal":
517
+ return /*#__PURE__*/_jsx(HighlighterIcon, {
518
+ size: 16
519
+ });
520
+ case "benchmark":
521
+ return /*#__PURE__*/_jsx(BenchmarkIcon, {
522
+ size: 16,
523
+ color: buoyColors.textSecondary
524
+ });
525
+ default:
526
+ return /*#__PURE__*/_jsx(Info, {
527
+ size: 16,
528
+ color: buoyColors.textSecondary
529
+ });
530
+ }
531
+ };
532
+ return /*#__PURE__*/_jsx(TouchableOpacity, {
533
+ activeOpacity: disabled ? 1 : 0.85,
534
+ onPress: () => !disabled && onToggle(),
535
+ style: {
536
+ marginBottom: 8,
537
+ opacity: disabled ? 0.5 : 1
538
+ },
539
+ children: /*#__PURE__*/_jsx(View, {
540
+ style: [styles.glassCard, value && styles.glassCardActive],
541
+ children: /*#__PURE__*/_jsxs(View, {
542
+ style: styles.glassCardInner,
543
+ children: [/*#__PURE__*/_jsx(View, {
544
+ style: [styles.iconContainer, value && styles.iconContainerActive],
545
+ children: getToolIcon(keyName)
546
+ }), /*#__PURE__*/_jsxs(View, {
547
+ style: styles.toolInfo,
548
+ children: [/*#__PURE__*/_jsxs(Text, {
549
+ style: [styles.toolName, value && styles.toolNameActive],
550
+ children: [getToolLabel(keyName), disabled ? " (MAX 6)" : ""]
551
+ }), /*#__PURE__*/_jsx(Text, {
552
+ style: styles.toolDescription,
553
+ numberOfLines: 1,
554
+ children: getToolDescription(keyName)
555
+ })]
556
+ }), /*#__PURE__*/_jsx(View, {
557
+ style: [styles.pillToggle, value ? styles.pillToggleOn : styles.pillToggleOff, disabled && {
558
+ opacity: 0.5
559
+ }],
560
+ children: /*#__PURE__*/_jsx(Text, {
561
+ style: [styles.pillToggleText, value ? styles.pillToggleTextOn : styles.pillToggleTextOff],
562
+ children: value ? "ON" : "OFF"
563
+ })
564
+ })]
565
+ })
566
+ })
567
+ }, keyName);
568
+ };
569
+
570
+ // Toggle expanded state for a setting card
571
+ const toggleSettingExpanded = settingKey => {
572
+ setExpandedSettings(prev => {
573
+ const newSet = new Set(prev);
574
+ if (newSet.has(settingKey)) {
575
+ newSet.delete(settingKey);
576
+ } else {
577
+ newSet.add(settingKey);
578
+ }
579
+ return newSet;
580
+ });
581
+ };
582
+
583
+ // Render a global setting toggle card with expandable description
584
+ const renderGlobalSettingCard = (settingKey, label, category, shortDescription, fullDescription, recommendation) => {
585
+ const value = settings.globalSettings?.[settingKey] ?? false;
586
+ const isExpanded = expandedSettings.has(settingKey);
587
+ const color = buoyColors.primary;
588
+ return /*#__PURE__*/_jsx(View, {
589
+ style: {
590
+ marginBottom: 10
591
+ },
592
+ children: /*#__PURE__*/_jsxs(TouchableOpacity, {
593
+ activeOpacity: 0.85,
594
+ onPress: () => toggleSettingExpanded(settingKey),
595
+ style: [styles.expandableCard, isExpanded && {
596
+ borderColor: color,
597
+ borderWidth: 2,
598
+ shadowColor: color,
599
+ shadowOpacity: 0.8,
600
+ shadowRadius: 20,
601
+ shadowOffset: {
602
+ width: 0,
603
+ height: 0
604
+ },
605
+ elevation: 10,
606
+ transform: [{
607
+ scale: 1.01
608
+ }]
609
+ }],
610
+ children: [/*#__PURE__*/_jsxs(View, {
611
+ style: styles.expandableCardHeader,
612
+ children: [/*#__PURE__*/_jsx(View, {
613
+ style: styles.expandableCardCategory,
614
+ children: /*#__PURE__*/_jsx(Text, {
615
+ style: styles.expandableCardCategoryText,
616
+ children: category
617
+ })
618
+ }), /*#__PURE__*/_jsxs(View, {
619
+ style: styles.expandableCardTitle,
620
+ children: [/*#__PURE__*/_jsx(Text, {
621
+ style: styles.expandableCardTitleText,
622
+ children: label
623
+ }), !isExpanded && /*#__PURE__*/_jsx(Text, {
624
+ style: styles.expandableCardSubtitle,
625
+ numberOfLines: 1,
626
+ children: shortDescription
627
+ })]
628
+ }), /*#__PURE__*/_jsxs(View, {
629
+ style: styles.expandableCardActions,
630
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
631
+ onPress: () => toggleGlobalSetting(settingKey),
632
+ activeOpacity: 0.8,
633
+ style: [styles.pillToggle, {
634
+ backgroundColor: value ? `${buoyColors.success}33` : buoyColors.hover,
635
+ borderColor: value ? `${buoyColors.success}88` : buoyColors.border,
636
+ shadowColor: value ? buoyColors.success : "transparent",
637
+ shadowOffset: {
638
+ width: 0,
639
+ height: 0
640
+ },
641
+ shadowOpacity: value ? 0.4 : 0,
642
+ shadowRadius: value ? 8 : 0
643
+ }],
644
+ children: /*#__PURE__*/_jsx(Text, {
645
+ style: [styles.pillToggleText, {
646
+ color: value ? buoyColors.success : buoyColors.textMuted
647
+ }],
648
+ children: value ? "ON" : "OFF"
649
+ })
650
+ }), isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
651
+ size: 18,
652
+ color: "#7F91B2"
653
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
654
+ size: 18,
655
+ color: "#7F91B2"
656
+ })]
657
+ })]
658
+ }), isExpanded && /*#__PURE__*/_jsxs(View, {
659
+ style: styles.expandableCardBody,
660
+ children: [/*#__PURE__*/_jsxs(View, {
661
+ style: styles.expandableCardSection,
662
+ children: [/*#__PURE__*/_jsx(Text, {
663
+ style: styles.expandableCardSectionTitle,
664
+ children: "DESCRIPTION"
665
+ }), /*#__PURE__*/_jsx(Text, {
666
+ style: styles.expandableCardSectionText,
667
+ children: fullDescription
668
+ })]
669
+ }), /*#__PURE__*/_jsxs(View, {
670
+ style: styles.expandableCardSection,
671
+ children: [/*#__PURE__*/_jsx(Text, {
672
+ style: styles.expandableCardSectionTitle,
673
+ children: "RECOMMENDATION"
674
+ }), /*#__PURE__*/_jsx(Text, {
675
+ style: styles.expandableCardSectionText,
676
+ children: recommendation
677
+ })]
678
+ })]
679
+ })]
680
+ })
681
+ }, settingKey);
682
+ };
683
+ const renderContent = () => /*#__PURE__*/_jsxs(View, {
684
+ style: styles.container,
685
+ children: [/*#__PURE__*/_jsxs(ScrollView, {
686
+ style: styles.scrollContent,
687
+ showsVerticalScrollIndicator: false,
688
+ contentContainerStyle: styles.scrollContainer,
689
+ children: [activeTab === "dial" && /*#__PURE__*/_jsx(View, {
690
+ style: styles.section,
691
+ children: (() => {
692
+ const enabledCount = Object.values(settings.dialTools).filter(v => v).length;
693
+ const isAtLimit = enabledCount >= MAX_DIAL_SLOTS;
694
+ return Object.entries(settings.dialTools).map(([key, value]) => {
695
+ const isDisabled = !value && isAtLimit;
696
+ return renderToolCard(key, value, isDisabled, () => toggleDialTool(key));
697
+ });
698
+ })()
699
+ }), activeTab === "floating" && /*#__PURE__*/_jsx(View, {
700
+ style: styles.section,
701
+ children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolCard(key, value, false, () => toggleFloatingTool(key)))
702
+ }), activeTab === "settings" && /*#__PURE__*/_jsxs(View, {
703
+ style: styles.section,
704
+ children: [/*#__PURE__*/_jsxs(View, {
705
+ style: [styles.storageStatusCard, isStorageExpanded && {
706
+ borderColor: storageBackend === "filesystem" ? buoyColors.success + "60" : storageBackend === "asyncstorage" ? buoyColors.warning + "60" : buoyColors.error + "60"
707
+ }],
708
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
709
+ activeOpacity: 0.8,
710
+ onPress: () => setIsStorageExpanded(!isStorageExpanded),
711
+ children: /*#__PURE__*/_jsxs(View, {
712
+ style: styles.storageStatusHeader,
713
+ children: [/*#__PURE__*/_jsx(View, {
714
+ style: [styles.storageStatusIcon, {
715
+ backgroundColor: storageBackend === "filesystem" ? buoyColors.success + "15" : storageBackend === "asyncstorage" ? buoyColors.warning + "15" : buoyColors.error + "15"
716
+ }],
717
+ children: /*#__PURE__*/_jsx(Database, {
718
+ size: 18,
719
+ color: storageBackend === "filesystem" ? buoyColors.success : storageBackend === "asyncstorage" ? buoyColors.warning : buoyColors.error
720
+ })
721
+ }), /*#__PURE__*/_jsxs(View, {
722
+ style: styles.storageStatusInfo,
723
+ children: [/*#__PURE__*/_jsx(Text, {
724
+ style: styles.storageStatusLabel,
725
+ children: "STORAGE TYPE"
726
+ }), /*#__PURE__*/_jsxs(View, {
727
+ style: styles.storageStatusBadgeContainer,
728
+ children: [/*#__PURE__*/_jsx(View, {
729
+ style: [styles.storageStatusBadge, {
730
+ backgroundColor: storageBackend === "filesystem" ? buoyColors.success + "20" : storageBackend === "asyncstorage" ? buoyColors.warning + "20" : buoyColors.error + "20",
731
+ borderColor: storageBackend === "filesystem" ? buoyColors.success + "60" : storageBackend === "asyncstorage" ? buoyColors.warning + "60" : buoyColors.error + "60"
732
+ }],
733
+ children: /*#__PURE__*/_jsx(Text, {
734
+ style: [styles.storageStatusBadgeText, {
735
+ color: storageBackend === "filesystem" ? buoyColors.success : storageBackend === "asyncstorage" ? buoyColors.warning : buoyColors.error
736
+ }],
737
+ children: storageBackend === "filesystem" ? "FILE SYSTEM" : storageBackend === "asyncstorage" ? "ASYNC STORAGE" : storageBackend === "memory" ? "MEMORY" : "LOADING..."
738
+ })
739
+ }), storageBackend === "filesystem" && /*#__PURE__*/_jsx(CheckCircle2, {
740
+ size: 14,
741
+ color: buoyColors.success
742
+ }), storageBackend === "asyncstorage" && /*#__PURE__*/_jsx(AlertTriangle, {
743
+ size: 14,
744
+ color: buoyColors.warning
745
+ }), storageBackend === "memory" && /*#__PURE__*/_jsx(AlertTriangle, {
746
+ size: 14,
747
+ color: buoyColors.error
748
+ })]
749
+ })]
750
+ }), isStorageExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
751
+ size: 18,
752
+ color: buoyColors.textMuted
753
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
754
+ size: 18,
755
+ color: buoyColors.textMuted
756
+ })]
757
+ })
758
+ }), /*#__PURE__*/_jsx(Text, {
759
+ style: styles.storageStatusDescription,
760
+ children: storageBackend === "filesystem" ? "Settings persist independently and survive AsyncStorage.clear() calls during logout." : storageBackend === "asyncstorage" ? "Settings may be lost if AsyncStorage is cleared during logout." : storageBackend === "memory" ? "Settings are stored in memory only and will be lost on app restart." : "Checking storage backend..."
761
+ }), storageBackend === "asyncstorage" && /*#__PURE__*/_jsxs(View, {
762
+ style: styles.adviceHint,
763
+ children: [/*#__PURE__*/_jsx(Zap, {
764
+ size: 14,
765
+ color: buoyColors.warning
766
+ }), /*#__PURE__*/_jsxs(Text, {
767
+ style: styles.adviceHintText,
768
+ children: [/*#__PURE__*/_jsx(Text, {
769
+ style: styles.adviceHintBold,
770
+ children: "Tip:"
771
+ }), " Install", " ", /*#__PURE__*/_jsx(Text, {
772
+ style: styles.adviceHintCode,
773
+ children: "expo-file-system"
774
+ }), " to persist settings through logout flows."]
775
+ })]
776
+ }), storageBackend === "memory" && /*#__PURE__*/_jsxs(View, {
777
+ style: styles.adviceHintsContainer,
778
+ children: [/*#__PURE__*/_jsxs(View, {
779
+ style: [styles.adviceHint, {
780
+ backgroundColor: buoyColors.error + "10"
781
+ }],
782
+ children: [/*#__PURE__*/_jsx(AlertTriangle, {
783
+ size: 14,
784
+ color: buoyColors.error
785
+ }), /*#__PURE__*/_jsxs(Text, {
786
+ style: [styles.adviceHintText, {
787
+ color: buoyColors.error
788
+ }],
789
+ children: [/*#__PURE__*/_jsx(Text, {
790
+ style: styles.adviceHintBold,
791
+ children: "No persistent storage available!"
792
+ }), " Settings will reset on every app restart."]
793
+ })]
794
+ }), /*#__PURE__*/_jsxs(View, {
795
+ style: styles.adviceHint,
796
+ children: [/*#__PURE__*/_jsx(Zap, {
797
+ size: 14,
798
+ color: buoyColors.primary
799
+ }), /*#__PURE__*/_jsxs(Text, {
800
+ style: styles.adviceHintText,
801
+ children: [/*#__PURE__*/_jsx(Text, {
802
+ style: styles.adviceHintBold,
803
+ children: "Best:"
804
+ }), " Install", " ", /*#__PURE__*/_jsx(Text, {
805
+ style: styles.adviceHintCode,
806
+ children: "expo-file-system"
807
+ }), " for logout-safe persistence."]
808
+ })]
809
+ }), /*#__PURE__*/_jsxs(View, {
810
+ style: styles.adviceHint,
811
+ children: [/*#__PURE__*/_jsx(HardDrive, {
812
+ size: 14,
813
+ color: buoyColors.textMuted
814
+ }), /*#__PURE__*/_jsxs(Text, {
815
+ style: styles.adviceHintText,
816
+ children: [/*#__PURE__*/_jsx(Text, {
817
+ style: styles.adviceHintBold,
818
+ children: "Alternative:"
819
+ }), " Install", " ", /*#__PURE__*/_jsx(Text, {
820
+ style: styles.adviceHintCode,
821
+ children: "@react-native-async-storage/async-storage"
822
+ }), " for basic persistence."]
823
+ })]
824
+ })]
825
+ }), isStorageExpanded && /*#__PURE__*/_jsxs(View, {
826
+ style: styles.storageExpandedContent,
827
+ children: [/*#__PURE__*/_jsxs(View, {
828
+ style: styles.storageExpandedHeader,
829
+ children: [/*#__PURE__*/_jsx(FileText, {
830
+ size: 14,
831
+ color: buoyColors.primary
832
+ }), /*#__PURE__*/_jsx(Text, {
833
+ style: styles.storageExpandedTitle,
834
+ children: "SAVED SETTINGS"
835
+ }), /*#__PURE__*/_jsx(Text, {
836
+ style: styles.storageExpandedCount,
837
+ children: savedKeysLoading ? "..." : `${savedKeys.length} keys`
838
+ })]
839
+ }), savedKeysLoading ? /*#__PURE__*/_jsx(Text, {
840
+ style: styles.storageKeyItem,
841
+ children: "Loading..."
842
+ }) : savedKeys.length === 0 ? /*#__PURE__*/_jsx(Text, {
843
+ style: styles.storageKeyItemEmpty,
844
+ children: "No settings saved yet"
845
+ }) : /*#__PURE__*/_jsx(ScrollView, {
846
+ style: styles.storageKeysList,
847
+ nestedScrollEnabled: true,
848
+ showsVerticalScrollIndicator: true,
849
+ children: savedKeys.map(key => /*#__PURE__*/_jsx(View, {
850
+ style: styles.storageKeyItem,
851
+ children: /*#__PURE__*/_jsx(Text, {
852
+ style: styles.storageKeyText,
853
+ numberOfLines: 1,
854
+ children: key.replace("@react_buoy_", "")
855
+ })
856
+ }, key))
857
+ })]
858
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
859
+ style: [styles.clearStorageButton, isClearing && styles.clearStorageButtonDisabled, clearSuccess && styles.clearStorageButtonSuccess],
860
+ onPress: handleClearStorage,
861
+ disabled: isClearing,
862
+ activeOpacity: 0.7,
863
+ children: [clearSuccess ? /*#__PURE__*/_jsx(CheckCircle2, {
864
+ size: 14,
865
+ color: buoyColors.success
866
+ }) : /*#__PURE__*/_jsx(Trash2, {
867
+ size: 14,
868
+ color: isClearing ? buoyColors.textMuted : buoyColors.error
869
+ }), /*#__PURE__*/_jsx(Text, {
870
+ style: [styles.clearStorageButtonText, clearSuccess && {
871
+ color: buoyColors.success
872
+ }, isClearing && {
873
+ color: buoyColors.textMuted
874
+ }],
875
+ children: clearSuccess ? "CLEARED" : isClearing ? "CLEARING..." : "CLEAR ALL SETTINGS"
876
+ })]
877
+ })]
878
+ }), renderGlobalSettingCard("enableSharedModalDimensions", "SHARED MODAL SIZE", "MODAL", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately. Enable only if you prefer uniform modal sizes across all dev tools."), isDevelopmentMode && /*#__PURE__*/_jsxs(View, {
879
+ style: styles.exportConfigCard,
880
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
881
+ activeOpacity: 0.85,
882
+ onPress: () => setIsExportExpanded(!isExportExpanded),
883
+ style: styles.exportConfigHeader,
884
+ children: [/*#__PURE__*/_jsx(View, {
885
+ style: styles.exportConfigIconContainer,
886
+ children: /*#__PURE__*/_jsx(FileCode, {
887
+ size: 18,
888
+ color: buoyColors.success
889
+ })
890
+ }), /*#__PURE__*/_jsxs(View, {
891
+ style: styles.exportConfigInfo,
892
+ children: [/*#__PURE__*/_jsx(Text, {
893
+ style: styles.exportConfigLabel,
894
+ children: "EXPORT CONFIG"
895
+ }), /*#__PURE__*/_jsx(Text, {
896
+ style: styles.exportConfigHint,
897
+ children: "Save your settings to code"
898
+ })]
899
+ }), /*#__PURE__*/_jsx(View, {
900
+ style: styles.exportConfigActions,
901
+ children: isExportExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
902
+ size: 18,
903
+ color: buoyColors.textMuted
904
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
905
+ size: 18,
906
+ color: buoyColors.textMuted
907
+ })
908
+ })]
909
+ }), /*#__PURE__*/_jsxs(View, {
910
+ style: styles.exportHintBanner,
911
+ children: [/*#__PURE__*/_jsx(Zap, {
912
+ size: 14,
913
+ color: buoyColors.warning
914
+ }), /*#__PURE__*/_jsxs(Text, {
915
+ style: styles.exportHintText,
916
+ children: [/*#__PURE__*/_jsx(Text, {
917
+ style: styles.exportHintBold,
918
+ children: "New!"
919
+ }), " Configure your tools above, then export to set team defaults."]
920
+ })]
921
+ }), isExportExpanded && /*#__PURE__*/_jsxs(View, {
922
+ style: styles.exportCodeContainer,
923
+ children: [/*#__PURE__*/_jsx(View, {
924
+ style: styles.exportCodeHeader,
925
+ children: /*#__PURE__*/_jsx(Text, {
926
+ style: styles.exportCodeTitle,
927
+ children: "PROPS TO ADD"
928
+ })
929
+ }), enabledConfig.floating.length > 0 && /*#__PURE__*/_jsxs(View, {
930
+ style: styles.exportJsonBlock,
931
+ children: [/*#__PURE__*/_jsx(Text, {
932
+ style: styles.exportJsonProp,
933
+ children: "defaultFloatingTools"
934
+ }), /*#__PURE__*/_jsx(Text, {
935
+ style: styles.exportJsonEquals,
936
+ children: "="
937
+ }), /*#__PURE__*/_jsx(Text, {
938
+ style: styles.exportJsonBracket,
939
+ children: "{"
940
+ }), /*#__PURE__*/_jsx(Text, {
941
+ style: styles.exportJsonArrayBracket,
942
+ children: "["
943
+ }), /*#__PURE__*/_jsx(View, {
944
+ style: styles.exportJsonArrayContent,
945
+ children: enabledConfig.floating.map((id, index) => /*#__PURE__*/_jsxs(View, {
946
+ style: styles.exportJsonArrayItem,
947
+ children: [/*#__PURE__*/_jsxs(Text, {
948
+ style: styles.exportJsonString,
949
+ children: ["'", id, "'"]
950
+ }), index < enabledConfig.floating.length - 1 && /*#__PURE__*/_jsx(Text, {
951
+ style: styles.exportJsonComma,
952
+ children: ","
953
+ })]
954
+ }, id))
955
+ }), /*#__PURE__*/_jsx(Text, {
956
+ style: styles.exportJsonArrayBracket,
957
+ children: "]"
958
+ }), /*#__PURE__*/_jsx(Text, {
959
+ style: styles.exportJsonBracket,
960
+ children: "}"
961
+ })]
962
+ }), enabledConfig.dial.length > 0 && /*#__PURE__*/_jsxs(View, {
963
+ style: [styles.exportJsonBlock, enabledConfig.floating.length > 0 && {
964
+ marginTop: 8
965
+ }],
966
+ children: [/*#__PURE__*/_jsx(Text, {
967
+ style: styles.exportJsonProp,
968
+ children: "defaultDialTools"
969
+ }), /*#__PURE__*/_jsx(Text, {
970
+ style: styles.exportJsonEquals,
971
+ children: "="
972
+ }), /*#__PURE__*/_jsx(Text, {
973
+ style: styles.exportJsonBracket,
974
+ children: "{"
975
+ }), /*#__PURE__*/_jsx(Text, {
976
+ style: styles.exportJsonArrayBracket,
977
+ children: "["
978
+ }), /*#__PURE__*/_jsx(View, {
979
+ style: styles.exportJsonArrayContent,
980
+ children: enabledConfig.dial.map((id, index) => /*#__PURE__*/_jsxs(View, {
981
+ style: styles.exportJsonArrayItem,
982
+ children: [/*#__PURE__*/_jsxs(Text, {
983
+ style: styles.exportJsonString,
984
+ children: ["'", id, "'"]
985
+ }), index < enabledConfig.dial.length - 1 && /*#__PURE__*/_jsx(Text, {
986
+ style: styles.exportJsonComma,
987
+ children: ","
988
+ })]
989
+ }, id))
990
+ }), /*#__PURE__*/_jsx(Text, {
991
+ style: styles.exportJsonArrayBracket,
992
+ children: "]"
993
+ }), /*#__PURE__*/_jsx(Text, {
994
+ style: styles.exportJsonBracket,
995
+ children: "}"
996
+ })]
997
+ }), enabledConfig.floating.length === 0 && enabledConfig.dial.length === 0 && /*#__PURE__*/_jsx(View, {
998
+ style: styles.exportJsonBlock,
999
+ children: /*#__PURE__*/_jsx(Text, {
1000
+ style: styles.exportJsonComment,
1001
+ children: "// No tools enabled"
1002
+ })
1003
+ }), /*#__PURE__*/_jsxs(Text, {
1004
+ style: styles.exportCodeDescription,
1005
+ children: ["Add these props to your existing", " ", /*#__PURE__*/_jsx(Text, {
1006
+ style: styles.exportCodeInline,
1007
+ children: "<FloatingDevTools />"
1008
+ }), " ", "component to set team defaults."]
1009
+ })]
1010
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
1011
+ style: [styles.exportCopyButton, copySuccess && styles.exportCopyButtonSuccess],
1012
+ onPress: handleCopyConfig,
1013
+ activeOpacity: 0.7,
1014
+ children: [copySuccess ? /*#__PURE__*/_jsx(CheckCircle2, {
1015
+ size: 14,
1016
+ color: buoyColors.success
1017
+ }) : /*#__PURE__*/_jsx(Copy, {
1018
+ size: 14,
1019
+ color: buoyColors.success
1020
+ }), /*#__PURE__*/_jsx(Text, {
1021
+ style: [styles.exportCopyButtonText, copySuccess && {
1022
+ color: buoyColors.success
1023
+ }],
1024
+ children: copySuccess ? "COPIED!" : "COPY CONFIG TO CLIPBOARD"
1025
+ })]
1026
+ })]
1027
+ })]
1028
+ }), activeTab === "pro" && /*#__PURE__*/_jsx(View, {
1029
+ style: styles.proContainer,
1030
+ children: isPro ? /*#__PURE__*/_jsxs(_Fragment, {
1031
+ children: [/*#__PURE__*/_jsxs(View, {
1032
+ style: styles.proSection,
1033
+ children: [/*#__PURE__*/_jsxs(SectionHeader, {
1034
+ children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1035
+ icon: Zap,
1036
+ color: buoyColors.primary,
1037
+ size: 12
1038
+ }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1039
+ children: "LICENSE STATUS"
1040
+ }), /*#__PURE__*/_jsx(SectionHeader.Badge, {
1041
+ count: "Active",
1042
+ color: buoyColors.success
1043
+ })]
1044
+ }), /*#__PURE__*/_jsx(View, {
1045
+ style: styles.proSectionContent,
1046
+ children: /*#__PURE__*/_jsxs(View, {
1047
+ style: styles.proStatsRow,
1048
+ children: [/*#__PURE__*/_jsxs(View, {
1049
+ style: styles.proStatItem,
1050
+ children: [/*#__PURE__*/_jsx(Text, {
1051
+ style: styles.proStatValue,
1052
+ children: seats?.used ?? 0
1053
+ }), /*#__PURE__*/_jsx(Text, {
1054
+ style: styles.proStatLabel,
1055
+ children: "USED"
1056
+ })]
1057
+ }), /*#__PURE__*/_jsx(View, {
1058
+ style: styles.proStatDivider
1059
+ }), /*#__PURE__*/_jsxs(View, {
1060
+ style: styles.proStatItem,
1061
+ children: [/*#__PURE__*/_jsx(Text, {
1062
+ style: styles.proStatValue,
1063
+ children: seats?.total ?? "∞"
1064
+ }), /*#__PURE__*/_jsx(Text, {
1065
+ style: styles.proStatLabel,
1066
+ children: "LIMIT"
1067
+ })]
1068
+ }), /*#__PURE__*/_jsx(View, {
1069
+ style: styles.proStatDivider
1070
+ }), /*#__PURE__*/_jsxs(View, {
1071
+ style: styles.proStatItem,
1072
+ children: [/*#__PURE__*/_jsx(Text, {
1073
+ style: [styles.proStatValue, (seats?.remaining ?? 0) <= 0 && styles.proStatValueDanger],
1074
+ children: seats?.remaining ?? "∞"
1075
+ }), /*#__PURE__*/_jsx(Text, {
1076
+ style: styles.proStatLabel,
1077
+ children: "AVAILABLE"
1078
+ })]
1079
+ })]
1080
+ })
1081
+ })]
1082
+ }), /*#__PURE__*/_jsxs(View, {
1083
+ style: styles.proSection,
1084
+ children: [/*#__PURE__*/_jsxs(SectionHeader, {
1085
+ children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1086
+ icon: Smartphone,
1087
+ color: buoyColors.info,
1088
+ size: 12
1089
+ }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1090
+ children: "REGISTERED DEVICES"
1091
+ }), devices.length > 0 && /*#__PURE__*/_jsx(SectionHeader.Badge, {
1092
+ count: devices.length,
1093
+ color: buoyColors.info
1094
+ }), refreshDevices && /*#__PURE__*/_jsx(SectionHeader.Actions, {
1095
+ children: /*#__PURE__*/_jsx(TouchableOpacity, {
1096
+ onPress: () => refreshDevices(),
1097
+ disabled: devicesLoading,
1098
+ style: {
1099
+ marginLeft: 8
1100
+ },
1101
+ children: /*#__PURE__*/_jsx(RefreshCw, {
1102
+ size: 14,
1103
+ color: devicesLoading ? buoyColors.textMuted : buoyColors.textSecondary
1104
+ })
1105
+ })
1106
+ })]
1107
+ }), /*#__PURE__*/_jsxs(View, {
1108
+ style: styles.proSectionContent,
1109
+ children: [devicesLoading && devices.length === 0 ? /*#__PURE__*/_jsx(View, {
1110
+ style: styles.proEmptyState,
1111
+ children: /*#__PURE__*/_jsx(Text, {
1112
+ style: styles.proEmptyStateText,
1113
+ children: "Loading devices..."
1114
+ })
1115
+ }) : devicesError ? /*#__PURE__*/_jsxs(View, {
1116
+ style: styles.proErrorState,
1117
+ children: [/*#__PURE__*/_jsx(AlertTriangle, {
1118
+ size: 16,
1119
+ color: buoyColors.error
1120
+ }), /*#__PURE__*/_jsx(Text, {
1121
+ style: styles.proErrorStateText,
1122
+ children: devicesError
1123
+ })]
1124
+ }) : devices.length === 0 ? /*#__PURE__*/_jsxs(View, {
1125
+ style: styles.proEmptyState,
1126
+ children: [/*#__PURE__*/_jsx(Smartphone, {
1127
+ size: 24,
1128
+ color: buoyColors.textMuted
1129
+ }), /*#__PURE__*/_jsx(Text, {
1130
+ style: styles.proEmptyStateText,
1131
+ children: "No devices registered"
1132
+ })]
1133
+ }) : /*#__PURE__*/_jsx(View, {
1134
+ style: styles.proDevicesList,
1135
+ children: devices.map(device => /*#__PURE__*/_jsxs(View, {
1136
+ style: [styles.proDeviceRow, device.isCurrentDevice && styles.proDeviceRowCurrent],
1137
+ children: [/*#__PURE__*/_jsx(View, {
1138
+ style: styles.proDeviceIcon,
1139
+ children: /*#__PURE__*/_jsx(Smartphone, {
1140
+ size: 16,
1141
+ color: buoyColors.text
1142
+ })
1143
+ }), /*#__PURE__*/_jsxs(View, {
1144
+ style: styles.proDeviceInfo,
1145
+ children: [/*#__PURE__*/_jsxs(View, {
1146
+ style: styles.proDeviceNameRow,
1147
+ children: [/*#__PURE__*/_jsx(Text, {
1148
+ style: styles.proDeviceName,
1149
+ numberOfLines: 1,
1150
+ children: device.name
1151
+ }), device.isCurrentDevice && /*#__PURE__*/_jsx(View, {
1152
+ style: styles.proDeviceBadge,
1153
+ children: /*#__PURE__*/_jsx(Text, {
1154
+ style: styles.proDeviceBadgeText,
1155
+ children: "THIS DEVICE"
1156
+ })
1157
+ })]
1158
+ }), /*#__PURE__*/_jsxs(Text, {
1159
+ style: styles.proDeviceDetails,
1160
+ children: [device.platform, device.osVersion ? ` ${device.osVersion}` : "", device.model ? ` • ${device.model}` : ""]
1161
+ }), /*#__PURE__*/_jsxs(Text, {
1162
+ style: styles.proDeviceDate,
1163
+ children: ["Registered ", formatDeviceDate(device.registeredAt)]
1164
+ })]
1165
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1166
+ style: styles.proDeviceRemoveBtn,
1167
+ onPress: () => handleRemoveDevice(device.id, device.name, device.isCurrentDevice),
1168
+ disabled: deactivatingDeviceId === device.id,
1169
+ children: deactivatingDeviceId === device.id ? /*#__PURE__*/_jsx(RefreshCw, {
1170
+ size: 14,
1171
+ color: buoyColors.textMuted
1172
+ }) : /*#__PURE__*/_jsx(Trash2, {
1173
+ size: 14,
1174
+ color: buoyColors.error
1175
+ })
1176
+ })]
1177
+ }, device.id))
1178
+ }), !isCurrentDeviceRegistered && /*#__PURE__*/_jsxs(TouchableOpacity, {
1179
+ style: styles.proRegisterDeviceBtn,
1180
+ onPress: handleRegisterDevice,
1181
+ disabled: devicesLoading,
1182
+ activeOpacity: 0.7,
1183
+ children: [/*#__PURE__*/_jsx(Plus, {
1184
+ size: 14,
1185
+ color: buoyColors.primary
1186
+ }), /*#__PURE__*/_jsx(Text, {
1187
+ style: styles.proRegisterDeviceBtnText,
1188
+ children: "Register This Device"
1189
+ })]
1190
+ })]
1191
+ })]
1192
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1193
+ style: styles.proSignOutBtn,
1194
+ onPress: () => {
1195
+ const {
1196
+ Alert
1197
+ } = require("react-native");
1198
+ Alert.alert("Sign Out of Pro?", "This will:\n\n• Remove your license key from this device\n• Deactivate this device from your license\n• Free up a seat for another device\n\nYou can sign back in anytime with your license key.", [{
1199
+ text: "Cancel",
1200
+ style: "cancel"
1201
+ }, {
1202
+ text: "Sign Out",
1203
+ style: "destructive",
1204
+ onPress: () => license?.clearLicense()
1205
+ }]);
1206
+ },
1207
+ activeOpacity: 0.7,
1208
+ children: /*#__PURE__*/_jsx(Text, {
1209
+ style: styles.proSignOutBtnText,
1210
+ children: "Sign Out of Pro"
1211
+ })
1212
+ })]
1213
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
1214
+ children: [/*#__PURE__*/_jsxs(View, {
1215
+ style: styles.proSection,
1216
+ children: [/*#__PURE__*/_jsxs(SectionHeader, {
1217
+ children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1218
+ icon: Zap,
1219
+ color: buoyColors.textMuted,
1220
+ size: 12
1221
+ }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1222
+ children: "LICENSE STATUS"
1223
+ }), /*#__PURE__*/_jsx(SectionHeader.Badge, {
1224
+ count: "Free",
1225
+ color: buoyColors.textMuted
1226
+ })]
1227
+ }), /*#__PURE__*/_jsx(View, {
1228
+ style: styles.proSectionContent,
1229
+ children: /*#__PURE__*/_jsx(Text, {
1230
+ style: styles.proFreeDescription,
1231
+ children: "Upgrade to Pro to unlock all features and support development."
1232
+ })
1233
+ })]
1234
+ }), /*#__PURE__*/_jsxs(View, {
1235
+ style: styles.proSection,
1236
+ children: [/*#__PURE__*/_jsxs(SectionHeader, {
1237
+ children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1238
+ icon: CheckCircle2,
1239
+ color: buoyColors.primary,
1240
+ size: 12
1241
+ }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1242
+ children: "PRO FEATURES"
1243
+ })]
1244
+ }), /*#__PURE__*/_jsx(View, {
1245
+ style: styles.proSectionContent,
1246
+ children: /*#__PURE__*/_jsxs(View, {
1247
+ style: styles.proFeaturesList,
1248
+ children: [/*#__PURE__*/_jsxs(View, {
1249
+ style: styles.proFeatureItem,
1250
+ children: [/*#__PURE__*/_jsx(CheckCircle2, {
1251
+ size: 14,
1252
+ color: buoyColors.primary
1253
+ }), /*#__PURE__*/_jsx(Text, {
1254
+ style: styles.proFeatureText,
1255
+ children: "Advanced Settings"
1256
+ })]
1257
+ }), /*#__PURE__*/_jsxs(View, {
1258
+ style: styles.proFeatureItem,
1259
+ children: [/*#__PURE__*/_jsx(CheckCircle2, {
1260
+ size: 14,
1261
+ color: buoyColors.primary
1262
+ }), /*#__PURE__*/_jsx(Text, {
1263
+ style: styles.proFeatureText,
1264
+ children: "Export Configuration"
1265
+ })]
1266
+ }), /*#__PURE__*/_jsxs(View, {
1267
+ style: styles.proFeatureItem,
1268
+ children: [/*#__PURE__*/_jsx(CheckCircle2, {
1269
+ size: 14,
1270
+ color: buoyColors.primary
1271
+ }), /*#__PURE__*/_jsx(Text, {
1272
+ style: styles.proFeatureText,
1273
+ children: "Team Defaults"
1274
+ })]
1275
+ }), /*#__PURE__*/_jsxs(View, {
1276
+ style: styles.proFeatureItem,
1277
+ children: [/*#__PURE__*/_jsx(CheckCircle2, {
1278
+ size: 14,
1279
+ color: buoyColors.primary
1280
+ }), /*#__PURE__*/_jsx(Text, {
1281
+ style: styles.proFeatureText,
1282
+ children: "Priority Support"
1283
+ })]
1284
+ })]
1285
+ })
1286
+ })]
1287
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
1288
+ style: styles.proUpgradeBtn,
1289
+ onPress: () => setShowLicenseModal(true),
1290
+ activeOpacity: 0.8,
1291
+ children: [/*#__PURE__*/_jsx(Zap, {
1292
+ size: 16,
1293
+ color: buoyColors.base
1294
+ }), /*#__PURE__*/_jsx(Text, {
1295
+ style: styles.proUpgradeBtnText,
1296
+ children: "Upgrade to Pro"
1297
+ })]
1298
+ })]
1299
+ })
1300
+ })]
1301
+ }), /*#__PURE__*/_jsx(LicenseEntryModal, {
1302
+ visible: showLicenseModal,
1303
+ onClose: () => {
1304
+ setShowLicenseModal(false);
1305
+ setLicenseModalForDeviceRegistration(false);
1306
+ },
1307
+ onSuccess: () => {
1308
+ setShowLicenseModal(false);
1309
+ setLicenseModalForDeviceRegistration(false);
1310
+ },
1311
+ license: license,
1312
+ startAtDeviceRegistration: licenseModalForDeviceRegistration,
1313
+ initialExistingDevices: licenseModalForDeviceRegistration ? devices : [],
1314
+ initialMaxDevices: licenseModalForDeviceRegistration ? seats?.total ?? undefined : undefined,
1315
+ initialCurrentDeviceCount: licenseModalForDeviceRegistration ? seats?.used ?? undefined : undefined
1316
+ })]
1317
+ });
1318
+ return /*#__PURE__*/_jsx(JsModal, {
1319
+ visible: visible,
1320
+ onClose: onClose,
1321
+ header: {
1322
+ showToggleButton: false,
1323
+ customContent: /*#__PURE__*/_jsxs(ModalHeader, {
1324
+ children: [/*#__PURE__*/_jsx(ModalHeader.Content, {
1325
+ title: "",
1326
+ noMargin: true,
1327
+ children: /*#__PURE__*/_jsx(TabSelector, {
1328
+ tabs: [{
1329
+ key: "dial",
1330
+ label: "DIAL"
1331
+ }, {
1332
+ key: "floating",
1333
+ label: "FLOATING"
1334
+ }, {
1335
+ key: "settings",
1336
+ label: "SETTINGS"
1337
+ }, {
1338
+ key: "pro",
1339
+ label: "PRO"
1340
+ }],
1341
+ activeTab: activeTab,
1342
+ onTabChange: tab => setActiveTab(tab)
1343
+ })
1344
+ }), /*#__PURE__*/_jsx(ModalHeader.Actions, {})]
1345
+ })
1346
+ },
1347
+ initialMode: "bottomSheet",
1348
+ onModeChange: handleModeChange,
1349
+ persistenceKey: devToolsStorageKeys.settings.root(),
1350
+ enablePersistence: true,
1351
+ maxHeight: screenHeight - insets.top,
1352
+ initialHeight: modalHeight,
1353
+ initialFloatingPosition: {
1354
+ x: (screenWidth - modalWidth) / 2,
1355
+ y: insets.top + 20
1356
+ },
1357
+ enableGlitchEffects: true,
1358
+ children: renderContent()
1359
+ });
1360
+ };
1361
+
1362
+ // Basic default settings for the hook (when apps are not available and no defaults configured)
1363
+ const basicDefaultSettings = {
1364
+ dialTools: {},
1365
+ floatingTools: {
1366
+ environment: false
1367
+ },
1368
+ globalSettings: {
1369
+ enableSharedModalDimensions: false
1370
+ }
1371
+ };
1372
+
1373
+ /**
1374
+ * Creates default settings from the team configuration arrays.
1375
+ * Used by the hook when no stored settings exist.
1376
+ */
1377
+ const createDefaultsFromConfig = (defaultFloatingTools, defaultDialTools) => {
1378
+ const floatingSet = new Set(defaultFloatingTools ?? []);
1379
+ const dialSet = new Set(defaultDialTools ?? []);
1380
+
1381
+ // Build dial tools record from defaults
1382
+ const dialTools = {};
1383
+ for (const id of dialSet) {
1384
+ dialTools[id] = true;
1385
+ }
1386
+
1387
+ // Build floating tools record from defaults
1388
+ const floatingTools = {
1389
+ environment: floatingSet.has('environment')
1390
+ };
1391
+ for (const id of floatingSet) {
1392
+ if (id !== 'environment') {
1393
+ floatingTools[id] = true;
1394
+ }
1395
+ }
1396
+ return {
1397
+ dialTools: enforceDialLimit(dialTools),
1398
+ floatingTools,
1399
+ globalSettings: {
1400
+ enableSharedModalDimensions: false
1401
+ }
1402
+ };
1403
+ };
1404
+
1405
+ /**
1406
+ * Convenience hook for accessing persisted dev tools settings. Subscribes to the internal
1407
+ * event bus so all surfaces stay in sync when the modal saves new preferences.
1408
+ *
1409
+ * When no saved settings exist, the hook uses the default configuration from
1410
+ * the DefaultConfigProvider (if available) to determine initial tool states.
1411
+ */
1412
+ export const useDevToolsSettings = () => {
1413
+ // Get team default configuration from context
1414
+ const {
1415
+ defaultFloatingTools,
1416
+ defaultDialTools
1417
+ } = useDefaultConfig();
1418
+
1419
+ // Compute the effective defaults based on team configuration
1420
+ const effectiveDefaults = useMemo(() => {
1421
+ if (!defaultFloatingTools && !defaultDialTools) {
1422
+ return basicDefaultSettings;
1423
+ }
1424
+ return createDefaultsFromConfig(defaultFloatingTools, defaultDialTools);
1425
+ }, [defaultFloatingTools, defaultDialTools]);
1426
+ const [settings, setSettings] = useState(effectiveDefaults);
1427
+ const loadSettings = useCallback(async () => {
1428
+ try {
1429
+ const savedSettings = await safeGetItem(STORAGE_KEY);
1430
+ if (savedSettings) {
1431
+ const parsed = JSON.parse(savedSettings);
1432
+ const merged = mergeWithDefaults(effectiveDefaults, parsed);
1433
+ setSettings(merged);
1434
+ return;
1435
+ }
1436
+ // No saved settings - use the effective defaults (including team config)
1437
+ setSettings(effectiveDefaults);
1438
+ } catch (error) {
1439
+ console.error("Failed to load dev tools settings:", error);
1440
+ setSettings(effectiveDefaults);
1441
+ }
1442
+ }, [effectiveDefaults]);
1443
+ useEffect(() => {
1444
+ loadSettings();
1445
+ // Subscribe to settings changes
1446
+ const unsub = settingsBus.addListener(payload => {
1447
+ try {
1448
+ if (payload) {
1449
+ setSettings(payload);
1450
+ }
1451
+ } catch (err) {
1452
+ // Listener errors are intentionally swallowed to avoid breaking subscribers
1453
+ }
1454
+ });
1455
+ return () => {
1456
+ unsub();
1457
+ };
1458
+ }, [loadSettings]);
1459
+
1460
+ // Refresh settings when component using this hook becomes visible
1461
+ const refreshSettings = useCallback(() => {
1462
+ loadSettings();
1463
+ }, [loadSettings]);
1464
+ return {
1465
+ settings,
1466
+ refreshSettings
1467
+ };
1468
+ };
1469
+ const styles = StyleSheet.create({
1470
+ container: {
1471
+ flex: 1,
1472
+ backgroundColor: buoyColors.base
1473
+ },
1474
+ // Header styles matching React Query modal exactly
1475
+ headerContainer: {
1476
+ flex: 1,
1477
+ flexDirection: "row",
1478
+ alignItems: "center",
1479
+ justifyContent: "space-between",
1480
+ paddingHorizontal: 12
1481
+ },
1482
+ tabNavigationContainer: {
1483
+ flex: 1,
1484
+ flexDirection: "row",
1485
+ backgroundColor: buoyColors.hover,
1486
+ borderRadius: 6,
1487
+ padding: 2,
1488
+ borderWidth: 1,
1489
+ borderColor: buoyColors.border,
1490
+ justifyContent: "space-evenly"
1491
+ },
1492
+ tabButton: {
1493
+ paddingHorizontal: 8,
1494
+ paddingVertical: 5,
1495
+ borderRadius: 4,
1496
+ alignItems: "center",
1497
+ justifyContent: "center",
1498
+ flex: 1,
1499
+ marginHorizontal: 1
1500
+ },
1501
+ tabButtonActive: {
1502
+ backgroundColor: buoyColors.primary + "20",
1503
+ borderWidth: 1,
1504
+ borderColor: buoyColors.primary
1505
+ },
1506
+ tabButtonInactive: {
1507
+ backgroundColor: "transparent"
1508
+ },
1509
+ tabButtonText: {
1510
+ fontSize: 12,
1511
+ fontWeight: "600",
1512
+ letterSpacing: 0.5,
1513
+ fontFamily: "monospace",
1514
+ textTransform: "uppercase"
1515
+ },
1516
+ tabButtonTextActive: {
1517
+ color: buoyColors.primary
1518
+ },
1519
+ tabButtonTextInactive: {
1520
+ color: buoyColors.textMuted
1521
+ },
1522
+ // Scroll content
1523
+ scrollContent: {
1524
+ flex: 1
1525
+ },
1526
+ scrollContainer: {
1527
+ paddingTop: 16,
1528
+ paddingBottom: 24
1529
+ },
1530
+ // Sections
1531
+ section: {
1532
+ marginHorizontal: 16,
1533
+ marginBottom: 24
1534
+ },
1535
+ sectionHeader: {
1536
+ flexDirection: "row",
1537
+ alignItems: "center",
1538
+ marginBottom: 12,
1539
+ paddingHorizontal: 4
1540
+ },
1541
+ sectionIndicator: {
1542
+ width: 3,
1543
+ height: 16,
1544
+ borderRadius: 2,
1545
+ backgroundColor: buoyColors.primary,
1546
+ marginRight: 8,
1547
+ shadowColor: buoyColors.primary,
1548
+ shadowOffset: {
1549
+ width: 0,
1550
+ height: 0
1551
+ },
1552
+ shadowOpacity: 0.6,
1553
+ shadowRadius: 4
1554
+ },
1555
+ sectionTitle: {
1556
+ flex: 1,
1557
+ color: buoyColors.primary,
1558
+ fontSize: 13,
1559
+ fontWeight: "700",
1560
+ letterSpacing: 1.2
1561
+ },
1562
+ sectionCount: {
1563
+ color: buoyColors.textSecondary,
1564
+ fontSize: 11,
1565
+ opacity: 0.7
1566
+ },
1567
+ // Tool Cards - Clean unified Buoy theme
1568
+ glassCard: {
1569
+ borderRadius: 12,
1570
+ paddingVertical: 12,
1571
+ paddingHorizontal: 14,
1572
+ backgroundColor: buoyColors.card,
1573
+ borderWidth: 1,
1574
+ borderColor: buoyColors.border
1575
+ },
1576
+ glassCardActive: {
1577
+ borderColor: buoyColors.primary + "50",
1578
+ backgroundColor: buoyColors.primary + "08"
1579
+ },
1580
+ glassCardInner: {
1581
+ flexDirection: "row",
1582
+ alignItems: "center",
1583
+ gap: 12
1584
+ },
1585
+ iconContainer: {
1586
+ width: 32,
1587
+ height: 32,
1588
+ borderRadius: 8,
1589
+ backgroundColor: buoyColors.hover,
1590
+ alignItems: "center",
1591
+ justifyContent: "center"
1592
+ },
1593
+ iconContainerActive: {
1594
+ backgroundColor: buoyColors.primary + "20"
1595
+ },
1596
+ toolInfo: {
1597
+ flex: 1
1598
+ },
1599
+ toolName: {
1600
+ color: buoyColors.text,
1601
+ fontWeight: "600",
1602
+ fontSize: 13
1603
+ },
1604
+ toolNameActive: {
1605
+ color: buoyColors.text
1606
+ },
1607
+ toolDescription: {
1608
+ color: buoyColors.textMuted,
1609
+ fontSize: 11,
1610
+ marginTop: 2
1611
+ },
1612
+ pillToggle: {
1613
+ paddingHorizontal: 14,
1614
+ paddingVertical: 6,
1615
+ borderRadius: 6,
1616
+ borderWidth: 1,
1617
+ minWidth: 48,
1618
+ alignItems: "center"
1619
+ },
1620
+ pillToggleOn: {
1621
+ backgroundColor: buoyColors.primary + "20",
1622
+ borderColor: buoyColors.primary
1623
+ },
1624
+ pillToggleOff: {
1625
+ backgroundColor: buoyColors.hover,
1626
+ borderColor: buoyColors.border
1627
+ },
1628
+ pillToggleText: {
1629
+ fontWeight: "600",
1630
+ fontSize: 11,
1631
+ letterSpacing: 0.5
1632
+ },
1633
+ pillToggleTextOn: {
1634
+ color: buoyColors.primary
1635
+ },
1636
+ pillToggleTextOff: {
1637
+ color: buoyColors.textMuted
1638
+ },
1639
+ closeButton: {
1640
+ marginLeft: 8,
1641
+ paddingHorizontal: 10,
1642
+ paddingVertical: 6,
1643
+ borderRadius: 6,
1644
+ backgroundColor: buoyColors.error + "1A",
1645
+ borderWidth: 1,
1646
+ borderColor: buoyColors.error + "33"
1647
+ },
1648
+ closeButtonText: {
1649
+ color: buoyColors.error,
1650
+ fontSize: 14,
1651
+ fontWeight: "700",
1652
+ letterSpacing: 0.5
1653
+ },
1654
+ // Expandable Settings Card styles
1655
+ expandableCard: {
1656
+ backgroundColor: buoyColors.card,
1657
+ borderRadius: 8,
1658
+ borderWidth: 1,
1659
+ borderColor: buoyColors.border,
1660
+ padding: 12
1661
+ },
1662
+ expandableCardHeader: {
1663
+ flexDirection: "row",
1664
+ alignItems: "center",
1665
+ gap: 12
1666
+ },
1667
+ expandableCardCategory: {
1668
+ backgroundColor: buoyColors.primary + "20",
1669
+ borderWidth: 1,
1670
+ borderColor: buoyColors.primary + "40",
1671
+ borderRadius: 4,
1672
+ paddingHorizontal: 8,
1673
+ paddingVertical: 4
1674
+ },
1675
+ expandableCardCategoryText: {
1676
+ fontSize: 10,
1677
+ fontWeight: "700",
1678
+ color: buoyColors.primary,
1679
+ letterSpacing: 0.5
1680
+ },
1681
+ expandableCardTitle: {
1682
+ flex: 1,
1683
+ paddingHorizontal: 4
1684
+ },
1685
+ expandableCardTitleText: {
1686
+ fontFamily: "monospace",
1687
+ fontSize: 12,
1688
+ fontWeight: "700",
1689
+ color: buoyColors.text
1690
+ },
1691
+ expandableCardSubtitle: {
1692
+ fontSize: 10,
1693
+ color: buoyColors.textMuted,
1694
+ marginTop: 2
1695
+ },
1696
+ expandableCardActions: {
1697
+ flexDirection: "row",
1698
+ alignItems: "center",
1699
+ gap: 8
1700
+ },
1701
+ expandableCardBody: {
1702
+ marginTop: 12,
1703
+ paddingTop: 12,
1704
+ borderTopWidth: 1,
1705
+ borderTopColor: buoyColors.border + "50"
1706
+ },
1707
+ expandableCardSection: {
1708
+ marginBottom: 12
1709
+ },
1710
+ expandableCardSectionTitle: {
1711
+ fontSize: 10,
1712
+ fontWeight: "700",
1713
+ color: buoyColors.primary,
1714
+ letterSpacing: 1,
1715
+ marginBottom: 6
1716
+ },
1717
+ expandableCardSectionText: {
1718
+ fontSize: 12,
1719
+ color: buoyColors.textSecondary,
1720
+ lineHeight: 18
1721
+ },
1722
+ // Storage Status Card styles
1723
+ storageStatusCard: {
1724
+ backgroundColor: buoyColors.card,
1725
+ borderRadius: 8,
1726
+ borderWidth: 1,
1727
+ borderColor: buoyColors.border,
1728
+ padding: 12,
1729
+ marginBottom: 10
1730
+ },
1731
+ storageStatusHeader: {
1732
+ flexDirection: "row",
1733
+ alignItems: "center",
1734
+ gap: 12,
1735
+ marginBottom: 8
1736
+ },
1737
+ storageStatusIcon: {
1738
+ width: 36,
1739
+ height: 36,
1740
+ borderRadius: 8,
1741
+ backgroundColor: buoyColors.hover,
1742
+ alignItems: "center",
1743
+ justifyContent: "center"
1744
+ },
1745
+ storageStatusInfo: {
1746
+ flex: 1
1747
+ },
1748
+ storageStatusLabel: {
1749
+ fontSize: 10,
1750
+ fontWeight: "700",
1751
+ color: buoyColors.textMuted,
1752
+ letterSpacing: 1,
1753
+ marginBottom: 4
1754
+ },
1755
+ storageStatusBadgeContainer: {
1756
+ flexDirection: "row",
1757
+ alignItems: "center",
1758
+ gap: 6
1759
+ },
1760
+ storageStatusBadge: {
1761
+ paddingHorizontal: 8,
1762
+ paddingVertical: 4,
1763
+ borderRadius: 4,
1764
+ borderWidth: 1
1765
+ },
1766
+ storageStatusBadgeText: {
1767
+ fontSize: 11,
1768
+ fontWeight: "700",
1769
+ letterSpacing: 0.5
1770
+ },
1771
+ storageStatusDescription: {
1772
+ fontSize: 11,
1773
+ color: buoyColors.textMuted,
1774
+ lineHeight: 16,
1775
+ marginBottom: 12
1776
+ },
1777
+ clearStorageButton: {
1778
+ flexDirection: "row",
1779
+ alignItems: "center",
1780
+ justifyContent: "center",
1781
+ gap: 8,
1782
+ paddingVertical: 10,
1783
+ paddingHorizontal: 16,
1784
+ borderRadius: 6,
1785
+ backgroundColor: buoyColors.error + "15",
1786
+ borderWidth: 1,
1787
+ borderColor: buoyColors.error + "40"
1788
+ },
1789
+ clearStorageButtonDisabled: {
1790
+ opacity: 0.5
1791
+ },
1792
+ clearStorageButtonSuccess: {
1793
+ backgroundColor: buoyColors.success + "15",
1794
+ borderColor: buoyColors.success + "40"
1795
+ },
1796
+ clearStorageButtonText: {
1797
+ fontSize: 12,
1798
+ fontWeight: "700",
1799
+ color: buoyColors.error,
1800
+ letterSpacing: 0.5
1801
+ },
1802
+ // Advice hints
1803
+ adviceHintsContainer: {
1804
+ gap: 8,
1805
+ marginBottom: 12
1806
+ },
1807
+ adviceHint: {
1808
+ flexDirection: "row",
1809
+ alignItems: "flex-start",
1810
+ gap: 8,
1811
+ backgroundColor: buoyColors.warning + "10",
1812
+ borderRadius: 6,
1813
+ padding: 10,
1814
+ marginBottom: 8
1815
+ },
1816
+ adviceHintText: {
1817
+ flex: 1,
1818
+ fontSize: 11,
1819
+ color: buoyColors.textSecondary,
1820
+ lineHeight: 16
1821
+ },
1822
+ adviceHintBold: {
1823
+ fontWeight: "700",
1824
+ color: buoyColors.text
1825
+ },
1826
+ adviceHintCode: {
1827
+ fontFamily: "monospace",
1828
+ fontSize: 10,
1829
+ color: buoyColors.info,
1830
+ backgroundColor: buoyColors.info + "15",
1831
+ paddingHorizontal: 4,
1832
+ borderRadius: 3
1833
+ },
1834
+ // Expanded storage content
1835
+ storageExpandedContent: {
1836
+ marginTop: 12,
1837
+ paddingTop: 12,
1838
+ borderTopWidth: 1,
1839
+ borderTopColor: buoyColors.border + "50"
1840
+ },
1841
+ storageExpandedHeader: {
1842
+ flexDirection: "row",
1843
+ alignItems: "center",
1844
+ gap: 8,
1845
+ marginBottom: 10
1846
+ },
1847
+ storageExpandedTitle: {
1848
+ fontSize: 10,
1849
+ fontWeight: "700",
1850
+ color: buoyColors.primary,
1851
+ letterSpacing: 1,
1852
+ flex: 1
1853
+ },
1854
+ storageExpandedCount: {
1855
+ fontSize: 10,
1856
+ color: buoyColors.textMuted
1857
+ },
1858
+ storageKeysList: {
1859
+ gap: 4,
1860
+ maxHeight: 150
1861
+ },
1862
+ storageKeyItem: {
1863
+ backgroundColor: buoyColors.hover,
1864
+ borderRadius: 4,
1865
+ paddingHorizontal: 8,
1866
+ paddingVertical: 6,
1867
+ borderWidth: 1,
1868
+ borderColor: buoyColors.border + "50"
1869
+ },
1870
+ storageKeyItemEmpty: {
1871
+ fontSize: 11,
1872
+ color: buoyColors.textMuted,
1873
+ fontStyle: "italic",
1874
+ paddingVertical: 8
1875
+ },
1876
+ storageKeyText: {
1877
+ fontSize: 11,
1878
+ color: buoyColors.textSecondary,
1879
+ fontFamily: "monospace"
1880
+ },
1881
+ // Export Config Card styles
1882
+ exportConfigCard: {
1883
+ backgroundColor: buoyColors.card,
1884
+ borderRadius: 8,
1885
+ borderWidth: 1,
1886
+ borderColor: buoyColors.success + "40",
1887
+ padding: 12,
1888
+ marginTop: 10
1889
+ },
1890
+ exportConfigHeader: {
1891
+ flexDirection: "row",
1892
+ alignItems: "center",
1893
+ gap: 12
1894
+ },
1895
+ exportConfigIconContainer: {
1896
+ width: 36,
1897
+ height: 36,
1898
+ borderRadius: 8,
1899
+ backgroundColor: buoyColors.success + "15",
1900
+ alignItems: "center",
1901
+ justifyContent: "center"
1902
+ },
1903
+ exportConfigInfo: {
1904
+ flex: 1
1905
+ },
1906
+ exportConfigLabel: {
1907
+ fontSize: 12,
1908
+ fontWeight: "700",
1909
+ color: buoyColors.success,
1910
+ letterSpacing: 0.5
1911
+ },
1912
+ exportConfigHint: {
1913
+ fontSize: 10,
1914
+ color: buoyColors.textMuted,
1915
+ marginTop: 2
1916
+ },
1917
+ exportConfigActions: {
1918
+ flexDirection: "row",
1919
+ alignItems: "center"
1920
+ },
1921
+ exportHintBanner: {
1922
+ flexDirection: "row",
1923
+ alignItems: "flex-start",
1924
+ gap: 8,
1925
+ backgroundColor: buoyColors.warning + "10",
1926
+ borderRadius: 6,
1927
+ padding: 10,
1928
+ marginTop: 12
1929
+ },
1930
+ exportHintText: {
1931
+ flex: 1,
1932
+ fontSize: 11,
1933
+ color: buoyColors.textSecondary,
1934
+ lineHeight: 16
1935
+ },
1936
+ exportHintBold: {
1937
+ fontWeight: "700",
1938
+ color: buoyColors.warning
1939
+ },
1940
+ exportCodeContainer: {
1941
+ marginTop: 12,
1942
+ paddingTop: 12,
1943
+ borderTopWidth: 1,
1944
+ borderTopColor: buoyColors.border + "50"
1945
+ },
1946
+ exportCodeHeader: {
1947
+ flexDirection: "row",
1948
+ alignItems: "center",
1949
+ gap: 8,
1950
+ marginBottom: 8
1951
+ },
1952
+ exportCodeTitle: {
1953
+ fontSize: 10,
1954
+ fontWeight: "700",
1955
+ color: buoyColors.success,
1956
+ letterSpacing: 1
1957
+ },
1958
+ exportCodeBlock: {
1959
+ backgroundColor: buoyColors.hover,
1960
+ borderRadius: 6,
1961
+ padding: 12,
1962
+ borderWidth: 1,
1963
+ borderColor: buoyColors.border + "50"
1964
+ },
1965
+ exportCodeText: {
1966
+ fontSize: 11,
1967
+ color: buoyColors.text,
1968
+ fontFamily: "monospace",
1969
+ lineHeight: 18
1970
+ },
1971
+ exportCodeDescription: {
1972
+ fontSize: 10,
1973
+ color: buoyColors.textMuted,
1974
+ marginTop: 8,
1975
+ lineHeight: 14
1976
+ },
1977
+ exportCodeInline: {
1978
+ fontFamily: "monospace",
1979
+ fontSize: 10,
1980
+ color: buoyColors.info,
1981
+ backgroundColor: buoyColors.info + "15",
1982
+ paddingHorizontal: 4,
1983
+ borderRadius: 3
1984
+ },
1985
+ exportCopyButton: {
1986
+ flexDirection: "row",
1987
+ alignItems: "center",
1988
+ justifyContent: "center",
1989
+ gap: 8,
1990
+ paddingVertical: 10,
1991
+ paddingHorizontal: 16,
1992
+ borderRadius: 6,
1993
+ backgroundColor: buoyColors.success + "15",
1994
+ borderWidth: 1,
1995
+ borderColor: buoyColors.success + "40",
1996
+ marginTop: 12
1997
+ },
1998
+ exportCopyButtonSuccess: {
1999
+ backgroundColor: buoyColors.success + "25",
2000
+ borderColor: buoyColors.success + "60"
2001
+ },
2002
+ exportCopyButtonText: {
2003
+ fontSize: 12,
2004
+ fontWeight: "700",
2005
+ color: buoyColors.success,
2006
+ letterSpacing: 0.5
2007
+ },
2008
+ // JSON-like viewer styles (syntax highlighting)
2009
+ exportJsonBlock: {
2010
+ backgroundColor: buoyColors.hover,
2011
+ borderRadius: 6,
2012
+ padding: 12,
2013
+ borderWidth: 1,
2014
+ borderColor: buoyColors.border + "50",
2015
+ flexDirection: "row",
2016
+ flexWrap: "wrap",
2017
+ alignItems: "center",
2018
+ gap: 4
2019
+ },
2020
+ exportJsonProp: {
2021
+ fontSize: 12,
2022
+ fontFamily: "monospace",
2023
+ color: buoyColors.info,
2024
+ // Cyan for prop names
2025
+ fontWeight: "600"
2026
+ },
2027
+ exportJsonEquals: {
2028
+ fontSize: 12,
2029
+ fontFamily: "monospace",
2030
+ color: buoyColors.textMuted
2031
+ },
2032
+ exportJsonBracket: {
2033
+ fontSize: 12,
2034
+ fontFamily: "monospace",
2035
+ color: buoyColors.warning // Yellow for JSX brackets
2036
+ },
2037
+ exportJsonArrayBracket: {
2038
+ fontSize: 12,
2039
+ fontFamily: "monospace",
2040
+ color: buoyColors.text
2041
+ },
2042
+ exportJsonArrayContent: {
2043
+ flexDirection: "row",
2044
+ flexWrap: "wrap",
2045
+ alignItems: "center",
2046
+ gap: 2
2047
+ },
2048
+ exportJsonArrayItem: {
2049
+ flexDirection: "row",
2050
+ alignItems: "center"
2051
+ },
2052
+ exportJsonString: {
2053
+ fontSize: 12,
2054
+ fontFamily: "monospace",
2055
+ color: buoyColors.success // Green for strings
2056
+ },
2057
+ exportJsonComma: {
2058
+ fontSize: 12,
2059
+ fontFamily: "monospace",
2060
+ color: buoyColors.textMuted,
2061
+ marginRight: 4
2062
+ },
2063
+ exportJsonComment: {
2064
+ fontSize: 12,
2065
+ fontFamily: "monospace",
2066
+ color: buoyColors.textMuted,
2067
+ fontStyle: "italic"
2068
+ },
2069
+ // Pro tab styles - using buoyColors for consistency with other modals
2070
+ proContainer: {
2071
+ paddingHorizontal: 16,
2072
+ paddingTop: 16,
2073
+ paddingBottom: 24
2074
+ },
2075
+ proSection: {
2076
+ backgroundColor: buoyColors.card,
2077
+ borderRadius: 8,
2078
+ borderWidth: 1,
2079
+ borderColor: buoyColors.border,
2080
+ marginBottom: 12,
2081
+ overflow: "hidden"
2082
+ },
2083
+ proSectionContent: {
2084
+ paddingHorizontal: 16,
2085
+ paddingTop: 8,
2086
+ paddingBottom: 16
2087
+ },
2088
+ proStatsRow: {
2089
+ flexDirection: "row",
2090
+ justifyContent: "center",
2091
+ alignItems: "center",
2092
+ paddingVertical: 8
2093
+ },
2094
+ proStatItem: {
2095
+ alignItems: "center",
2096
+ paddingHorizontal: 20
2097
+ },
2098
+ proStatValue: {
2099
+ fontSize: 20,
2100
+ fontWeight: "700",
2101
+ color: buoyColors.text
2102
+ },
2103
+ proStatValueDanger: {
2104
+ color: buoyColors.error
2105
+ },
2106
+ proStatLabel: {
2107
+ fontSize: 10,
2108
+ color: buoyColors.textMuted,
2109
+ letterSpacing: 0.5,
2110
+ marginTop: 2
2111
+ },
2112
+ proStatDivider: {
2113
+ width: 1,
2114
+ height: 24,
2115
+ backgroundColor: buoyColors.border
2116
+ },
2117
+ proEmptyState: {
2118
+ alignItems: "center",
2119
+ paddingVertical: 20,
2120
+ gap: 8
2121
+ },
2122
+ proEmptyStateText: {
2123
+ fontSize: 11,
2124
+ color: buoyColors.textMuted,
2125
+ fontStyle: "italic"
2126
+ },
2127
+ proErrorState: {
2128
+ flexDirection: "row",
2129
+ alignItems: "center",
2130
+ gap: 8,
2131
+ paddingVertical: 12,
2132
+ paddingHorizontal: 12,
2133
+ backgroundColor: buoyColors.error + "15",
2134
+ borderRadius: 6
2135
+ },
2136
+ proErrorStateText: {
2137
+ fontSize: 11,
2138
+ color: buoyColors.error,
2139
+ flex: 1
2140
+ },
2141
+ proDevicesList: {
2142
+ gap: 6
2143
+ },
2144
+ proDeviceRow: {
2145
+ flexDirection: "row",
2146
+ alignItems: "center",
2147
+ paddingVertical: 8,
2148
+ paddingHorizontal: 10,
2149
+ backgroundColor: buoyColors.input,
2150
+ borderRadius: 6,
2151
+ borderWidth: 1,
2152
+ borderColor: buoyColors.border
2153
+ },
2154
+ proDeviceRowCurrent: {
2155
+ borderColor: buoyColors.success + "50",
2156
+ backgroundColor: buoyColors.success + "08"
2157
+ },
2158
+ proDeviceIcon: {
2159
+ width: 28,
2160
+ height: 28,
2161
+ borderRadius: 14,
2162
+ backgroundColor: buoyColors.hover,
2163
+ alignItems: "center",
2164
+ justifyContent: "center"
2165
+ },
2166
+ proDeviceInfo: {
2167
+ flex: 1,
2168
+ marginLeft: 10
2169
+ },
2170
+ proDeviceNameRow: {
2171
+ flexDirection: "row",
2172
+ alignItems: "center",
2173
+ gap: 6
2174
+ },
2175
+ proDeviceName: {
2176
+ fontSize: 12,
2177
+ fontWeight: "600",
2178
+ color: buoyColors.text,
2179
+ flexShrink: 1
2180
+ },
2181
+ proDeviceBadge: {
2182
+ backgroundColor: buoyColors.success + "20",
2183
+ paddingHorizontal: 5,
2184
+ paddingVertical: 1,
2185
+ borderRadius: 3
2186
+ },
2187
+ proDeviceBadgeText: {
2188
+ fontSize: 8,
2189
+ fontWeight: "700",
2190
+ color: buoyColors.success,
2191
+ letterSpacing: 0.3
2192
+ },
2193
+ proDeviceDetails: {
2194
+ fontSize: 10,
2195
+ color: buoyColors.textMuted,
2196
+ marginTop: 2
2197
+ },
2198
+ proDeviceDate: {
2199
+ fontSize: 9,
2200
+ color: buoyColors.textMuted,
2201
+ marginTop: 2
2202
+ },
2203
+ proDeviceRemoveBtn: {
2204
+ padding: 6,
2205
+ marginLeft: 4
2206
+ },
2207
+ proRegisterDeviceBtn: {
2208
+ flexDirection: "row",
2209
+ alignItems: "center",
2210
+ justifyContent: "center",
2211
+ gap: 8,
2212
+ paddingVertical: 10,
2213
+ paddingHorizontal: 16,
2214
+ borderRadius: 8,
2215
+ backgroundColor: buoyColors.primary + "15",
2216
+ borderWidth: 1,
2217
+ borderColor: buoyColors.primary + "40",
2218
+ marginTop: 10
2219
+ },
2220
+ proRegisterDeviceBtnText: {
2221
+ fontSize: 12,
2222
+ fontWeight: "600",
2223
+ color: buoyColors.primary
2224
+ },
2225
+ proSignOutBtn: {
2226
+ alignItems: "center",
2227
+ justifyContent: "center",
2228
+ paddingVertical: 12,
2229
+ marginTop: 8
2230
+ },
2231
+ proSignOutBtnText: {
2232
+ fontSize: 12,
2233
+ color: buoyColors.textMuted
2234
+ },
2235
+ proFreeDescription: {
2236
+ fontSize: 11,
2237
+ color: buoyColors.textSecondary,
2238
+ lineHeight: 16
2239
+ },
2240
+ proFeaturesList: {
2241
+ gap: 8
2242
+ },
2243
+ proFeatureItem: {
2244
+ flexDirection: "row",
2245
+ alignItems: "center",
2246
+ gap: 10
2247
+ },
2248
+ proFeatureText: {
2249
+ fontSize: 12,
2250
+ color: buoyColors.text
2251
+ },
2252
+ proUpgradeBtn: {
2253
+ flexDirection: "row",
2254
+ alignItems: "center",
2255
+ justifyContent: "center",
2256
+ gap: 8,
2257
+ backgroundColor: buoyColors.primary,
2258
+ borderRadius: 10,
2259
+ paddingVertical: 14,
2260
+ shadowColor: buoyColors.primary,
2261
+ shadowOffset: {
2262
+ width: 0,
2263
+ height: 0
2264
+ },
2265
+ shadowOpacity: 0.4,
2266
+ shadowRadius: 8
2267
+ },
2268
+ proUpgradeBtnText: {
2269
+ fontSize: 14,
2270
+ fontWeight: "700",
2271
+ color: buoyColors.base
2272
+ }
2273
+ });