@teo-garcia/react-shared 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +138 -136
  2. package/dist/components/client-only/client-only.d.ts +11 -0
  3. package/dist/components/client-only/client-only.d.ts.map +1 -0
  4. package/dist/components/client-only/client-only.js +17 -0
  5. package/dist/components/client-only/index.d.ts +2 -0
  6. package/dist/components/client-only/index.d.ts.map +1 -0
  7. package/dist/components/client-only/index.js +1 -0
  8. package/dist/components/dev-panel/dev-panel-features.d.ts +10 -0
  9. package/dist/components/dev-panel/dev-panel-features.d.ts.map +1 -0
  10. package/dist/components/dev-panel/dev-panel-features.js +29 -0
  11. package/dist/components/dev-panel/dev-panel.d.ts +15 -20
  12. package/dist/components/dev-panel/dev-panel.d.ts.map +1 -1
  13. package/dist/components/dev-panel/dev-panel.js +627 -121
  14. package/dist/components/dev-panel/index.d.ts +1 -1
  15. package/dist/components/dev-panel/index.d.ts.map +1 -1
  16. package/dist/components/dev-panel/index.js +1 -1
  17. package/dist/components/dev-panel/use-dev-panel-diagnostics.d.ts +39 -0
  18. package/dist/components/dev-panel/use-dev-panel-diagnostics.d.ts.map +1 -0
  19. package/dist/components/dev-panel/use-dev-panel-diagnostics.js +384 -0
  20. package/dist/components/index.d.ts +1 -0
  21. package/dist/components/index.d.ts.map +1 -1
  22. package/dist/components/index.js +1 -0
  23. package/dist/components/separator/separator.d.ts +4 -4
  24. package/dist/components/separator/separator.d.ts.map +1 -1
  25. package/dist/components/separator/separator.js +18 -6
  26. package/dist/components/skeleton/skeleton.d.ts +1 -1
  27. package/dist/components/skeleton/skeleton.d.ts.map +1 -1
  28. package/dist/components/skeleton/skeleton.js +19 -4
  29. package/dist/components/skip-link/skip-link.d.ts.map +1 -1
  30. package/dist/components/skip-link/skip-link.js +8 -5
  31. package/dist/hooks/index.d.ts +1 -0
  32. package/dist/hooks/index.d.ts.map +1 -1
  33. package/dist/hooks/index.js +1 -0
  34. package/dist/hooks/use-breakpoint.d.ts +9 -0
  35. package/dist/hooks/use-breakpoint.d.ts.map +1 -0
  36. package/dist/hooks/use-breakpoint.js +47 -0
  37. package/dist/index.d.ts +2 -7
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -7
  40. package/package.json +46 -1
@@ -1,27 +1,12 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useState } from 'react';
4
- const DEFAULT_BREAKPOINTS = {
5
- sm: 640,
6
- md: 768,
7
- lg: 1024,
8
- xl: 1280,
9
- '2xl': 1536,
10
- };
11
- const POSITION_STYLES = {
12
- 'top-left': { top: '0.75rem', left: '0.75rem' },
13
- 'top-right': { top: '0.75rem', right: '0.75rem' },
14
- 'bottom-left': { bottom: '0.75rem', left: '0.75rem' },
15
- 'bottom-right': { bottom: '0.75rem', right: '0.75rem' },
16
- };
17
- function resolveBreakpoint(width, breakpoints) {
18
- const sorted = Object.entries(breakpoints).sort(([, a], [, b]) => b - a);
19
- for (const [name, min] of sorted) {
20
- if (width >= min)
21
- return name;
22
- }
23
- return 'xs';
24
- }
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState, } from 'react';
4
+ import { DEFAULT_BREAKPOINTS, useBreakpoint, } from '../../hooks/use-breakpoint.js';
5
+ import { ALL_DEV_PANEL_FEATURES, } from './dev-panel-features.js';
6
+ import { devPanelFeaturesKey, useDevPanelDiagnostics, } from './use-dev-panel-diagnostics.js';
7
+ export { ALL_DEV_PANEL_FEATURES, } from './dev-panel-features.js';
8
+ const OUTLINE_STYLE_ID = 'react-shared-dev-panel-outline-style';
9
+ const GRID_STYLE_ID = 'react-shared-dev-panel-grid-style';
25
10
  function matchShortcut(e, shortcut) {
26
11
  const parts = shortcut.toLowerCase().split('+');
27
12
  const key = parts.at(-1) ?? '';
@@ -31,122 +16,643 @@ function matchShortcut(e, shortcut) {
31
16
  e.ctrlKey === parts.includes('ctrl') &&
32
17
  e.altKey === parts.includes('alt'));
33
18
  }
34
- function DevPanelInner({ breakpoints = DEFAULT_BREAKPOINTS, children, position = 'bottom-left', shortcut = 'shift+d', }) {
19
+ function formatShortcutLabel(shortcut) {
20
+ return shortcut
21
+ .split('+')
22
+ .map((part) => part.length <= 1
23
+ ? part.toUpperCase()
24
+ : part[0].toUpperCase() + part.slice(1))
25
+ .join(' + ');
26
+ }
27
+ function createThemeStyles(theme) {
28
+ if (theme === 'dark') {
29
+ return {
30
+ accent: '#2563eb',
31
+ badgeBackground: 'rgba(15, 23, 42, 0.08)',
32
+ badgeText: '#0f172a',
33
+ border: 'rgba(15, 23, 42, 0.12)',
34
+ chipBg: 'rgba(15, 23, 42, 0.06)',
35
+ collapsedShadow: '0 8px 32px rgba(15, 23, 42, 0.14), 0 2px 8px rgba(15, 23, 42, 0.08), inset 0 1px 0 rgba(255,255,255,0.75)',
36
+ mutedText: '#64748b',
37
+ panelBackground: 'rgba(255, 255, 255, 0.94)',
38
+ panelShadow: '0 22px 48px rgba(15, 23, 42, 0.18), 0 8px 16px rgba(15, 23, 42, 0.08), inset 0 1px 0 rgba(255,255,255,0.78)',
39
+ rowDivider: 'rgba(15, 23, 42, 0.07)',
40
+ strongText: '#0f172a',
41
+ subtleText: '#475569',
42
+ toggleActive: 'rgba(37, 99, 235, 0.2)',
43
+ toggleIdle: 'rgba(15, 23, 42, 0.06)',
44
+ };
45
+ }
46
+ return {
47
+ accent: '#7dd3fc',
48
+ badgeBackground: 'rgba(255, 255, 255, 0.12)',
49
+ badgeText: '#f8fafc',
50
+ border: 'rgba(255, 255, 255, 0.16)',
51
+ chipBg: 'rgba(255, 255, 255, 0.06)',
52
+ collapsedShadow: '0 10px 40px rgba(0, 0, 0, 0.45), 0 2px 12px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255,255,255,0.06)',
53
+ mutedText: '#94a3b8',
54
+ panelBackground: 'rgba(2, 6, 23, 0.94)',
55
+ panelShadow: '0 24px 56px rgba(0, 0, 0, 0.55), 0 10px 24px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255,255,255,0.05)',
56
+ rowDivider: 'rgba(255, 255, 255, 0.08)',
57
+ strongText: '#f8fafc',
58
+ subtleText: '#cbd5e1',
59
+ toggleActive: 'rgba(125, 211, 252, 0.2)',
60
+ toggleIdle: 'rgba(255, 255, 255, 0.08)',
61
+ };
62
+ }
63
+ const PANEL_BASE = {
64
+ position: 'fixed',
65
+ bottom: '1rem',
66
+ left: '50%',
67
+ transform: 'translateX(-50%)',
68
+ zIndex: 9999,
69
+ backdropFilter: 'blur(16px)',
70
+ WebkitBackdropFilter: 'blur(16px)',
71
+ boxSizing: 'border-box',
72
+ lineHeight: 1.35,
73
+ maxWidth: 'min(100vw - 1.5rem, 36rem)',
74
+ userSelect: 'none',
75
+ };
76
+ const MONO = 'ui-monospace, "SFMono-Regular", "Cascadia Code", "Fira Code", monospace';
77
+ const SANS = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
78
+ /** Core readout and table sizing (slightly larger for legibility). */
79
+ const FS = {
80
+ core: '0.9375rem',
81
+ footer: '0.6875rem',
82
+ kvLabel: '0.78125rem',
83
+ kvValue: '0.8125rem',
84
+ section: '0.6875rem',
85
+ toolbar: '0.8125rem',
86
+ };
87
+ function CollapseChevron({ expanded, themeStyles, }) {
88
+ return (_jsx("span", { "aria-hidden": true, style: {
89
+ color: themeStyles.mutedText,
90
+ display: 'inline-flex',
91
+ lineHeight: 1,
92
+ transform: expanded ? 'rotate(0deg)' : 'rotate(-180deg)',
93
+ transition: 'transform 0.22s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s ease',
94
+ }, children: _jsx("span", { style: { fontSize: '0.7rem', position: 'relative', top: '0.06em' }, children: "\u25BC" }) }));
95
+ }
96
+ function DevPanelInner({ breakpoints = DEFAULT_BREAKPOINTS, children, features = [...ALL_DEV_PANEL_FEATURES], items = [], layout = 'inspector', shortcut = 'shift+d', storageKey = 'react-shared-dev-panel', }) {
97
+ const { breakpoint, height, width } = useBreakpoint(breakpoints);
35
98
  const [open, setOpen] = useState(true);
36
- const [viewport, setViewport] = useState({ w: 0, h: 0 });
37
- const [dark, setDark] = useState(false);
99
+ const [outlineOn, setOutlineOn] = useState(false);
100
+ const [gridOn, setGridOn] = useState(false);
101
+ const diagnostics = useDevPanelDiagnostics(features);
102
+ const theme = diagnostics.resolvedTheme;
103
+ const themeStyles = createThemeStyles(theme);
104
+ const featureKey = devPanelFeaturesKey(features);
105
+ const featureSet = useMemo(() => new Set(features), [featureKey]);
106
+ useEffect(() => {
107
+ if (typeof document === 'undefined')
108
+ return;
109
+ if (!featureSet.has('outline')) {
110
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-outline');
111
+ document.getElementById(OUTLINE_STYLE_ID)?.remove();
112
+ return;
113
+ }
114
+ let styleEl = document.getElementById(OUTLINE_STYLE_ID);
115
+ if (!styleEl) {
116
+ styleEl = document.createElement('style');
117
+ styleEl.id = OUTLINE_STYLE_ID;
118
+ styleEl.textContent = `
119
+ html[data-react-shared-dev-panel-outline] * {
120
+ outline: 1px solid rgba(239, 68, 68, 0.42) !important;
121
+ outline-offset: -1px !important;
122
+ }
123
+ `;
124
+ document.head.appendChild(styleEl);
125
+ }
126
+ if (outlineOn) {
127
+ document.documentElement.setAttribute('data-react-shared-dev-panel-outline', '');
128
+ }
129
+ else {
130
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-outline');
131
+ }
132
+ return () => {
133
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-outline');
134
+ };
135
+ }, [featureSet, outlineOn]);
38
136
  useEffect(() => {
39
- function update() {
40
- setViewport({ w: window.innerWidth, h: window.innerHeight });
41
- setDark(document.documentElement.classList.contains('dark') ||
42
- window.matchMedia('(prefers-color-scheme: dark)').matches);
43
- }
44
- update();
45
- window.addEventListener('resize', update);
46
- // Watch for class changes on <html> (Tailwind dark mode toggle)
47
- const observer = new MutationObserver(update);
48
- observer.observe(document.documentElement, {
49
- attributes: true,
50
- attributeFilter: ['class'],
51
- });
137
+ if (typeof document === 'undefined')
138
+ return;
139
+ if (!featureSet.has('grid')) {
140
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-grid');
141
+ document.getElementById(GRID_STYLE_ID)?.remove();
142
+ return;
143
+ }
144
+ let styleEl = document.getElementById(GRID_STYLE_ID);
145
+ if (!styleEl) {
146
+ styleEl = document.createElement('style');
147
+ styleEl.id = GRID_STYLE_ID;
148
+ styleEl.textContent = `
149
+ html[data-react-shared-dev-panel-grid]::before {
150
+ content: "";
151
+ position: fixed;
152
+ inset: 0;
153
+ pointer-events: none;
154
+ z-index: 2147483645;
155
+ background-size: 8px 8px;
156
+ background-image:
157
+ linear-gradient(to right, rgba(148, 163, 184, 0.14) 1px, transparent 1px),
158
+ linear-gradient(to bottom, rgba(148, 163, 184, 0.14) 1px, transparent 1px);
159
+ }
160
+ `;
161
+ document.head.appendChild(styleEl);
162
+ }
163
+ if (gridOn) {
164
+ document.documentElement.setAttribute('data-react-shared-dev-panel-grid', '');
165
+ }
166
+ else {
167
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-grid');
168
+ }
52
169
  return () => {
53
- window.removeEventListener('resize', update);
54
- observer.disconnect();
170
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-grid');
55
171
  };
56
- }, []);
172
+ }, [featureSet, gridOn]);
173
+ useEffect(() => {
174
+ if (typeof window === 'undefined')
175
+ return;
176
+ const saved = window.localStorage.getItem(storageKey);
177
+ if (saved === 'open' || saved === 'closed') {
178
+ setOpen(saved === 'open');
179
+ }
180
+ }, [storageKey]);
181
+ useEffect(() => {
182
+ if (typeof window === 'undefined')
183
+ return;
184
+ window.localStorage.setItem(storageKey, open ? 'open' : 'closed');
185
+ }, [open, storageKey]);
57
186
  useEffect(() => {
58
187
  function handleKey(e) {
59
188
  if (matchShortcut(e, shortcut))
60
- setOpen((v) => !v);
189
+ setOpen((value) => !value);
61
190
  }
62
191
  window.addEventListener('keydown', handleKey);
63
192
  return () => window.removeEventListener('keydown', handleKey);
64
193
  }, [shortcut]);
65
- const allBreakpoints = [
66
- ['xs', 0],
67
- ...Object.entries(breakpoints).sort(([, a], [, b]) => a - b),
68
- ];
69
- const current = resolveBreakpoint(viewport.w, breakpoints);
70
- const base = {
71
- position: 'fixed',
72
- ...POSITION_STYLES[position],
73
- zIndex: 9999,
74
- fontFamily: 'ui-monospace, monospace',
75
- fontSize: '0.72rem',
76
- backdropFilter: 'blur(10px)',
77
- WebkitBackdropFilter: 'blur(10px)',
194
+ const coreReadoutStyle = {
195
+ color: themeStyles.strongText,
196
+ fontFamily: MONO,
197
+ fontSize: FS.core,
198
+ fontVariantNumeric: 'tabular-nums',
199
+ fontWeight: 600,
200
+ };
201
+ const bpPillStyle = {
202
+ ...coreReadoutStyle,
203
+ background: themeStyles.badgeBackground,
204
+ borderRadius: '9999px',
205
+ color: themeStyles.badgeText,
206
+ letterSpacing: '0.02em',
207
+ padding: '0.28rem 0.55rem',
208
+ };
209
+ const themeTextStyle = {
210
+ color: themeStyles.subtleText,
211
+ fontFamily: SANS,
212
+ fontSize: FS.core,
213
+ fontWeight: 600,
214
+ letterSpacing: '0.04em',
215
+ textTransform: 'lowercase',
78
216
  };
217
+ const dot = (_jsx("span", { "aria-hidden": true, style: {
218
+ color: themeStyles.accent,
219
+ flexShrink: 0,
220
+ fontSize: '0.55rem',
221
+ lineHeight: 1,
222
+ marginRight: '0.15rem',
223
+ }, children: "\u25CF" }));
224
+ const coreStrip = (_jsxs(_Fragment, { children: [dot, _jsx("span", { style: coreReadoutStyle, title: 'innerWidth x innerHeight (CSS px)', children: `${width}\u00d7${height}` }), _jsx("span", { style: bpPillStyle, title: 'Active min-width key from breakpoints prop', children: breakpoint }), _jsx("span", { style: themeTextStyle, title: 'Resolved app / system theme', children: theme })] }));
225
+ function buildSections(d) {
226
+ const sections = [];
227
+ const layoutRows = [];
228
+ if (featureSet.has('scroll')) {
229
+ layoutRows.push({
230
+ key: 'scroll',
231
+ label: 'scroll',
232
+ title: 'window.scrollY and documentElement.scrollHeight',
233
+ value: `${d.scrollY} / ${d.scrollHeight}`,
234
+ });
235
+ }
236
+ if (featureSet.has('safeArea')) {
237
+ const sum = d.safeTop + d.safeRight + d.safeBottom + d.safeLeft;
238
+ layoutRows.push({
239
+ key: 'safe',
240
+ label: 'safe area',
241
+ title: 'env(safe-area-inset-*)',
242
+ value: sum === 0
243
+ ? 'none'
244
+ : `${d.safeTop} ${d.safeRight} ${d.safeBottom} ${d.safeLeft}`,
245
+ });
246
+ }
247
+ if (featureSet.has('visualViewport')) {
248
+ layoutRows.push({
249
+ key: 'vvp',
250
+ label: 'visual vp',
251
+ title: 'visualViewport dimensions and scale',
252
+ value: `${d.vvWidth}\u00d7${d.vvHeight} @${d.vvScale}`,
253
+ });
254
+ }
255
+ if (featureSet.has('scrollbar')) {
256
+ layoutRows.push({
257
+ key: 'sb',
258
+ label: 'scrollbar',
259
+ title: 'innerWidth minus clientWidth',
260
+ value: String(d.scrollbarWidth),
261
+ });
262
+ }
263
+ if (featureSet.has('orientation')) {
264
+ layoutRows.push({
265
+ key: 'orient',
266
+ label: 'orient',
267
+ title: 'screen.orientation or aspect',
268
+ value: d.orientation,
269
+ });
270
+ }
271
+ if (layoutRows.length)
272
+ sections.push({ rows: layoutRows, title: 'Layout' });
273
+ const inputRows = [];
274
+ if (featureSet.has('media')) {
275
+ inputRows.push({
276
+ key: 'motion',
277
+ label: 'motion',
278
+ title: 'prefers-reduced-motion',
279
+ value: d.reducedMotion ? 'reduce' : 'ok',
280
+ }, {
281
+ key: 'pointer',
282
+ label: 'pointer',
283
+ title: 'pointer: coarse',
284
+ value: d.pointerCoarse ? 'coarse' : 'fine',
285
+ }, {
286
+ key: 'hover',
287
+ label: 'hover',
288
+ title: 'hover: hover',
289
+ value: d.canHover ? 'yes' : 'no',
290
+ });
291
+ }
292
+ if (featureSet.has('dpr')) {
293
+ inputRows.push({
294
+ key: 'dpr',
295
+ label: 'dpr',
296
+ title: 'devicePixelRatio',
297
+ value: String(d.dpr),
298
+ });
299
+ }
300
+ if (inputRows.length)
301
+ sections.push({ rows: inputRows, title: 'Input' });
302
+ const displayRows = [];
303
+ if (featureSet.has('colorScheme')) {
304
+ displayRows.push({
305
+ key: 'scheme',
306
+ label: 'scheme',
307
+ title: 'prefers-color-scheme vs resolved theme',
308
+ value: `${d.prefersColorScheme} \u2192 ${d.resolvedTheme}`,
309
+ });
310
+ }
311
+ if (featureSet.has('contrast')) {
312
+ displayRows.push({
313
+ key: 'contrast',
314
+ label: 'contrast',
315
+ title: 'prefers-contrast',
316
+ value: d.contrast,
317
+ });
318
+ }
319
+ if (featureSet.has('reducedTransparency')) {
320
+ displayRows.push({
321
+ key: 'transparency',
322
+ label: 'transparency',
323
+ title: 'prefers-reduced-transparency',
324
+ value: d.reducedTransparency ? 'reduce' : 'ok',
325
+ });
326
+ }
327
+ if (featureSet.has('inverted')) {
328
+ displayRows.push({
329
+ key: 'invert',
330
+ label: 'invert',
331
+ title: 'inverted-colors',
332
+ value: d.invertedColors ? 'yes' : 'no',
333
+ });
334
+ }
335
+ if (displayRows.length)
336
+ sections.push({ rows: displayRows, title: 'Display' });
337
+ const networkRows = [];
338
+ if (featureSet.has('connection')) {
339
+ networkRows.push({
340
+ key: 'conn',
341
+ label: 'connection',
342
+ title: 'Network Information API (effectiveType, downlink, rtt)',
343
+ value: d.connectionSummary || 'unavailable',
344
+ });
345
+ }
346
+ if (featureSet.has('saveData')) {
347
+ networkRows.push({
348
+ key: 'savedata',
349
+ label: 'save data',
350
+ title: 'prefers-reduced-data',
351
+ value: d.saveDataReduced ? 'reduce' : 'no',
352
+ });
353
+ }
354
+ if (networkRows.length)
355
+ sections.push({ rows: networkRows, title: 'Network' });
356
+ const runtimeRows = [];
357
+ if (featureSet.has('online')) {
358
+ runtimeRows.push({
359
+ key: 'online',
360
+ label: 'online',
361
+ title: 'navigator.onLine',
362
+ value: d.online ? 'yes' : 'no',
363
+ });
364
+ }
365
+ if (featureSet.has('visibility')) {
366
+ runtimeRows.push({
367
+ key: 'visible',
368
+ label: 'visible',
369
+ title: 'document.visibilityState',
370
+ value: d.visibility,
371
+ });
372
+ }
373
+ if (featureSet.has('fullscreen')) {
374
+ runtimeRows.push({
375
+ key: 'fullscreen',
376
+ label: 'fullscreen',
377
+ title: 'document.fullscreenElement',
378
+ value: d.fullscreen ? 'yes' : 'no',
379
+ });
380
+ }
381
+ if (featureSet.has('displayMode')) {
382
+ runtimeRows.push({
383
+ key: 'standalone',
384
+ label: 'standalone',
385
+ title: 'display-mode: standalone or navigator.standalone',
386
+ value: d.displayStandalone ? 'yes' : 'no',
387
+ });
388
+ }
389
+ if (runtimeRows.length)
390
+ sections.push({ rows: runtimeRows, title: 'Runtime' });
391
+ const localeRows = [];
392
+ if (featureSet.has('locale')) {
393
+ localeRows.push({
394
+ key: 'locale',
395
+ label: 'locale',
396
+ title: 'navigator.language',
397
+ value: d.locale || 'unknown',
398
+ }, {
399
+ key: 'tz',
400
+ label: 'timezone',
401
+ title: 'Intl time zone',
402
+ value: d.timeZone || 'unknown',
403
+ });
404
+ }
405
+ if (localeRows.length)
406
+ sections.push({ rows: localeRows, title: 'Locale' });
407
+ if (featureSet.has('focus')) {
408
+ sections.push({
409
+ rows: [
410
+ {
411
+ key: 'focus',
412
+ label: 'focus',
413
+ title: 'document.activeElement (tag, id, classes)',
414
+ value: d.focusPeek || '—',
415
+ },
416
+ ],
417
+ title: 'DOM',
418
+ });
419
+ }
420
+ const perfRows = [];
421
+ if (featureSet.has('perf')) {
422
+ perfRows.push({
423
+ key: 'fps',
424
+ label: 'fps',
425
+ title: 'Estimated frames per second (rAF)',
426
+ value: d.fpsSampled ? String(d.fps) : '…',
427
+ });
428
+ }
429
+ if (featureSet.has('memory')) {
430
+ perfRows.push({
431
+ key: 'heap',
432
+ label: 'heap',
433
+ title: 'Chrome performance.memory used (MB)',
434
+ value: d.heapUsedMb == null ? 'n/a' : `${d.heapUsedMb} MB`,
435
+ });
436
+ }
437
+ if (perfRows.length)
438
+ sections.push({ rows: perfRows, title: 'Performance' });
439
+ const appRows = items.map((item, i) => ({
440
+ key: `app-${i}`,
441
+ label: item.label,
442
+ title: item.title ?? '',
443
+ value: item.value,
444
+ }));
445
+ if (appRows.length)
446
+ sections.push({ rows: appRows, title: 'App' });
447
+ return sections;
448
+ }
449
+ const sections = buildSections(diagnostics);
450
+ const hasToolbar = featureSet.has('outline') || featureSet.has('grid');
451
+ const hasDetails = sections.length > 0 || children != null || hasToolbar;
452
+ const shortcutHint = formatShortcutLabel(shortcut);
453
+ function renderKvRow(row) {
454
+ return (_jsxs("div", { title: row.title, style: {
455
+ alignItems: 'baseline',
456
+ borderBottom: `1px solid ${themeStyles.rowDivider}`,
457
+ columnGap: '1rem',
458
+ display: 'grid',
459
+ gridTemplateColumns: 'minmax(6.5rem, 10rem) minmax(0, 1fr)',
460
+ padding: '0.32rem 0',
461
+ }, children: [_jsx("span", { style: {
462
+ color: themeStyles.mutedText,
463
+ fontFamily: SANS,
464
+ fontSize: FS.kvLabel,
465
+ fontWeight: 600,
466
+ lineHeight: 1.35,
467
+ textAlign: 'left',
468
+ textTransform: 'lowercase',
469
+ }, children: row.label }), _jsx("span", { style: {
470
+ color: themeStyles.strongText,
471
+ fontFamily: MONO,
472
+ fontSize: FS.kvValue,
473
+ fontVariantNumeric: 'tabular-nums',
474
+ fontWeight: 600,
475
+ lineHeight: 1.4,
476
+ overflowWrap: 'anywhere',
477
+ textAlign: 'right',
478
+ wordBreak: 'break-word',
479
+ }, children: row.value })] }, row.key));
480
+ }
481
+ function renderSection(section, index) {
482
+ return (_jsxs("div", { style: {
483
+ marginTop: index === 0 ? 0 : '0.55rem',
484
+ paddingTop: index === 0 ? 0 : '0.45rem',
485
+ borderTop: index === 0 ? 'none' : `1px solid ${themeStyles.border}`,
486
+ }, children: [_jsx("div", { style: {
487
+ color: themeStyles.mutedText,
488
+ fontFamily: SANS,
489
+ fontSize: FS.section,
490
+ fontWeight: 700,
491
+ letterSpacing: '0.1em',
492
+ marginBottom: '0.4rem',
493
+ textTransform: 'uppercase',
494
+ }, children: section.title }), section.rows.map((row) => renderKvRow(row))] }, section.title));
495
+ }
496
+ function renderToolbar() {
497
+ if (!hasToolbar)
498
+ return null;
499
+ const btn = {
500
+ background: themeStyles.toggleIdle,
501
+ border: `1px solid ${themeStyles.border}`,
502
+ borderRadius: '10px',
503
+ color: themeStyles.subtleText,
504
+ cursor: 'pointer',
505
+ fontFamily: SANS,
506
+ fontSize: FS.toolbar,
507
+ fontWeight: 600,
508
+ padding: '0.4rem 0.75rem',
509
+ };
510
+ return (_jsxs("div", { style: {
511
+ borderTop: `1px solid ${themeStyles.border}`,
512
+ display: 'flex',
513
+ flexWrap: 'wrap',
514
+ gap: '0.45rem',
515
+ marginTop: sections.length > 0 ? '0.5rem' : 0,
516
+ paddingTop: '0.5rem',
517
+ }, children: [_jsx("span", { style: {
518
+ alignSelf: 'center',
519
+ color: themeStyles.mutedText,
520
+ fontFamily: SANS,
521
+ fontSize: FS.section,
522
+ fontWeight: 700,
523
+ letterSpacing: '0.1em',
524
+ marginRight: '0.15rem',
525
+ textTransform: 'uppercase',
526
+ }, children: "Debug" }), featureSet.has('outline') ? (_jsxs("button", { "aria-pressed": outlineOn, onClick: () => setOutlineOn((v) => !v), style: {
527
+ ...btn,
528
+ background: outlineOn
529
+ ? themeStyles.toggleActive
530
+ : themeStyles.toggleIdle,
531
+ }, title: 'Outline every element', type: 'button', children: ["Outline ", outlineOn ? 'on' : 'off'] })) : null, featureSet.has('grid') ? (_jsxs("button", { "aria-pressed": gridOn, onClick: () => setGridOn((v) => !v), style: {
532
+ ...btn,
533
+ background: gridOn
534
+ ? themeStyles.toggleActive
535
+ : themeStyles.toggleIdle,
536
+ }, title: '8px grid overlay', type: 'button', children: ["Grid ", gridOn ? 'on' : 'off'] })) : null] }));
537
+ }
538
+ const scrollMaxH = layout === 'hud' ? 'min(28vh, 12rem)' : 'min(42vh, 22rem)';
539
+ const expandedRadius = layout === 'hud' ? '9999px' : '16px';
540
+ const detailBlock = open && hasDetails ? (_jsxs("div", { style: {
541
+ marginTop: '0.35rem',
542
+ maxHeight: scrollMaxH,
543
+ overflowX: 'hidden',
544
+ overflowY: 'auto',
545
+ paddingRight: '0.15rem',
546
+ }, children: [sections.map((s, i) => renderSection(s, i)), children ? (_jsxs("div", { style: {
547
+ borderTop: sections.length > 0
548
+ ? `1px solid ${themeStyles.border}`
549
+ : 'none',
550
+ marginTop: sections.length > 0 ? '0.55rem' : 0,
551
+ paddingTop: sections.length > 0 ? '0.45rem' : 0,
552
+ }, children: [_jsx("div", { style: {
553
+ color: themeStyles.mutedText,
554
+ fontFamily: SANS,
555
+ fontSize: FS.section,
556
+ fontWeight: 700,
557
+ letterSpacing: '0.1em',
558
+ marginBottom: '0.35rem',
559
+ textTransform: 'uppercase',
560
+ }, children: "Slot" }), _jsx("div", { style: {
561
+ color: themeStyles.subtleText,
562
+ fontFamily: SANS,
563
+ fontSize: FS.kvValue,
564
+ fontWeight: 600,
565
+ }, children: children })] })) : null, renderToolbar()] })) : null;
566
+ const headerRow = (_jsx("div", { style: {
567
+ alignItems: 'center',
568
+ borderBottom: open && hasDetails ? `1px solid ${themeStyles.border}` : 'none',
569
+ display: 'flex',
570
+ flexWrap: layout === 'hud' ? 'nowrap' : 'wrap',
571
+ gap: '0.5rem',
572
+ justifyContent: 'space-between',
573
+ minHeight: '2.35rem',
574
+ paddingBottom: open && hasDetails ? '0.5rem' : 0,
575
+ }, children: _jsx("div", { style: {
576
+ alignItems: 'center',
577
+ display: 'flex',
578
+ flexWrap: 'wrap',
579
+ gap: '0.5rem',
580
+ minWidth: 0,
581
+ }, children: coreStrip }) }));
79
582
  if (!open) {
80
- return (_jsx("button", { onClick: () => setOpen(true), title: `Dev Panel (${shortcut})`, style: {
81
- ...base,
82
- background: 'rgba(15,15,15,0.88)',
83
- color: '#06b6d4',
84
- border: '1px solid rgba(255,255,255,0.08)',
85
- borderRadius: '6px',
86
- padding: '0.2rem 0.5rem',
583
+ return (_jsxs("div", { onClick: () => setOpen(true), onKeyDown: (e) => {
584
+ if (e.key === 'Enter' || e.key === ' ') {
585
+ e.preventDefault();
586
+ setOpen(true);
587
+ }
588
+ }, role: 'button', tabIndex: 0, title: `Dev panel (${shortcut})`, style: {
589
+ ...PANEL_BASE,
590
+ alignItems: 'center',
591
+ background: themeStyles.panelBackground,
592
+ border: `1px solid ${themeStyles.border}`,
593
+ borderRadius: '9999px',
594
+ boxShadow: themeStyles.collapsedShadow,
87
595
  cursor: 'pointer',
88
- lineHeight: 1.5,
89
- }, children: current }));
596
+ display: 'flex',
597
+ outline: 'none',
598
+ padding: '0.5rem 0.65rem 0.5rem 0.85rem',
599
+ transition: 'box-shadow 0.22s ease, transform 0.2s ease',
600
+ }, children: [_jsx("div", { style: {
601
+ alignItems: 'center',
602
+ display: 'flex',
603
+ flex: 1,
604
+ flexWrap: 'nowrap',
605
+ gap: '0.55rem',
606
+ minWidth: 0,
607
+ }, children: coreStrip }), _jsx(CollapseChevron, { expanded: false, themeStyles: themeStyles })] }));
90
608
  }
91
- return (_jsxs("div", { style: {
92
- ...base,
93
- background: 'rgba(12,12,12,0.93)',
94
- color: '#cbd5e1',
95
- border: '1px solid rgba(255,255,255,0.07)',
96
- borderRadius: '10px',
97
- padding: '0.6rem 0.75rem',
98
- lineHeight: 1.7,
99
- minWidth: '13rem',
100
- boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
101
- userSelect: 'none',
102
- }, children: [_jsx("button", { onClick: () => setOpen(false), title: 'Close', style: {
103
- position: 'absolute',
104
- top: '0.3rem',
105
- right: '0.45rem',
106
- background: 'none',
107
- border: 'none',
108
- color: '#475569',
109
- cursor: 'pointer',
110
- fontSize: '0.65rem',
111
- lineHeight: 1,
112
- padding: 0,
113
- }, children: "\u2715" }), _jsx("div", { style: {
114
- display: 'flex',
115
- gap: '0.4rem',
116
- alignItems: 'baseline',
117
- marginBottom: '0.1rem',
118
- }, children: allBreakpoints.map(([name]) => (_jsx("span", { style: {
119
- color: name === current ? '#06b6d4' : '#334155',
120
- fontWeight: name === current ? 700 : 400,
121
- fontSize: name === current ? '0.78rem' : '0.68rem',
122
- letterSpacing: name === current ? '0.02em' : undefined,
123
- }, children: name }, name))) }), _jsxs("div", { style: {
609
+ return (_jsxs("div", { "aria-label": 'Development panel', role: 'status', style: {
610
+ ...PANEL_BASE,
611
+ background: themeStyles.panelBackground,
612
+ border: `1px solid ${themeStyles.border}`,
613
+ borderRadius: expandedRadius,
614
+ boxShadow: themeStyles.panelShadow,
615
+ display: 'flex',
616
+ flexDirection: 'column',
617
+ maxWidth: layout === 'inspector'
618
+ ? 'min(100vw - 1.5rem, 36rem)'
619
+ : 'min(100vw - 1.5rem, 56rem)',
620
+ padding: '0.65rem 0.7rem 0.5rem 0.85rem',
621
+ transition: 'box-shadow 0.22s ease',
622
+ }, children: [_jsxs("div", { style: {
623
+ alignItems: 'flex-start',
124
624
  display: 'flex',
125
- gap: '0.75rem',
126
- color: '#475569',
127
- fontSize: '0.68rem',
128
- }, children: [_jsxs("span", { children: [viewport.w, " \u00D7 ", viewport.h] }), _jsx("span", { children: dark ? '☾ dark' : '☀ light' })] }), children && (_jsx("div", { style: {
129
- marginTop: '0.4rem',
130
- paddingTop: '0.4rem',
131
- borderTop: '1px solid rgba(255,255,255,0.05)',
132
- color: '#64748b',
133
- fontSize: '0.68rem',
134
- lineHeight: 1.6,
135
- }, children: children })), _jsxs("div", { style: {
136
- marginTop: '0.3rem',
137
- color: '#1e293b',
138
- fontSize: '0.6rem',
139
- }, children: [shortcut, " to toggle"] })] }));
625
+ gap: '0.45rem',
626
+ justifyContent: 'space-between',
627
+ }, children: [_jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [headerRow, detailBlock] }), _jsx("button", { "aria-label": 'Collapse dev panel', onClick: () => setOpen(false), style: {
628
+ alignItems: 'center',
629
+ background: 'rgba(148, 163, 184, 0.14)',
630
+ border: 'none',
631
+ borderRadius: '9999px',
632
+ color: themeStyles.mutedText,
633
+ cursor: 'pointer',
634
+ display: 'inline-flex',
635
+ flexShrink: 0,
636
+ justifyContent: 'center',
637
+ lineHeight: 1,
638
+ minHeight: '2rem',
639
+ minWidth: '2rem',
640
+ padding: 0,
641
+ transition: 'background 0.15s ease',
642
+ }, title: 'Collapse', type: 'button', children: _jsx(CollapseChevron, { expanded: true, themeStyles: themeStyles }) })] }), _jsxs("p", { style: {
643
+ color: themeStyles.mutedText,
644
+ fontFamily: SANS,
645
+ fontSize: FS.footer,
646
+ letterSpacing: '0.05em',
647
+ margin: 0,
648
+ opacity: 0.92,
649
+ paddingTop: '0.45rem',
650
+ textAlign: 'center',
651
+ }, children: [shortcutHint, " to toggle"] })] }));
140
652
  }
141
653
  /**
142
- * Fixed overlay showing the current Tailwind breakpoint, viewport dimensions,
143
- * and color scheme. Dev-only renders nothing in production.
144
- *
145
- * Much better than raw px — shows the named breakpoint (`lg`) so you immediately
146
- * know which Tailwind classes are active, not just "1124px".
147
- *
148
- * Pass children to add custom debug rows (route, user, feature flags, etc.).
149
- * Toggle visibility with the keyboard shortcut (default: Shift+D).
654
+ * Development overlay: viewport px, Tailwind-style breakpoint, resolved theme,
655
+ * plus optional diagnostics enabled via `features`.
150
656
  */
151
657
  export function DevPanel(props) {
152
658
  if (process.env.NODE_ENV === 'production')