@teo-garcia/react-shared 1.2.1 → 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 -17
  12. package/dist/components/dev-panel/dev-panel.d.ts.map +1 -1
  13. package/dist/components/dev-panel/dev-panel.js +619 -99
  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 +11 -1
@@ -1,21 +1,12 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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
- function resolveBreakpoint(width, breakpoints) {
12
- const sorted = Object.entries(breakpoints).sort(([, a], [, b]) => b - a);
13
- for (const [name, min] of sorted) {
14
- if (width >= min)
15
- return name;
16
- }
17
- return 'xs';
18
- }
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';
19
10
  function matchShortcut(e, shortcut) {
20
11
  const parts = shortcut.toLowerCase().split('+');
21
12
  const key = parts.at(-1) ?? '';
@@ -25,114 +16,643 @@ function matchShortcut(e, shortcut) {
25
16
  e.ctrlKey === parts.includes('ctrl') &&
26
17
  e.altKey === parts.includes('alt'));
27
18
  }
28
- const PILL_BASE = {
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 = {
29
64
  position: 'fixed',
30
65
  bottom: '1rem',
31
66
  left: '50%',
32
67
  transform: 'translateX(-50%)',
33
68
  zIndex: 9999,
34
- fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
35
- fontSize: '0.7rem',
36
- lineHeight: 1,
37
- background: 'rgba(9, 9, 11, 0.88)',
38
- border: '1px solid rgba(255, 255, 255, 0.08)',
39
- borderRadius: '9999px',
40
- backdropFilter: 'blur(12px)',
41
- WebkitBackdropFilter: 'blur(12px)',
42
- boxShadow: '0 4px 24px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.04)',
69
+ backdropFilter: 'blur(16px)',
70
+ WebkitBackdropFilter: 'blur(16px)',
71
+ boxSizing: 'border-box',
72
+ lineHeight: 1.35,
73
+ maxWidth: 'min(100vw - 1.5rem, 36rem)',
43
74
  userSelect: 'none',
44
- whiteSpace: 'nowrap',
45
75
  };
46
- const DIVIDER = {
47
- width: '1px',
48
- height: '0.7rem',
49
- background: 'rgba(255,255,255,0.1)',
50
- flexShrink: 0,
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',
51
86
  };
52
- function DevPanelInner({ breakpoints = DEFAULT_BREAKPOINTS, children, shortcut = 'shift+d', }) {
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);
53
98
  const [open, setOpen] = useState(true);
54
- const [viewport, setViewport] = useState({ w: 0, h: 0 });
55
- 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]);
56
106
  useEffect(() => {
57
- function update() {
58
- setViewport({ w: window.innerWidth, h: window.innerHeight });
59
- setDark(document.documentElement.classList.contains('dark') ||
60
- window.matchMedia('(prefers-color-scheme: dark)').matches);
61
- }
62
- update();
63
- window.addEventListener('resize', update);
64
- const observer = new MutationObserver(update);
65
- observer.observe(document.documentElement, {
66
- attributes: true,
67
- attributeFilter: ['class'],
68
- });
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
+ }
69
132
  return () => {
70
- window.removeEventListener('resize', update);
71
- observer.disconnect();
133
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-outline');
72
134
  };
73
- }, []);
135
+ }, [featureSet, outlineOn]);
136
+ useEffect(() => {
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
+ }
169
+ return () => {
170
+ document.documentElement.removeAttribute('data-react-shared-dev-panel-grid');
171
+ };
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]);
74
186
  useEffect(() => {
75
187
  function handleKey(e) {
76
188
  if (matchShortcut(e, shortcut))
77
- setOpen((v) => !v);
189
+ setOpen((value) => !value);
78
190
  }
79
191
  window.addEventListener('keydown', handleKey);
80
192
  return () => window.removeEventListener('keydown', handleKey);
81
193
  }, [shortcut]);
82
- const allBreakpoints = [
83
- ['xs', 0],
84
- ...Object.entries(breakpoints).sort(([, a], [, b]) => a - b),
85
- ];
86
- const current = resolveBreakpoint(viewport.w, breakpoints);
87
- if (!open) {
88
- return (_jsxs("button", { onClick: () => setOpen(true), title: `Dev Panel (${shortcut})`, style: {
89
- ...PILL_BASE,
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',
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}`,
90
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 }) }));
582
+ if (!open) {
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,
91
590
  alignItems: 'center',
92
- gap: '0.3rem',
93
- padding: '0.35rem 0.6rem',
591
+ background: themeStyles.panelBackground,
592
+ border: `1px solid ${themeStyles.border}`,
593
+ borderRadius: '9999px',
594
+ boxShadow: themeStyles.collapsedShadow,
94
595
  cursor: 'pointer',
95
- color: 'rgba(255,255,255,0.45)',
96
- }, children: [_jsx("span", { "aria-hidden": true, style: { color: '#38bdf8', fontSize: '0.45rem', lineHeight: 1 }, children: "\u25CF" }), _jsx("span", { style: {
97
- color: '#e2e8f0',
98
- fontWeight: 600,
99
- letterSpacing: '0.05em',
100
- }, 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 })] }));
101
608
  }
102
- return (_jsxs("div", { style: {
103
- ...PILL_BASE,
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,
104
615
  display: 'flex',
105
- alignItems: 'center',
106
- gap: '0.55rem',
107
- padding: '0.4rem 0.65rem 0.4rem 0.7rem',
108
- }, children: [_jsx("span", { "aria-hidden": true, style: { color: '#38bdf8', fontSize: '0.45rem', lineHeight: 1 }, children: "\u25CF" }), _jsx("div", { style: { display: 'flex', alignItems: 'baseline', gap: '0.3rem' }, children: allBreakpoints.map(([name]) => (_jsx("span", { style: {
109
- color: name === current ? '#e2e8f0' : 'rgba(255,255,255,0.18)',
110
- fontWeight: name === current ? 600 : 400,
111
- fontSize: name === current ? '0.72rem' : '0.67rem',
112
- letterSpacing: name === current ? '0.05em' : '0.02em',
113
- }, children: name }, name))) }), _jsx("div", { style: DIVIDER }), _jsxs("span", { style: { color: 'rgba(255,255,255,0.3)', letterSpacing: '0.02em' }, children: [viewport.w, _jsx("span", { style: { color: 'rgba(255,255,255,0.12)', margin: '0 0.15rem' }, children: "\u00D7" }), viewport.h] }), _jsx("div", { style: DIVIDER }), _jsx("span", { title: dark ? 'dark mode' : 'light mode', style: { color: 'rgba(255,255,255,0.3)', fontSize: '0.65rem' }, children: dark ? '◑' : '◯' }), children && (_jsxs(_Fragment, { children: [_jsx("div", { style: DIVIDER }), _jsx("div", { style: {
114
- color: 'rgba(255,255,255,0.28)',
115
- fontSize: '0.68rem',
116
- }, children: children })] })), _jsx("div", { style: DIVIDER }), _jsxs("span", { style: { color: 'rgba(255,255,255,0.15)', fontSize: '0.65rem' }, children: [shortcut, " to toggle"] }), _jsx("button", { onClick: () => setOpen(false), title: 'Close', style: {
117
- background: 'none',
118
- border: 'none',
119
- color: 'rgba(255,255,255,0.2)',
120
- cursor: 'pointer',
121
- fontSize: '0.58rem',
122
- lineHeight: 1,
123
- padding: '0 0 0 0.1rem',
124
- marginLeft: '0.05rem',
125
- }, children: "\u2715" })] }));
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',
624
+ display: 'flex',
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"] })] }));
126
652
  }
127
653
  /**
128
- * Fixed bottom-center overlay showing the current Tailwind breakpoint, viewport
129
- * dimensions, and color scheme. Dev-only — renders nothing in production.
130
- *
131
- * Positioned at bottom-center to avoid clashing with Next.js (top-left) and
132
- * TanStack devtools (bottom-right). Collapses to a compact pill on close.
133
- *
134
- * Pass `children` to add custom debug rows (route, user ID, feature flags…).
135
- * Toggle with the keyboard shortcut (default: Shift+D).
654
+ * Development overlay: viewport px, Tailwind-style breakpoint, resolved theme,
655
+ * plus optional diagnostics enabled via `features`.
136
656
  */
137
657
  export function DevPanel(props) {
138
658
  if (process.env.NODE_ENV === 'production')