@buoy-gg/core 7.0.15 → 7.0.16

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 (99) hide show
  1. package/lib/commonjs/floatingMenu/AppHost.web.js +359 -0
  2. package/lib/commonjs/floatingMenu/AppHostLogic.js +6 -0
  3. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +787 -418
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.js +1 -1
  5. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +244 -36
  6. package/lib/commonjs/floatingMenu/FloatingMenu.js +2 -2
  7. package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +2 -3
  8. package/lib/commonjs/floatingMenu/MinimizedToolsStack.web.js +287 -0
  9. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +16 -0
  10. package/lib/commonjs/floatingMenu/autoExternalSync.js +9 -0
  11. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +7 -4
  12. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +373 -231
  13. package/lib/commonjs/floatingMenu/dial/DialIcon.js +25 -3
  14. package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +1 -1
  15. package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.web.js +154 -0
  16. package/lib/commonjs/floatingMenu/dial/onboardingConstants.web.js +34 -0
  17. package/lib/commonjs/floatingMenu/floatingTools.js +3 -3
  18. package/lib/commonjs/floatingMenu/floatingTools.web.js +191 -28
  19. package/lib/commonjs/index.js +2 -2
  20. package/lib/commonjs/index.web.js +19 -0
  21. package/lib/commonjs/utils/autoDiscoverPresets.web.js +50 -5
  22. package/lib/module/floatingMenu/AppHost.web.js +351 -0
  23. package/lib/module/floatingMenu/AppHostLogic.js +6 -0
  24. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +792 -422
  25. package/lib/module/floatingMenu/FloatingDevTools.js +2 -2
  26. package/lib/module/floatingMenu/FloatingDevTools.web.js +247 -38
  27. package/lib/module/floatingMenu/FloatingMenu.js +2 -2
  28. package/lib/module/floatingMenu/MinimizedToolsContext.js +2 -3
  29. package/lib/module/floatingMenu/MinimizedToolsStack.web.js +284 -0
  30. package/lib/module/floatingMenu/autoDiscoverPresets.js +16 -0
  31. package/lib/module/floatingMenu/autoExternalSync.js +9 -0
  32. package/lib/module/floatingMenu/dial/DialDevTools.js +8 -5
  33. package/lib/module/floatingMenu/dial/DialDevTools.web.js +375 -233
  34. package/lib/module/floatingMenu/dial/DialIcon.js +26 -4
  35. package/lib/module/floatingMenu/dial/OnboardingTooltip.js +1 -1
  36. package/lib/module/floatingMenu/dial/OnboardingTooltip.web.js +149 -0
  37. package/lib/module/floatingMenu/dial/onboardingConstants.web.js +27 -0
  38. package/lib/module/floatingMenu/floatingTools.js +3 -3
  39. package/lib/module/floatingMenu/floatingTools.web.js +190 -28
  40. package/lib/module/index.js +2 -2
  41. package/lib/module/index.web.js +5 -0
  42. package/lib/module/utils/autoDiscoverPresets.web.js +53 -7
  43. package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts +38 -0
  44. package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts.map +1 -0
  45. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts +21 -1
  46. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +5 -2
  48. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +2 -14
  50. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts +2 -20
  52. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.web.d.ts +21 -0
  54. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.web.d.ts.map +1 -0
  55. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts +16 -2
  59. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.web.d.ts +9 -0
  62. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.web.d.ts.map +1 -0
  63. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts +13 -0
  64. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts.map +1 -0
  65. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts +29 -2
  66. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/index.web.d.ts +1 -0
  68. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts +2 -0
  70. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts.map +1 -1
  71. package/lib/typescript/module/floatingMenu/AppHost.web.d.ts +38 -0
  72. package/lib/typescript/module/floatingMenu/AppHost.web.d.ts.map +1 -0
  73. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts +21 -1
  74. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +1 -1
  75. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +5 -2
  76. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  77. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +2 -14
  78. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  79. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts +2 -20
  80. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts.map +1 -1
  81. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.web.d.ts +21 -0
  82. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.web.d.ts.map +1 -0
  83. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  84. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  85. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  86. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts +16 -2
  87. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  88. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  89. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.web.d.ts +9 -0
  90. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.web.d.ts.map +1 -0
  91. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts +13 -0
  92. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts.map +1 -0
  93. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts +29 -2
  94. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +1 -1
  95. package/lib/typescript/module/index.web.d.ts +1 -0
  96. package/lib/typescript/module/index.web.d.ts.map +1 -1
  97. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts +2 -0
  98. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts.map +1 -1
  99. package/package.json +5 -5
@@ -6,8 +6,11 @@
6
6
  * Uses shared configuration and logic from @buoy-gg/floating-tools-react.
7
7
  * Platform-specific: CSS, mouse events, web JSX.
8
8
  *
9
- * This follows the same pattern as DialDevTools - all colors, labels, descriptions,
10
- * and styling constants come from the shared core package.
9
+ * VISUAL PARITY: styled 1:1 against the native reference
10
+ * (devtools-floating-menu/src/floatingMenu/DevToolsSettingsModal.tsx, which
11
+ * renders inside JsModal + ModalHeader + TabSelector from @buoy-gg/shared-ui).
12
+ * shared-ui is RN-only, so its buoyColors palette and the native-only style
13
+ * values are inlined below with provenance comments.
11
14
  *
12
15
  * @platform React DOM Web (Vite, CRA, Next.js, Electron)
13
16
  */
@@ -18,20 +21,61 @@ import {
18
21
  useSettings,
19
22
  // Tab configuration
20
23
  settingsTabs,
21
- // Colors & styling
22
- settingsColors, settingsStyles, getToolColor,
24
+ // Shared style tokens (used where they match the native reference)
25
+ settingsStyles,
23
26
  // Tool metadata
24
27
  getToolLabel, getToolDescription,
25
28
  // Global settings config
26
29
  globalSettingsConfig
27
30
  // Types
28
31
  } from '@buoy-gg/floating-tools-react';
29
- import { ReduxIcon } from '@buoy-gg/floating-tools-core';
32
+ import { QueryIcon, EnvIcon, StorageIcon, NetworkIcon, RoutesIcon, HighlighterIcon, ReduxIcon, EventsIcon, WifiIcon, ImageOverlayIcon, ZustandIcon, JotaiIcon } from '@buoy-gg/floating-tools-core';
33
+ // JsModal.web + storage keys via the consumer's shared-ui web shim — the
34
+ // settings sheet is a real JsModal on web now, same as native.
35
+ import { JsModal, devToolsStorageKeys } from '@buoy-gg/shared-ui';
36
+
37
+ /** env(safe-area-inset-top) via a one-shot probe (native: useSafeAreaInsets). */
38
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
39
+ function readTopSafeAreaInset() {
40
+ if (typeof document === 'undefined') return 0;
41
+ const probe = document.createElement('div');
42
+ probe.style.cssText = 'position:fixed;top:env(safe-area-inset-top);visibility:hidden;pointer-events:none';
43
+ document.body.appendChild(probe);
44
+ const top = probe.getBoundingClientRect().top;
45
+ document.body.removeChild(probe);
46
+ return Math.max(0, top);
47
+ }
48
+
49
+ // =============================
50
+ // Native palette (provenance)
51
+ // =============================
52
+
53
+ /**
54
+ * buoyColors from @buoy-gg/shared-ui (gameUIColors.ts) — the palette the
55
+ * native settings modal is styled with. shared-ui cannot be imported on web,
56
+ * so the values are inlined verbatim.
57
+ */
58
+ const buoy = {
59
+ base: '#121212',
60
+ card: '#1A1A1A',
61
+ hover: '#242424',
62
+ input: '#2A2A2A',
63
+ border: '#333333',
64
+ borderStrong: '#444444',
65
+ text: '#E0E0E0',
66
+ textSecondary: '#A0A0A0',
67
+ textMuted: '#888888',
68
+ primary: '#20C997',
69
+ success: '#20C997',
70
+ warning: '#FFA94D',
71
+ error: '#EF4444',
72
+ info: '#00B8E6'
73
+ };
30
74
 
31
75
  // =============================
32
76
  // Types
33
77
  // =============================
34
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
78
+
35
79
  // =============================
36
80
  // Web Storage Adapter
37
81
  // =============================
@@ -45,21 +89,30 @@ const webStorageAdapter = {
45
89
  // Icon Components (Web SVGs)
46
90
  // =============================
47
91
 
48
- const Laptop = ({
92
+ /** Fallback for shared-ui Layers icon (native debug-borders card icon). */
93
+ const Layers = ({
49
94
  size = 24,
50
95
  color = 'currentColor'
51
- }) => /*#__PURE__*/_jsx("svg", {
96
+ }) => /*#__PURE__*/_jsxs("svg", {
52
97
  width: size,
53
98
  height: size,
54
99
  viewBox: "0 0 24 24",
55
100
  fill: "none",
56
101
  stroke: color,
57
102
  strokeWidth: "2",
58
- children: /*#__PURE__*/_jsx("path", {
59
- d: "M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"
60
- })
103
+ strokeLinecap: "round",
104
+ strokeLinejoin: "round",
105
+ children: [/*#__PURE__*/_jsx("path", {
106
+ d: "M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"
107
+ }), /*#__PURE__*/_jsx("path", {
108
+ d: "m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"
109
+ }), /*#__PURE__*/_jsx("path", {
110
+ d: "m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"
111
+ })]
61
112
  });
62
- const Database = ({
113
+
114
+ /** Fallback for shared-ui RenderCountIcon (native highlight-updates card icon). */
115
+ const Sparkles = ({
63
116
  size = 24,
64
117
  color = 'currentColor'
65
118
  }) => /*#__PURE__*/_jsxs("svg", {
@@ -69,18 +122,23 @@ const Database = ({
69
122
  fill: "none",
70
123
  stroke: color,
71
124
  strokeWidth: "2",
72
- children: [/*#__PURE__*/_jsx("ellipse", {
73
- cx: "12",
74
- cy: "5",
75
- rx: "9",
76
- ry: "3"
125
+ strokeLinecap: "round",
126
+ strokeLinejoin: "round",
127
+ children: [/*#__PURE__*/_jsx("path", {
128
+ d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
77
129
  }), /*#__PURE__*/_jsx("path", {
78
- d: "M3 5V19A9 3 0 0 0 21 19V5"
130
+ d: "M20 3v4"
79
131
  }), /*#__PURE__*/_jsx("path", {
80
- d: "M3 12A9 3 0 0 0 21 12"
132
+ d: "M22 5h-4"
133
+ }), /*#__PURE__*/_jsx("path", {
134
+ d: "M4 17v2"
135
+ }), /*#__PURE__*/_jsx("path", {
136
+ d: "M5 18H3"
81
137
  })]
82
138
  });
83
- const Wifi = ({
139
+
140
+ /** Fallback for shared-ui SentryBugIcon (native sentry card icon, red). */
141
+ const AlertTriangle = ({
84
142
  size = 24,
85
143
  color = 'currentColor'
86
144
  }) => /*#__PURE__*/_jsxs("svg", {
@@ -90,20 +148,19 @@ const Wifi = ({
90
148
  fill: "none",
91
149
  stroke: color,
92
150
  strokeWidth: "2",
151
+ strokeLinecap: "round",
152
+ strokeLinejoin: "round",
93
153
  children: [/*#__PURE__*/_jsx("path", {
94
- d: "M5 13a10 10 0 0 1 14 0"
154
+ d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"
95
155
  }), /*#__PURE__*/_jsx("path", {
96
- d: "M8.5 16.5a5 5 0 0 1 7 0"
156
+ d: "M12 9v4"
97
157
  }), /*#__PURE__*/_jsx("path", {
98
- d: "M2 8.82a15 15 0 0 1 20 0"
99
- }), /*#__PURE__*/_jsx("line", {
100
- x1: "12",
101
- y1: "20",
102
- x2: "12.01",
103
- y2: "20"
158
+ d: "M12 17h.01"
104
159
  })]
105
160
  });
106
- const Search = ({
161
+
162
+ /** shared-ui Info icon (native default tool icon). */
163
+ const Info = ({
107
164
  size = 24,
108
165
  color = 'currentColor'
109
166
  }) => /*#__PURE__*/_jsxs("svg", {
@@ -113,15 +170,21 @@ const Search = ({
113
170
  fill: "none",
114
171
  stroke: color,
115
172
  strokeWidth: "2",
173
+ strokeLinecap: "round",
174
+ strokeLinejoin: "round",
116
175
  children: [/*#__PURE__*/_jsx("circle", {
117
- cx: "11",
118
- cy: "11",
119
- r: "8"
176
+ cx: "12",
177
+ cy: "12",
178
+ r: "10"
179
+ }), /*#__PURE__*/_jsx("path", {
180
+ d: "M12 16v-4"
120
181
  }), /*#__PURE__*/_jsx("path", {
121
- d: "m21 21-4.3-4.3"
182
+ d: "M12 8h.01"
122
183
  })]
123
184
  });
124
- const Map = ({
185
+
186
+ /** shared-ui Database icon (native storage status card icon). */
187
+ const Database = ({
125
188
  size = 24,
126
189
  color = 'currentColor'
127
190
  }) => /*#__PURE__*/_jsxs("svg", {
@@ -131,29 +194,44 @@ const Map = ({
131
194
  fill: "none",
132
195
  stroke: color,
133
196
  strokeWidth: "2",
134
- children: [/*#__PURE__*/_jsx("path", {
135
- d: "M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z"
197
+ strokeLinecap: "round",
198
+ strokeLinejoin: "round",
199
+ children: [/*#__PURE__*/_jsx("ellipse", {
200
+ cx: "12",
201
+ cy: "5",
202
+ rx: "9",
203
+ ry: "3"
136
204
  }), /*#__PURE__*/_jsx("path", {
137
- d: "M15 5.764v15"
205
+ d: "M3 5V19A9 3 0 0 0 21 19V5"
138
206
  }), /*#__PURE__*/_jsx("path", {
139
- d: "M9 3.236v15"
207
+ d: "M3 12A9 3 0 0 0 21 12"
140
208
  })]
141
209
  });
142
- const Zap = ({
210
+
211
+ /** shared-ui CheckCircle2 icon (native storage badge check). */
212
+ const CheckCircle2 = ({
143
213
  size = 24,
144
214
  color = 'currentColor'
145
- }) => /*#__PURE__*/_jsx("svg", {
215
+ }) => /*#__PURE__*/_jsxs("svg", {
146
216
  width: size,
147
217
  height: size,
148
218
  viewBox: "0 0 24 24",
149
219
  fill: "none",
150
220
  stroke: color,
151
221
  strokeWidth: "2",
152
- children: /*#__PURE__*/_jsx("path", {
153
- d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"
154
- })
222
+ strokeLinecap: "round",
223
+ strokeLinejoin: "round",
224
+ children: [/*#__PURE__*/_jsx("circle", {
225
+ cx: "12",
226
+ cy: "12",
227
+ r: "10"
228
+ }), /*#__PURE__*/_jsx("path", {
229
+ d: "m9 12 2 2 4-4"
230
+ })]
155
231
  });
156
- const Sparkles = ({
232
+
233
+ /** shared-ui FileText icon (native SAVED SETTINGS header icon). */
234
+ const FileText = ({
157
235
  size = 24,
158
236
  color = 'currentColor'
159
237
  }) => /*#__PURE__*/_jsxs("svg", {
@@ -163,37 +241,23 @@ const Sparkles = ({
163
241
  fill: "none",
164
242
  stroke: color,
165
243
  strokeWidth: "2",
244
+ strokeLinecap: "round",
245
+ strokeLinejoin: "round",
166
246
  children: [/*#__PURE__*/_jsx("path", {
167
- d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
247
+ d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"
168
248
  }), /*#__PURE__*/_jsx("path", {
169
- d: "M20 3v4"
249
+ d: "M14 2v4a2 2 0 0 0 2 2h4"
170
250
  }), /*#__PURE__*/_jsx("path", {
171
- d: "M22 5h-4"
251
+ d: "M10 9H8"
172
252
  }), /*#__PURE__*/_jsx("path", {
173
- d: "M4 17v2"
253
+ d: "M16 13H8"
174
254
  }), /*#__PURE__*/_jsx("path", {
175
- d: "M5 18H3"
255
+ d: "M16 17H8"
176
256
  })]
177
257
  });
178
- const Square = ({
179
- size = 24,
180
- color = 'currentColor'
181
- }) => /*#__PURE__*/_jsx("svg", {
182
- width: size,
183
- height: size,
184
- viewBox: "0 0 24 24",
185
- fill: "none",
186
- stroke: color,
187
- strokeWidth: "2",
188
- children: /*#__PURE__*/_jsx("rect", {
189
- width: "18",
190
- height: "18",
191
- x: "3",
192
- y: "3",
193
- rx: "2"
194
- })
195
- });
196
- const AlertTriangle = ({
258
+
259
+ /** shared-ui Trash2 icon (native CLEAR ALL SETTINGS button icon). */
260
+ const Trash2 = ({
197
261
  size = 24,
198
262
  color = 'currentColor'
199
263
  }) => /*#__PURE__*/_jsxs("svg", {
@@ -203,122 +267,193 @@ const AlertTriangle = ({
203
267
  fill: "none",
204
268
  stroke: color,
205
269
  strokeWidth: "2",
270
+ strokeLinecap: "round",
271
+ strokeLinejoin: "round",
206
272
  children: [/*#__PURE__*/_jsx("path", {
207
- d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"
273
+ d: "M3 6h18"
208
274
  }), /*#__PURE__*/_jsx("path", {
209
- d: "M12 9v4"
275
+ d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"
210
276
  }), /*#__PURE__*/_jsx("path", {
211
- d: "M12 17h.01"
277
+ d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
278
+ }), /*#__PURE__*/_jsx("line", {
279
+ x1: "10",
280
+ x2: "10",
281
+ y1: "11",
282
+ y2: "17"
283
+ }), /*#__PURE__*/_jsx("line", {
284
+ x1: "14",
285
+ x2: "14",
286
+ y1: "11",
287
+ y2: "17"
212
288
  })]
213
289
  });
214
- const SettingsIcon = ({
215
- size = 24,
290
+ function ChevronRightIcon({
291
+ size = 18,
216
292
  color = 'currentColor'
217
- }) => /*#__PURE__*/_jsxs("svg", {
218
- width: size,
219
- height: size,
220
- viewBox: "0 0 24 24",
221
- fill: "none",
222
- stroke: color,
223
- strokeWidth: "2",
224
- children: [/*#__PURE__*/_jsx("path", {
225
- d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"
226
- }), /*#__PURE__*/_jsx("circle", {
227
- cx: "12",
228
- cy: "12",
229
- r: "3"
230
- })]
231
- });
232
- function ChevronRightIcon() {
293
+ }) {
233
294
  return /*#__PURE__*/_jsx("svg", {
234
- width: "18",
235
- height: "18",
295
+ width: size,
296
+ height: size,
236
297
  viewBox: "0 0 24 24",
237
298
  fill: "none",
238
- stroke: "currentColor",
299
+ stroke: color,
239
300
  strokeWidth: "2",
301
+ strokeLinecap: "round",
302
+ strokeLinejoin: "round",
240
303
  children: /*#__PURE__*/_jsx("path", {
241
304
  d: "M9 18l6-6-6-6"
242
305
  })
243
306
  });
244
307
  }
245
- function CloseIcon() {
308
+ function ChevronDownIcon({
309
+ size = 18,
310
+ color = 'currentColor'
311
+ }) {
246
312
  return /*#__PURE__*/_jsx("svg", {
247
- width: "20",
248
- height: "20",
313
+ width: size,
314
+ height: size,
249
315
  viewBox: "0 0 24 24",
250
316
  fill: "none",
251
- stroke: "currentColor",
317
+ stroke: color,
252
318
  strokeWidth: "2",
319
+ strokeLinecap: "round",
320
+ strokeLinejoin: "round",
253
321
  children: /*#__PURE__*/_jsx("path", {
254
- d: "M18 6L6 18M6 6l12 12"
322
+ d: "m6 9 6 6 6-6"
255
323
  })
256
324
  });
257
325
  }
258
326
 
259
- // Tool icon mapping using SVG components
327
+ /**
328
+ * Tool icon mapping — mirrors the native getToolIcon() switch in
329
+ * DevToolsSettingsModal.tsx. Real tool icons come from floating-tools-core
330
+ * (web-compatible); shared-ui-only icons (SentryBugIcon, WifiCircuitIcon,
331
+ * Layers, RenderCountIcon) use the closest web SVG fallback.
332
+ */
260
333
  function ToolIcon({
261
- toolId,
262
- color
334
+ toolId
263
335
  }) {
264
- const size = 16;
265
- const iconMap = {
266
- env: /*#__PURE__*/_jsx(Laptop, {
267
- size: size,
268
- color: color
269
- }),
270
- storage: /*#__PURE__*/_jsx(Database, {
271
- size: size,
272
- color: color
273
- }),
274
- network: /*#__PURE__*/_jsx(Wifi, {
275
- size: size,
276
- color: color
277
- }),
278
- query: /*#__PURE__*/_jsx(Search, {
279
- size: size,
280
- color: color
281
- }),
282
- 'route-events': /*#__PURE__*/_jsx(Map, {
283
- size: size,
284
- color: color
285
- }),
286
- routes: /*#__PURE__*/_jsx(Map, {
287
- size: size,
288
- color: color
289
- }),
290
- benchmark: /*#__PURE__*/_jsx(Zap, {
291
- size: size,
292
- color: color
293
- }),
294
- 'highlight-updates': /*#__PURE__*/_jsx(Sparkles, {
295
- size: size,
296
- color: color
297
- }),
298
- renders: /*#__PURE__*/_jsx(Sparkles, {
299
- size: size,
300
- color: color
301
- }),
302
- 'debug-borders': /*#__PURE__*/_jsx(Square, {
303
- size: size,
304
- color: color
305
- }),
306
- sentry: /*#__PURE__*/_jsx(AlertTriangle, {
307
- size: size,
308
- color: color
309
- }),
310
- environment: /*#__PURE__*/_jsx(SettingsIcon, {
311
- size: size,
312
- color: color
313
- }),
314
- redux: /*#__PURE__*/_jsx(ReduxIcon, {
315
- size: size
316
- })
336
+ const size = 16; // native: getToolIcon renders all icons at size 16
337
+ switch (toolId) {
338
+ case 'query':
339
+ return /*#__PURE__*/_jsx(QueryIcon, {
340
+ size: size
341
+ });
342
+ case 'env':
343
+ case 'environment':
344
+ return /*#__PURE__*/_jsx(EnvIcon, {
345
+ size: size
346
+ });
347
+ case 'sentry':
348
+ // native: SentryBugIcon colorPreset="red" noBackground
349
+ return /*#__PURE__*/_jsx(AlertTriangle, {
350
+ size: size,
351
+ color: buoy.error
352
+ });
353
+ case 'storage':
354
+ return /*#__PURE__*/_jsx(StorageIcon, {
355
+ size: size
356
+ });
357
+ case 'wifi':
358
+ case 'query-wifi-toggle':
359
+ // native: WifiCircuitIcon colorPreset="green" (shared-ui only)
360
+ return /*#__PURE__*/_jsx(WifiIcon, {
361
+ size: size
362
+ });
363
+ case 'route-events':
364
+ case 'routes':
365
+ // web alias for the same tool
366
+ return /*#__PURE__*/_jsx(RoutesIcon, {
367
+ size: size
368
+ });
369
+ case 'network':
370
+ return /*#__PURE__*/_jsx(NetworkIcon, {
371
+ size: size
372
+ });
373
+ case 'debug-borders':
374
+ // native: Layers size 16 color buoyColors.primary
375
+ return /*#__PURE__*/_jsx(Layers, {
376
+ size: size,
377
+ color: buoy.primary
378
+ });
379
+ case 'highlight-updates':
380
+ // native: RenderCountIcon size 16 color buoyColors.primary
381
+ return /*#__PURE__*/_jsx(Sparkles, {
382
+ size: size,
383
+ color: buoy.primary
384
+ });
385
+ case 'highlight-updates-modal':
386
+ return /*#__PURE__*/_jsx(HighlighterIcon, {
387
+ size: size
388
+ });
389
+ case 'redux':
390
+ return /*#__PURE__*/_jsx(ReduxIcon, {
391
+ size: size
392
+ });
393
+ case 'image-overlay':
394
+ return /*#__PURE__*/_jsx(ImageOverlayIcon, {
395
+ size: size
396
+ });
397
+ case 'events':
398
+ return /*#__PURE__*/_jsx(EventsIcon, {
399
+ size: size
400
+ });
401
+ case 'zustand':
402
+ return /*#__PURE__*/_jsx(ZustandIcon, {
403
+ size: size
404
+ });
405
+ case 'jotai':
406
+ return /*#__PURE__*/_jsx(JotaiIcon, {
407
+ size: size
408
+ });
409
+ default:
410
+ // native: Info size 16 color buoyColors.textSecondary
411
+ return /*#__PURE__*/_jsx(Info, {
412
+ size: size,
413
+ color: buoy.textSecondary
414
+ });
415
+ }
416
+ }
417
+
418
+ // =============================
419
+ // Toggle Pill (shared by cards)
420
+ // =============================
421
+
422
+ /**
423
+ * Native styles.pillToggle / pillToggleOn / pillToggleOff.
424
+ * onColor lets the global-settings cards use the success color like native.
425
+ */
426
+ function togglePillStyle(on, onColor, onStrongBorder) {
427
+ return {
428
+ paddingLeft: 14,
429
+ // native DevToolsSettingsModal.tsx pillToggle: paddingHorizontal 14
430
+ paddingRight: 14,
431
+ paddingTop: settingsStyles.toggle.paddingVertical,
432
+ paddingBottom: settingsStyles.toggle.paddingVertical,
433
+ borderRadius: 6,
434
+ // native pillToggle: borderRadius 6
435
+ borderWidth: settingsStyles.toggle.borderWidth,
436
+ borderStyle: 'solid',
437
+ minWidth: 48,
438
+ // native pillToggle: minWidth 48
439
+ boxSizing: 'border-box',
440
+ display: 'inline-flex',
441
+ alignItems: 'center',
442
+ justifyContent: 'center',
443
+ backgroundColor: on ? `${onColor}${onStrongBorder ? '33' : '20'}` : buoy.hover,
444
+ borderColor: on ? onStrongBorder ? `${onColor}88` : onColor : buoy.border,
445
+ color: on ? onColor : buoy.textMuted,
446
+ fontSize: settingsStyles.toggle.fontSize,
447
+ fontWeight: 600,
448
+ // native pillToggleText: fontWeight 600
449
+ letterSpacing: 0.5,
450
+ // native pillToggleText: letterSpacing 0.5
451
+ fontFamily: 'inherit',
452
+ cursor: 'pointer',
453
+ transition: 'all 150ms ease',
454
+ // native global-settings toggle: teal glow when ON (shadowRadius 8 / opacity 0.4)
455
+ boxShadow: on && onStrongBorder ? `0 0 8px ${onColor}66` : 'none'
317
456
  };
318
- return iconMap[toolId] || /*#__PURE__*/_jsx(SettingsIcon, {
319
- size: size,
320
- color: color
321
- });
322
457
  }
323
458
 
324
459
  // =============================
@@ -332,63 +467,54 @@ function ToolCard({
332
467
  availableTools,
333
468
  onToggle
334
469
  }) {
335
- const [isHovered, setIsHovered] = useState(false);
336
- const color = getToolColor(toolId);
337
470
  const label = getToolLabel(toolId, availableTools);
338
471
  const description = getToolDescription(toolId, availableTools);
472
+
473
+ // native styles.glassCard / glassCardActive
339
474
  const cardStyle = {
340
475
  display: 'flex',
341
476
  flexDirection: 'row',
342
477
  alignItems: 'center',
343
- gap: settingsStyles.cardInner.gap,
344
- paddingTop: settingsStyles.card.paddingVertical,
345
- paddingBottom: settingsStyles.card.paddingVertical,
478
+ gap: 12,
479
+ // native glassCardInner: gap 12
480
+ paddingTop: 12,
481
+ // native glassCard: paddingVertical 12
482
+ paddingBottom: 12,
346
483
  paddingLeft: settingsStyles.card.paddingHorizontal,
347
484
  paddingRight: settingsStyles.card.paddingHorizontal,
348
- backgroundColor: settingsColors.cardBackground,
349
- borderRadius: settingsStyles.card.borderRadius,
485
+ backgroundColor: enabled ? `${buoy.primary}08` : buoy.card,
486
+ // native glassCardActive bg
487
+ borderRadius: 12,
488
+ // native glassCard: borderRadius 12
350
489
  borderWidth: settingsStyles.card.borderWidth,
351
490
  borderStyle: 'solid',
352
- borderColor: isHovered && !disabled ? settingsColors.cardBorderHover : settingsColors.cardBorder,
353
- marginBottom: settingsStyles.card.marginBottom,
491
+ borderColor: enabled ? `${buoy.primary}50` : buoy.border,
492
+ // native glassCardActive border
493
+ marginBottom: 8,
494
+ // native renderToolCard wrapper: marginBottom 8
354
495
  cursor: disabled ? 'not-allowed' : 'pointer',
355
- opacity: disabled ? 0.6 : 1,
356
- transition: 'all 150ms ease',
357
- boxShadow: enabled ? `0 0 6px ${color}33, 0 0 12px ${color}20` : 'none'
358
- };
359
- const toggleStyle = {
360
- paddingLeft: settingsStyles.toggle.paddingHorizontal,
361
- paddingRight: settingsStyles.toggle.paddingHorizontal,
362
- paddingTop: settingsStyles.toggle.paddingVertical,
363
- paddingBottom: settingsStyles.toggle.paddingVertical,
364
- borderRadius: settingsStyles.toggle.borderRadius,
365
- borderWidth: settingsStyles.toggle.borderWidth,
366
- borderStyle: 'solid',
367
- backgroundColor: enabled ? settingsColors.toggleOnBg(color) : settingsColors.toggleOffBg,
368
- borderColor: enabled ? settingsColors.toggleOnBorder(color) : settingsColors.toggleOffBorder,
369
- color: enabled ? color : settingsColors.toggleOffText,
370
- fontSize: settingsStyles.toggle.fontSize,
371
- fontWeight: settingsStyles.toggle.fontWeight,
372
- cursor: disabled ? 'not-allowed' : 'pointer',
373
- transition: 'all 150ms ease',
374
- boxShadow: enabled ? `0 0 8px ${color}66` : 'none'
496
+ opacity: disabled ? 0.5 : 1,
497
+ // native wrapper: opacity 0.5 when disabled
498
+ transition: 'all 150ms ease'
375
499
  };
376
500
  return /*#__PURE__*/_jsxs("div", {
377
501
  onClick: disabled ? undefined : onToggle,
378
- onMouseEnter: () => setIsHovered(true),
379
- onMouseLeave: () => setIsHovered(false),
380
502
  style: cardStyle,
381
503
  children: [/*#__PURE__*/_jsx("div", {
382
504
  style: {
383
- width: settingsStyles.iconContainer.width,
384
- height: settingsStyles.iconContainer.height,
505
+ width: 32,
506
+ // native iconContainer: 32x32
507
+ height: 32,
508
+ borderRadius: 8,
509
+ // native iconContainer: borderRadius 8
510
+ backgroundColor: enabled ? `${buoy.primary}20` : buoy.hover,
385
511
  display: 'flex',
386
512
  alignItems: 'center',
387
- justifyContent: 'center'
513
+ justifyContent: 'center',
514
+ flexShrink: 0
388
515
  },
389
516
  children: /*#__PURE__*/_jsx(ToolIcon, {
390
- toolId: toolId,
391
- color: color
517
+ toolId: toolId
392
518
  })
393
519
  }), /*#__PURE__*/_jsxs("div", {
394
520
  style: {
@@ -397,15 +523,18 @@ function ToolCard({
397
523
  },
398
524
  children: [/*#__PURE__*/_jsxs("div", {
399
525
  style: {
400
- color: settingsColors.text,
401
- fontWeight: settingsStyles.toolName.fontWeight,
526
+ color: buoy.text,
527
+ fontWeight: 600,
528
+ // native toolName: fontWeight 600
402
529
  fontSize: settingsStyles.toolName.fontSize
403
530
  },
404
531
  children: [label, disabled && ' (MAX 6)']
405
- }), description && /*#__PURE__*/_jsx("div", {
532
+ }), /*#__PURE__*/_jsx("div", {
406
533
  style: {
407
- color: settingsColors.textMuted,
534
+ color: buoy.textMuted,
408
535
  fontSize: settingsStyles.toolDescription.fontSize,
536
+ marginTop: 2,
537
+ // native toolDescription: marginTop 2
409
538
  overflow: 'hidden',
410
539
  textOverflow: 'ellipsis',
411
540
  whiteSpace: 'nowrap'
@@ -418,17 +547,181 @@ function ToolCard({
418
547
  if (!disabled) onToggle();
419
548
  },
420
549
  disabled: disabled,
421
- style: toggleStyle,
422
- children: enabled ? 'ON' : 'OFF'
423
- }), /*#__PURE__*/_jsx("div", {
424
550
  style: {
425
- color: settingsColors.textMuted
551
+ ...togglePillStyle(enabled, buoy.primary, false),
552
+ cursor: disabled ? 'not-allowed' : 'pointer'
426
553
  },
427
- children: /*#__PURE__*/_jsx(ChevronRightIcon, {})
554
+ children: enabled ? 'ON' : 'OFF'
428
555
  })]
429
556
  });
430
557
  }
431
558
 
559
+ // =============================
560
+ // Global Setting Card (expandable, native renderGlobalSettingCard)
561
+ // =============================
562
+
563
+ function GlobalSettingCard({
564
+ configKey,
565
+ label,
566
+ category,
567
+ shortDescription,
568
+ fullDescription,
569
+ recommendation,
570
+ value,
571
+ onToggle
572
+ }) {
573
+ const [isExpanded, setIsExpanded] = useState(false);
574
+
575
+ // native styles.expandableCard (+ expanded teal ring/glow/scale)
576
+ const cardStyle = {
577
+ backgroundColor: buoy.card,
578
+ borderRadius: 8,
579
+ borderWidth: isExpanded ? 2 : 1,
580
+ // native expanded: borderWidth 2
581
+ borderStyle: 'solid',
582
+ borderColor: isExpanded ? buoy.primary : buoy.border,
583
+ padding: 12,
584
+ cursor: 'pointer',
585
+ transition: 'all 150ms ease',
586
+ // native expanded: shadowColor primary, opacity 0.8, radius 20
587
+ boxShadow: isExpanded ? `0 0 20px ${buoy.primary}CC` : 'none',
588
+ transform: isExpanded ? 'scale(1.01)' : 'scale(1)' // native expanded: scale 1.01
589
+ };
590
+ return /*#__PURE__*/_jsx("div", {
591
+ style: {
592
+ marginBottom: 10
593
+ },
594
+ children: /*#__PURE__*/_jsxs("div", {
595
+ onClick: () => setIsExpanded(v => !v),
596
+ style: cardStyle,
597
+ children: [/*#__PURE__*/_jsxs("div", {
598
+ style: {
599
+ display: 'flex',
600
+ alignItems: 'center',
601
+ gap: 12
602
+ },
603
+ children: [/*#__PURE__*/_jsx("div", {
604
+ style: {
605
+ backgroundColor: `${buoy.primary}20`,
606
+ border: `1px solid ${buoy.primary}40`,
607
+ borderRadius: 4,
608
+ padding: '4px 8px',
609
+ flexShrink: 0
610
+ },
611
+ children: /*#__PURE__*/_jsx("span", {
612
+ style: {
613
+ fontSize: 10,
614
+ fontWeight: 700,
615
+ color: buoy.primary,
616
+ letterSpacing: 0.5
617
+ },
618
+ children: category
619
+ })
620
+ }), /*#__PURE__*/_jsxs("div", {
621
+ style: {
622
+ flex: 1,
623
+ minWidth: 0,
624
+ paddingLeft: 4,
625
+ paddingRight: 4
626
+ },
627
+ children: [/*#__PURE__*/_jsx("div", {
628
+ style: {
629
+ fontFamily: 'monospace',
630
+ fontSize: 12,
631
+ fontWeight: 700,
632
+ color: buoy.text
633
+ },
634
+ children: label
635
+ }), !isExpanded && /*#__PURE__*/_jsx("div", {
636
+ style: {
637
+ fontSize: 10,
638
+ color: buoy.textMuted,
639
+ marginTop: 2,
640
+ overflow: 'hidden',
641
+ textOverflow: 'ellipsis',
642
+ whiteSpace: 'nowrap'
643
+ },
644
+ children: shortDescription
645
+ })]
646
+ }), /*#__PURE__*/_jsxs("div", {
647
+ style: {
648
+ display: 'flex',
649
+ alignItems: 'center',
650
+ gap: 8,
651
+ flexShrink: 0
652
+ },
653
+ children: [/*#__PURE__*/_jsx("button", {
654
+ onClick: e => {
655
+ e.stopPropagation();
656
+ onToggle();
657
+ },
658
+ style: togglePillStyle(value, buoy.success, true),
659
+ children: value ? 'ON' : 'OFF'
660
+ }), isExpanded ?
661
+ /*#__PURE__*/
662
+ // native: ChevronDown size 18 color #7F91B2
663
+ _jsx(ChevronDownIcon, {
664
+ size: 18,
665
+ color: "#7F91B2"
666
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
667
+ size: 18,
668
+ color: "#7F91B2"
669
+ })]
670
+ })]
671
+ }), isExpanded && /*#__PURE__*/_jsxs("div", {
672
+ style: {
673
+ marginTop: 12,
674
+ paddingTop: 12,
675
+ borderTop: `1px solid ${buoy.border}50`
676
+ },
677
+ children: [/*#__PURE__*/_jsxs("div", {
678
+ style: {
679
+ marginBottom: 12
680
+ },
681
+ children: [/*#__PURE__*/_jsx("div", {
682
+ style: {
683
+ fontSize: 10,
684
+ fontWeight: 700,
685
+ color: buoy.primary,
686
+ letterSpacing: 1,
687
+ marginBottom: 6
688
+ },
689
+ children: "DESCRIPTION"
690
+ }), /*#__PURE__*/_jsx("div", {
691
+ style: {
692
+ fontSize: 12,
693
+ color: buoy.textSecondary,
694
+ lineHeight: '18px'
695
+ },
696
+ children: fullDescription
697
+ })]
698
+ }), /*#__PURE__*/_jsxs("div", {
699
+ style: {
700
+ marginBottom: 12
701
+ },
702
+ children: [/*#__PURE__*/_jsx("div", {
703
+ style: {
704
+ fontSize: 10,
705
+ fontWeight: 700,
706
+ color: buoy.primary,
707
+ letterSpacing: 1,
708
+ marginBottom: 6
709
+ },
710
+ children: "RECOMMENDATION"
711
+ }), /*#__PURE__*/_jsx("div", {
712
+ style: {
713
+ fontSize: 12,
714
+ color: buoy.textSecondary,
715
+ lineHeight: '18px'
716
+ },
717
+ children: recommendation
718
+ })]
719
+ })]
720
+ })]
721
+ })
722
+ }, configKey);
723
+ }
724
+
432
725
  // =============================
433
726
  // Main Component
434
727
  // =============================
@@ -443,7 +736,9 @@ export function SettingsModal({
443
736
  onSettingsChange
444
737
  }) {
445
738
  const [activeTab, setActiveTab] = useState('floating');
446
- const [isExiting, setIsExiting] = useState(false);
739
+ const [isStorageExpanded, setIsStorageExpanded] = useState(false);
740
+ const [savedKeys, setSavedKeys] = useState([]);
741
+ const [clearSuccess, setClearSuccess] = useState(false);
447
742
 
448
743
  // Use the shared settings hook
449
744
  const {
@@ -458,13 +753,9 @@ export function SettingsModal({
458
753
  onSettingsChange
459
754
  });
460
755
 
461
- // Handle close with animation
756
+ // JsModal owns the close animation now — this is just the Escape path.
462
757
  const handleClose = useCallback(() => {
463
- setIsExiting(true);
464
- setTimeout(() => {
465
- setIsExiting(false);
466
- onClose();
467
- }, 200);
758
+ onClose();
468
759
  }, [onClose]);
469
760
 
470
761
  // Escape key handler
@@ -478,255 +769,334 @@ export function SettingsModal({
478
769
  document.addEventListener('keydown', handleKeyDown);
479
770
  return () => document.removeEventListener('keydown', handleKeyDown);
480
771
  }, [visible, handleClose]);
772
+
773
+ // Saved-settings key list for the storage card (mirrors the native
774
+ // persistentStorage.getAllKeys() list; on web, Buoy keys in localStorage).
775
+ useEffect(() => {
776
+ if (!visible || !isStorageExpanded) return;
777
+ try {
778
+ const keys = Object.keys(localStorage).filter(key => key.startsWith('@react_buoy')).sort();
779
+ setSavedKeys(keys);
780
+ } catch {
781
+ setSavedKeys([]);
782
+ }
783
+ }, [visible, isStorageExpanded, clearSuccess, settings]);
784
+ const handleClearAll = useCallback(() => {
785
+ // Same behavior as before (reset via the shared hook); the 2s "CLEARED"
786
+ // flash mirrors the native clear button feedback.
787
+ actions.resetToDefaults();
788
+ setClearSuccess(true);
789
+ setTimeout(() => setClearSuccess(false), 2000);
790
+ }, [actions]);
481
791
  if (!visible) return null;
482
792
 
483
793
  // Filter tabs - exclude 'pro' for now on web
484
794
  const displayTabs = settingsTabs.filter(tab => tab.key !== 'pro');
485
- const containerStyle = {
486
- position: 'fixed',
487
- inset: 0,
488
- zIndex: 10000,
489
- display: 'flex',
490
- alignItems: 'center',
491
- justifyContent: 'center'
492
- };
493
- const backdropStyle = {
494
- position: 'absolute',
495
- inset: 0,
496
- backgroundColor: settingsColors.backdrop,
497
- opacity: isExiting ? 0 : 1,
498
- transition: 'opacity 200ms ease'
499
- };
500
- const modalStyle = {
501
- position: 'relative',
502
- width: '90%',
503
- maxWidth: 450,
504
- maxHeight: '80vh',
505
- backgroundColor: settingsColors.panel,
506
- borderRadius: 12,
507
- border: `1px solid ${settingsColors.cardBorder}`,
508
- boxShadow: `0 24px 48px rgba(0, 0, 0, 0.5), 0 0 24px ${settingsColors.info}20`,
509
- display: 'flex',
510
- flexDirection: 'column',
511
- overflow: 'hidden',
512
- transform: isExiting ? 'scale(0.95)' : 'scale(1)',
513
- opacity: isExiting ? 0 : 1,
514
- transition: 'transform 200ms ease, opacity 200ms ease'
515
- };
516
- return /*#__PURE__*/_jsxs("div", {
517
- role: "dialog",
518
- "aria-label": "Settings",
519
- "aria-modal": "true",
520
- style: containerStyle,
521
- children: [/*#__PURE__*/_jsx("div", {
522
- role: "button",
523
- "aria-label": "Close settings",
524
- tabIndex: 0,
525
- onClick: handleClose,
526
- style: backdropStyle
527
- }), /*#__PURE__*/_jsxs("div", {
528
- style: modalStyle,
529
- children: [/*#__PURE__*/_jsxs("div", {
530
- style: {
531
- display: 'flex',
532
- alignItems: 'center',
533
- padding: '12px 16px',
534
- borderBottom: `1px solid ${settingsColors.cardBorder}`
535
- },
536
- children: [/*#__PURE__*/_jsx("div", {
795
+
796
+ // Tab pill row rendered inside the JsModal header — native passes
797
+ // ModalHeader + TabSelector as header.customContent the same way.
798
+ const tabHeader = /*#__PURE__*/_jsx("div", {
799
+ style: {
800
+ display: 'flex',
801
+ alignItems: 'center',
802
+ paddingLeft: 4,
803
+ paddingRight: 28,
804
+ // clears JsModal's absolutely-positioned window controls
805
+ paddingBottom: 6
806
+ },
807
+ children: /*#__PURE__*/_jsx("div", {
808
+ style: {
809
+ flex: 1,
810
+ display: 'flex',
811
+ backgroundColor: buoy.hover,
812
+ borderRadius: 6,
813
+ padding: 2,
814
+ border: `1px solid ${buoy.border}`,
815
+ height: 28,
816
+ boxSizing: 'border-box'
817
+ },
818
+ children: displayTabs.map(tab => {
819
+ const isActive = activeTab === tab.key;
820
+ return /*#__PURE__*/_jsx("button", {
821
+ onClick: () => setActiveTab(tab.key),
537
822
  style: {
538
823
  flex: 1,
539
- display: 'flex',
540
- backgroundColor: settingsColors.panel,
541
- borderRadius: 6,
542
- padding: 2,
543
- border: `1px solid ${settingsColors.cardBorder}`
544
- },
545
- children: displayTabs.map(tab => {
546
- const isActive = activeTab === tab.key;
547
- return /*#__PURE__*/_jsx("button", {
548
- onClick: () => setActiveTab(tab.key),
549
- style: {
550
- flex: 1,
551
- paddingLeft: settingsStyles.tab.paddingHorizontal,
552
- paddingRight: settingsStyles.tab.paddingHorizontal,
553
- paddingTop: settingsStyles.tab.paddingVertical,
554
- paddingBottom: settingsStyles.tab.paddingVertical,
555
- borderRadius: settingsStyles.tab.borderRadius,
556
- backgroundColor: isActive ? settingsColors.tabActiveBg : settingsColors.tabInactiveBg,
557
- border: isActive ? `1px solid ${settingsColors.tabActiveBorder}40` : '1px solid transparent',
558
- color: isActive ? settingsColors.info : settingsColors.tabInactiveText,
559
- fontSize: settingsStyles.tab.fontSize,
560
- fontWeight: settingsStyles.tab.fontWeight,
561
- letterSpacing: settingsStyles.tab.letterSpacing,
562
- cursor: 'pointer',
563
- transition: 'all 150ms ease',
564
- textTransform: 'uppercase',
565
- fontFamily: 'monospace'
566
- },
567
- children: tab.label
568
- }, tab.key);
569
- })
570
- }), /*#__PURE__*/_jsx("button", {
571
- "aria-label": "Close",
572
- onClick: handleClose,
573
- style: {
574
- marginLeft: 12,
575
- background: 'none',
576
- border: 'none',
577
- padding: 4,
824
+ marginLeft: 1,
825
+ // native TabSelector tabButton: marginHorizontal 1
826
+ marginRight: 1,
827
+ paddingLeft: settingsStyles.tab.paddingHorizontal,
828
+ paddingRight: settingsStyles.tab.paddingHorizontal,
829
+ paddingTop: 3,
830
+ // native TabSelector tabButton: paddingVertical 3
831
+ paddingBottom: 3,
832
+ borderRadius: settingsStyles.tab.borderRadius,
833
+ // native tabButtonActive: bg primary+20, 1px primary border
834
+ backgroundColor: isActive ? `${buoy.primary}20` : 'transparent',
835
+ border: isActive ? `1px solid ${buoy.primary}` : '1px solid transparent',
836
+ color: isActive ? buoy.primary : buoy.textMuted,
837
+ fontSize: settingsStyles.tab.fontSize,
838
+ fontWeight: settingsStyles.tab.fontWeight,
839
+ letterSpacing: settingsStyles.tab.letterSpacing,
578
840
  cursor: 'pointer',
579
- color: settingsColors.textMuted,
841
+ transition: 'all 150ms ease',
842
+ textTransform: 'uppercase',
843
+ fontFamily: 'inherit',
844
+ // native TabSelector uses the system font
845
+ boxSizing: 'border-box',
580
846
  display: 'flex',
581
847
  alignItems: 'center',
582
848
  justifyContent: 'center'
583
849
  },
584
- children: /*#__PURE__*/_jsx(CloseIcon, {})
585
- })]
586
- }), /*#__PURE__*/_jsx("div", {
850
+ children: tab.label
851
+ }, tab.key);
852
+ })
853
+ })
854
+ });
855
+
856
+ // native: initialHeight = screenHeight / 3, floating starts centered at
857
+ // width min(screenWidth - 32, 400), y = insets.top + 20.
858
+ const modalWidth = Math.min(window.innerWidth - 32, 400);
859
+ const topInset = readTopSafeAreaInset();
860
+ return /*#__PURE__*/_jsx(JsModal, {
861
+ visible: visible,
862
+ onClose: handleClose,
863
+ header: {
864
+ showToggleButton: false,
865
+ customContent: tabHeader
866
+ },
867
+ initialMode: "bottomSheet",
868
+ persistenceKey: devToolsStorageKeys.settings.root(),
869
+ enablePersistence: true,
870
+ initialHeight: Math.floor(window.innerHeight * 0.33),
871
+ initialFloatingPosition: {
872
+ x: (window.innerWidth - modalWidth) / 2,
873
+ y: topInset + 20 // native: insets.top + 20
874
+ },
875
+ zIndex: 10001 // above the dial (10000) — settings opens over it
876
+ ,
877
+ disableScrollWrapper: true,
878
+ children: /*#__PURE__*/_jsx("div", {
879
+ style: {
880
+ flex: 1,
881
+ minHeight: 0,
882
+ overflow: 'auto',
883
+ backgroundColor: buoy.base,
884
+ paddingTop: settingsStyles.scrollContent.paddingTop,
885
+ paddingBottom: settingsStyles.scrollContent.paddingBottom,
886
+ paddingLeft: settingsStyles.section.marginHorizontal,
887
+ paddingRight: settingsStyles.section.marginHorizontal
888
+ },
889
+ children: isLoading ? /*#__PURE__*/_jsx("div", {
587
890
  style: {
588
- flex: 1,
589
- overflow: 'auto',
590
- paddingTop: settingsStyles.scrollContent.paddingTop,
591
- paddingBottom: settingsStyles.scrollContent.paddingBottom,
592
- paddingLeft: settingsStyles.section.marginHorizontal,
593
- paddingRight: settingsStyles.section.marginHorizontal
891
+ display: 'flex',
892
+ alignItems: 'center',
893
+ justifyContent: 'center',
894
+ padding: 40,
895
+ color: buoy.textMuted,
896
+ fontSize: 12
594
897
  },
595
- children: isLoading ? /*#__PURE__*/_jsx("div", {
596
- style: {
597
- display: 'flex',
598
- alignItems: 'center',
599
- justifyContent: 'center',
600
- padding: 40,
601
- color: settingsColors.textMuted
602
- },
603
- children: "Loading settings..."
604
- }) : /*#__PURE__*/_jsxs(_Fragment, {
605
- children: [activeTab === 'floating' && /*#__PURE__*/_jsxs("div", {
606
- children: [/*#__PURE__*/_jsx("div", {
898
+ children: "Loading settings..."
899
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
900
+ children: [activeTab === 'floating' && /*#__PURE__*/_jsx("div", {
901
+ children: Object.entries(settings.floatingTools).map(([id, enabled]) => /*#__PURE__*/_jsx(ToolCard, {
902
+ toolId: id,
903
+ enabled: enabled,
904
+ availableTools: availableTools,
905
+ onToggle: () => id === 'environment' ? actions.toggleEnvironment() : actions.toggleFloatingTool(id)
906
+ }, id))
907
+ }), activeTab === 'settings' && /*#__PURE__*/_jsxs("div", {
908
+ children: [/*#__PURE__*/_jsxs("div", {
909
+ style: {
910
+ backgroundColor: buoy.card,
911
+ borderRadius: 8,
912
+ border: `1px solid ${isStorageExpanded ? `${buoy.success}60` : buoy.border}`,
913
+ padding: 12,
914
+ marginBottom: 10,
915
+ transition: 'border-color 150ms ease'
916
+ },
917
+ children: [/*#__PURE__*/_jsxs("div", {
918
+ onClick: () => setIsStorageExpanded(v => !v),
607
919
  style: {
608
- fontSize: settingsStyles.sectionTitle.fontSize,
609
- fontWeight: settingsStyles.sectionTitle.fontWeight,
610
- letterSpacing: settingsStyles.sectionTitle.letterSpacing,
611
- textTransform: settingsStyles.sectionTitle.textTransform,
612
- color: settingsColors.textMuted,
613
- marginBottom: settingsStyles.sectionHeader.marginBottom
920
+ display: 'flex',
921
+ alignItems: 'center',
922
+ gap: 12,
923
+ marginBottom: 8,
924
+ cursor: 'pointer'
614
925
  },
615
- children: "CONFIGURE FLOATING TOOLBAR ITEMS"
616
- }), Object.entries(settings.floatingTools).map(([id, enabled]) => /*#__PURE__*/_jsx(ToolCard, {
617
- toolId: id,
618
- enabled: enabled,
619
- availableTools: availableTools,
620
- onToggle: () => id === 'environment' ? actions.toggleEnvironment() : actions.toggleFloatingTool(id)
621
- }, id))]
622
- }), activeTab === 'settings' && /*#__PURE__*/_jsxs("div", {
623
- children: [/*#__PURE__*/_jsx("div", {
624
- style: {
625
- fontSize: settingsStyles.sectionTitle.fontSize,
626
- fontWeight: settingsStyles.sectionTitle.fontWeight,
627
- letterSpacing: settingsStyles.sectionTitle.letterSpacing,
628
- textTransform: settingsStyles.sectionTitle.textTransform,
629
- color: settingsColors.textMuted,
630
- marginBottom: settingsStyles.sectionHeader.marginBottom
631
- },
632
- children: "GLOBAL SETTINGS"
633
- }), globalSettingsConfig.map(config => {
634
- const value = settings.globalSettings?.[config.key] ?? false;
635
- const color = settingsColors.success;
636
- return /*#__PURE__*/_jsx("div", {
926
+ children: [/*#__PURE__*/_jsx("div", {
637
927
  style: {
638
- padding: 12,
639
- backgroundColor: settingsColors.cardBackground,
928
+ width: 36,
929
+ height: 36,
640
930
  borderRadius: 8,
641
- border: `1px solid ${settingsColors.cardBorder}`,
642
- marginBottom: 10
931
+ backgroundColor: `${buoy.success}15`,
932
+ display: 'flex',
933
+ alignItems: 'center',
934
+ justifyContent: 'center',
935
+ flexShrink: 0
936
+ },
937
+ children: /*#__PURE__*/_jsx(Database, {
938
+ size: 18,
939
+ color: buoy.success
940
+ })
941
+ }), /*#__PURE__*/_jsxs("div", {
942
+ style: {
943
+ flex: 1,
944
+ minWidth: 0
643
945
  },
644
- children: /*#__PURE__*/_jsxs("div", {
946
+ children: [/*#__PURE__*/_jsx("div", {
947
+ style: {
948
+ fontSize: 10,
949
+ fontWeight: 700,
950
+ color: buoy.textMuted,
951
+ letterSpacing: 1,
952
+ marginBottom: 4
953
+ },
954
+ children: "STORAGE TYPE"
955
+ }), /*#__PURE__*/_jsxs("div", {
645
956
  style: {
646
957
  display: 'flex',
647
958
  alignItems: 'center',
648
- gap: 12
959
+ gap: 6
649
960
  },
650
- children: [/*#__PURE__*/_jsx("div", {
961
+ children: [/*#__PURE__*/_jsx("span", {
651
962
  style: {
652
- backgroundColor: `${settingsColors.info}20`,
653
- border: `1px solid ${settingsColors.info}40`,
963
+ padding: '4px 8px',
654
964
  borderRadius: 4,
655
- padding: '4px 8px'
965
+ border: `1px solid ${buoy.success}60`,
966
+ backgroundColor: `${buoy.success}20`,
967
+ color: buoy.success,
968
+ fontSize: 11,
969
+ fontWeight: 700,
970
+ letterSpacing: 0.5
656
971
  },
657
- children: /*#__PURE__*/_jsx("span", {
658
- style: {
659
- fontSize: 10,
660
- fontWeight: 700,
661
- color: settingsColors.info,
662
- letterSpacing: 0.5
663
- },
664
- children: config.category
665
- })
666
- }), /*#__PURE__*/_jsxs("div", {
667
- style: {
668
- flex: 1
669
- },
670
- children: [/*#__PURE__*/_jsx("div", {
671
- style: {
672
- fontFamily: 'monospace',
673
- fontSize: 12,
674
- fontWeight: 700,
675
- color: settingsColors.text
676
- },
677
- children: config.label
678
- }), /*#__PURE__*/_jsx("div", {
679
- style: {
680
- fontSize: 10,
681
- color: settingsColors.textMuted,
682
- marginTop: 2
683
- },
684
- children: config.shortDescription
685
- })]
686
- }), /*#__PURE__*/_jsx("button", {
687
- onClick: () => actions.toggleGlobalSetting(config.key),
972
+ children: "LOCAL STORAGE"
973
+ }), /*#__PURE__*/_jsx(CheckCircle2, {
974
+ size: 14,
975
+ color: buoy.success
976
+ })]
977
+ })]
978
+ }), isStorageExpanded ? /*#__PURE__*/_jsx(ChevronDownIcon, {
979
+ size: 18,
980
+ color: buoy.textMuted
981
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
982
+ size: 18,
983
+ color: buoy.textMuted
984
+ })]
985
+ }), /*#__PURE__*/_jsx("div", {
986
+ style: {
987
+ fontSize: 11,
988
+ color: buoy.textMuted,
989
+ lineHeight: '16px',
990
+ marginBottom: 12
991
+ },
992
+ children: "Settings persist in the browser's localStorage and survive page reloads."
993
+ }), isStorageExpanded && /*#__PURE__*/_jsxs("div", {
994
+ style: {
995
+ marginTop: 12,
996
+ paddingTop: 12,
997
+ borderTop: `1px solid ${buoy.border}50`,
998
+ marginBottom: 12
999
+ },
1000
+ children: [/*#__PURE__*/_jsxs("div", {
1001
+ style: {
1002
+ display: 'flex',
1003
+ alignItems: 'center',
1004
+ gap: 8,
1005
+ marginBottom: 10
1006
+ },
1007
+ children: [/*#__PURE__*/_jsx(FileText, {
1008
+ size: 14,
1009
+ color: buoy.primary
1010
+ }), /*#__PURE__*/_jsx("span", {
1011
+ style: {
1012
+ fontSize: 10,
1013
+ fontWeight: 700,
1014
+ color: buoy.primary,
1015
+ letterSpacing: 1,
1016
+ flex: 1
1017
+ },
1018
+ children: "SAVED SETTINGS"
1019
+ }), /*#__PURE__*/_jsxs("span", {
1020
+ style: {
1021
+ fontSize: 10,
1022
+ color: buoy.textMuted
1023
+ },
1024
+ children: [savedKeys.length, " keys"]
1025
+ })]
1026
+ }), savedKeys.length === 0 ? /*#__PURE__*/_jsx("div", {
1027
+ style: {
1028
+ fontSize: 11,
1029
+ color: buoy.textMuted,
1030
+ fontStyle: 'italic',
1031
+ padding: '8px 0'
1032
+ },
1033
+ children: "No settings saved yet"
1034
+ }) : /*#__PURE__*/_jsx("div", {
1035
+ style: {
1036
+ maxHeight: 150,
1037
+ overflow: 'auto'
1038
+ },
1039
+ children: savedKeys.map(key => /*#__PURE__*/_jsx("div", {
1040
+ style: {
1041
+ backgroundColor: buoy.hover,
1042
+ borderRadius: 4,
1043
+ padding: '6px 8px',
1044
+ border: `1px solid ${buoy.border}50`,
1045
+ marginBottom: 4
1046
+ },
1047
+ children: /*#__PURE__*/_jsx("div", {
688
1048
  style: {
689
- paddingLeft: settingsStyles.toggle.paddingHorizontal,
690
- paddingRight: settingsStyles.toggle.paddingHorizontal,
691
- paddingTop: settingsStyles.toggle.paddingVertical,
692
- paddingBottom: settingsStyles.toggle.paddingVertical,
693
- borderRadius: settingsStyles.toggle.borderRadius,
694
- borderWidth: settingsStyles.toggle.borderWidth,
695
- borderStyle: 'solid',
696
- backgroundColor: value ? settingsColors.toggleOnBg(color) : settingsColors.toggleOffBg,
697
- borderColor: value ? settingsColors.toggleOnBorder(color) : settingsColors.toggleOffBorder,
698
- color: value ? color : settingsColors.toggleOffText,
699
- fontSize: settingsStyles.toggle.fontSize,
700
- fontWeight: settingsStyles.toggle.fontWeight,
701
- cursor: 'pointer',
702
- transition: 'all 150ms ease',
703
- boxShadow: value ? `0 0 8px ${color}66` : 'none'
1049
+ fontSize: 11,
1050
+ color: buoy.textSecondary,
1051
+ fontFamily: 'monospace',
1052
+ overflow: 'hidden',
1053
+ textOverflow: 'ellipsis',
1054
+ whiteSpace: 'nowrap'
704
1055
  },
705
- children: value ? 'ON' : 'OFF'
706
- })]
707
- })
708
- }, config.key);
709
- }), /*#__PURE__*/_jsx("button", {
710
- onClick: () => actions.resetToDefaults(),
1056
+ children: key.replace('@react_buoy_', '')
1057
+ })
1058
+ }, key))
1059
+ })]
1060
+ }), /*#__PURE__*/_jsxs("button", {
1061
+ onClick: handleClearAll,
711
1062
  style: {
712
1063
  width: '100%',
713
- padding: '12px 16px',
714
- marginTop: 8,
1064
+ display: 'flex',
1065
+ alignItems: 'center',
1066
+ justifyContent: 'center',
1067
+ gap: 8,
1068
+ padding: '10px 16px',
715
1069
  borderRadius: 6,
716
- border: `1px solid ${settingsColors.error}40`,
717
- backgroundColor: `${settingsColors.error}15`,
718
- color: settingsColors.error,
1070
+ border: `1px solid ${clearSuccess ? `${buoy.success}40` : `${buoy.error}40`}`,
1071
+ backgroundColor: clearSuccess ? `${buoy.success}15` : `${buoy.error}15`,
1072
+ color: clearSuccess ? buoy.success : buoy.error,
719
1073
  fontSize: 12,
720
1074
  fontWeight: 700,
721
1075
  letterSpacing: 0.5,
1076
+ fontFamily: 'inherit',
722
1077
  cursor: 'pointer',
723
1078
  transition: 'all 150ms ease'
724
1079
  },
725
- children: "CLEAR ALL SETTINGS"
1080
+ children: [clearSuccess ? /*#__PURE__*/_jsx(CheckCircle2, {
1081
+ size: 14,
1082
+ color: buoy.success
1083
+ }) : /*#__PURE__*/_jsx(Trash2, {
1084
+ size: 14,
1085
+ color: buoy.error
1086
+ }), clearSuccess ? 'CLEARED' : 'CLEAR ALL SETTINGS']
726
1087
  })]
727
- })]
728
- })
729
- })]
730
- })]
1088
+ }), globalSettingsConfig.map(config => /*#__PURE__*/_jsx(GlobalSettingCard, {
1089
+ configKey: config.key,
1090
+ label: config.label,
1091
+ category: config.category,
1092
+ shortDescription: config.shortDescription,
1093
+ fullDescription: config.fullDescription,
1094
+ recommendation: config.recommendation,
1095
+ value: settings.globalSettings?.[config.key] ?? false,
1096
+ onToggle: () => actions.toggleGlobalSetting(config.key)
1097
+ }, config.key))]
1098
+ })]
1099
+ })
1100
+ })
731
1101
  });
732
1102
  }