@teo-garcia/react-shared 1.0.0 → 1.2.1

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 (108) hide show
  1. package/README.md +127 -96
  2. package/dist/components/aspect-ratio/aspect-ratio.d.ts +17 -0
  3. package/dist/components/aspect-ratio/aspect-ratio.d.ts.map +1 -0
  4. package/dist/components/aspect-ratio/aspect-ratio.js +14 -0
  5. package/dist/components/aspect-ratio/index.d.ts +2 -0
  6. package/dist/components/aspect-ratio/index.d.ts.map +1 -0
  7. package/dist/components/aspect-ratio/index.js +1 -0
  8. package/dist/components/debug-json/debug-json.d.ts +14 -0
  9. package/dist/components/debug-json/debug-json.d.ts.map +1 -0
  10. package/dist/components/debug-json/debug-json.js +32 -0
  11. package/dist/components/debug-json/index.d.ts +2 -0
  12. package/dist/components/debug-json/index.d.ts.map +1 -0
  13. package/dist/components/debug-json/index.js +1 -0
  14. package/dist/components/dev-panel/dev-panel.d.ts +27 -0
  15. package/dist/components/dev-panel/dev-panel.d.ts.map +1 -0
  16. package/dist/components/dev-panel/dev-panel.js +141 -0
  17. package/dist/components/dev-panel/index.d.ts +2 -0
  18. package/dist/components/dev-panel/index.d.ts.map +1 -0
  19. package/dist/components/dev-panel/index.js +1 -0
  20. package/dist/components/error-boundary/error-boundary.d.ts +3 -58
  21. package/dist/components/error-boundary/error-boundary.d.ts.map +1 -1
  22. package/dist/components/error-boundary/error-boundary.js +50 -76
  23. package/dist/components/error-boundary/index.d.ts +1 -0
  24. package/dist/components/error-boundary/index.d.ts.map +1 -1
  25. package/dist/components/focus-trap/focus-trap.d.ts +16 -0
  26. package/dist/components/focus-trap/focus-trap.d.ts.map +1 -0
  27. package/dist/components/focus-trap/focus-trap.js +57 -0
  28. package/dist/components/focus-trap/index.d.ts +2 -0
  29. package/dist/components/focus-trap/index.d.ts.map +1 -0
  30. package/dist/components/focus-trap/index.js +1 -0
  31. package/dist/components/index.d.ts +9 -0
  32. package/dist/components/index.d.ts.map +1 -1
  33. package/dist/components/index.js +9 -0
  34. package/dist/components/portal/index.d.ts +2 -0
  35. package/dist/components/portal/index.d.ts.map +1 -0
  36. package/dist/components/portal/index.js +1 -0
  37. package/dist/components/portal/portal.d.ts +14 -0
  38. package/dist/components/portal/portal.d.ts.map +1 -0
  39. package/dist/components/portal/portal.js +21 -0
  40. package/dist/components/separator/index.d.ts +2 -0
  41. package/dist/components/separator/index.d.ts.map +1 -0
  42. package/dist/components/separator/index.js +1 -0
  43. package/dist/components/separator/separator.d.ts +11 -0
  44. package/dist/components/separator/separator.d.ts.map +1 -0
  45. package/dist/components/separator/separator.js +11 -0
  46. package/dist/components/skeleton/index.d.ts +2 -0
  47. package/dist/components/skeleton/index.d.ts.map +1 -0
  48. package/dist/components/skeleton/index.js +1 -0
  49. package/dist/components/skeleton/skeleton.d.ts +3 -0
  50. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  51. package/dist/components/skeleton/skeleton.js +5 -0
  52. package/dist/components/skip-link/index.d.ts +2 -0
  53. package/dist/components/skip-link/index.d.ts.map +1 -0
  54. package/dist/components/skip-link/index.js +1 -0
  55. package/dist/components/skip-link/skip-link.d.ts +13 -0
  56. package/dist/components/skip-link/skip-link.d.ts.map +1 -0
  57. package/dist/components/skip-link/skip-link.js +26 -0
  58. package/dist/components/visually-hidden/index.d.ts +2 -0
  59. package/dist/components/visually-hidden/index.d.ts.map +1 -0
  60. package/dist/components/visually-hidden/index.js +1 -0
  61. package/dist/components/visually-hidden/visually-hidden.d.ts +3 -0
  62. package/dist/components/visually-hidden/visually-hidden.d.ts.map +1 -0
  63. package/dist/components/visually-hidden/visually-hidden.js +15 -0
  64. package/dist/hooks/index.d.ts +9 -0
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +9 -0
  67. package/dist/hooks/use-copy-to-clipboard.d.ts +7 -0
  68. package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  69. package/dist/hooks/use-copy-to-clipboard.js +23 -0
  70. package/dist/hooks/use-event-listener.d.ts +4 -0
  71. package/dist/hooks/use-event-listener.d.ts.map +1 -0
  72. package/dist/hooks/use-event-listener.js +13 -0
  73. package/dist/hooks/use-idle.d.ts +7 -0
  74. package/dist/hooks/use-idle.d.ts.map +1 -0
  75. package/dist/hooks/use-idle.js +35 -0
  76. package/dist/hooks/use-intersection-observer.d.ts +15 -0
  77. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  78. package/dist/hooks/use-intersection-observer.js +22 -0
  79. package/dist/hooks/use-latest.d.ts +7 -0
  80. package/dist/hooks/use-latest.d.ts.map +1 -0
  81. package/dist/hooks/use-latest.js +11 -0
  82. package/dist/hooks/use-network-status.d.ts +10 -0
  83. package/dist/hooks/use-network-status.d.ts.map +1 -0
  84. package/dist/hooks/use-network-status.js +19 -0
  85. package/dist/hooks/use-render-count.d.ts +7 -0
  86. package/dist/hooks/use-render-count.d.ts.map +1 -0
  87. package/dist/hooks/use-render-count.js +15 -0
  88. package/dist/hooks/use-toggle.d.ts +6 -0
  89. package/dist/hooks/use-toggle.d.ts.map +1 -0
  90. package/dist/hooks/use-toggle.js +12 -0
  91. package/dist/hooks/use-why-did-you-render.d.ts +8 -0
  92. package/dist/hooks/use-why-did-you-render.d.ts.map +1 -0
  93. package/dist/hooks/use-why-did-you-render.js +38 -0
  94. package/dist/types.d.ts +18 -3
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/utils/format-date.d.ts +11 -0
  97. package/dist/utils/format-date.d.ts.map +1 -0
  98. package/dist/utils/format-date.js +12 -0
  99. package/dist/utils/format-number.d.ts +11 -0
  100. package/dist/utils/format-number.d.ts.map +1 -0
  101. package/dist/utils/format-number.js +12 -0
  102. package/dist/utils/index.d.ts +3 -0
  103. package/dist/utils/index.d.ts.map +1 -1
  104. package/dist/utils/index.js +3 -0
  105. package/dist/utils/truncate.d.ts +11 -0
  106. package/dist/utils/truncate.d.ts.map +1 -0
  107. package/dist/utils/truncate.js +14 -0
  108. package/package.json +120 -1
package/README.md CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  # @teo-garcia/react-shared
4
4
 
5
- **Shared React components, hooks, utilities, and adapters for fullstack web
6
- templates**
5
+ **Shared React hooks, utilities, and components for fullstack web templates**
7
6
 
8
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
9
8
  [![npm](https://img.shields.io/npm/v/@teo-garcia/react-shared?color=blue)](https://www.npmjs.com/package/@teo-garcia/react-shared)
10
- [![React](https://img.shields.io/badge/React-18+-61DAFB?logo=react&logoColor=black)](https://react.dev)
9
+ [![React](https://img.shields.io/badge/React-19+-61DAFB?logo=react&logoColor=black)](https://react.dev)
11
10
 
12
11
  Part of the [@teo-garcia/templates](https://github.com/teo-garcia/templates)
13
12
  ecosystem
@@ -16,138 +15,170 @@ ecosystem
16
15
 
17
16
  ---
18
17
 
19
- ## Features
20
-
21
- | Area | Includes |
22
- | ----------------- | ------------------------------------------------------------------ |
23
- | **Components** | `ThemeSwitch`, `ViewportInfo`, `ErrorBoundary` |
24
- | **Hooks** | `useHealthcheck` with React Query |
25
- | **Utilities** | Environment helpers and MSW setup helpers |
26
- | **Adapters** | Theme and environment adapters for Next.js and Vite-based apps |
27
- | **Compatibility** | Framework-agnostic usage across Next.js and React Router templates |
28
- | **Type Safety** | TypeScript definitions for all exports |
29
- | **Packaging** | Tree-shakeable module exports |
30
-
31
18
  ## Requirements
32
19
 
33
- - React 18+
20
+ - React 19+
34
21
  - TypeScript
35
- - Tailwind CSS for included component styles
22
+ - Tailwind CSS in the consuming app (for `Skeleton` and `cn`)
36
23
 
37
- ## Quick Start
24
+ ## Installation
38
25
 
39
26
  ```bash
40
- # Install the package
41
27
  pnpm add @teo-garcia/react-shared
42
28
 
43
- # Required peer dependencies
44
- pnpm add react react-dom lucide-react
45
-
46
- # Optional for hooks
29
+ # Optional: hooks that use TanStack Query
47
30
  pnpm add @tanstack/react-query
48
31
 
49
- # Optional for MSW helpers
50
- pnpm add -D msw
32
+ # Optional: test utilities
33
+ pnpm add -D @testing-library/react
51
34
  ```
52
35
 
53
- ### Components
36
+ ---
54
37
 
55
- ```tsx
56
- import { ThemeSwitch } from '@teo-garcia/react-shared/components'
57
- import { useNextThemesAdapter } from '@teo-garcia/react-shared/adapters/theme'
38
+ ## Hooks
58
39
 
59
- export function App() {
60
- const themeAdapter = useNextThemesAdapter()
61
- return <ThemeSwitch themeAdapter={themeAdapter} />
62
- }
63
- ```
40
+ All hooks are framework-agnostic and SSR-safe.
64
41
 
65
- ### Hooks
42
+ | Hook | Description |
43
+ | --------------------------------- | ---------------------------------------------------------------------------------------------- |
44
+ | `useDebounce(value, delay)` | Returns a debounced copy of `value` that updates after `delay` ms of inactivity |
45
+ | `useLocalStorage(key, initial)` | `localStorage`-backed state as a tuple `[value, set, remove]`. Returns `initial` on the server |
46
+ | `useMediaQuery(query)` | Tracks a CSS media query. Returns `false` on the server |
47
+ | `useOnClickOutside(ref, handler)` | Fires `handler` on `mousedown`/`touchstart` outside the given ref |
48
+ | `usePrevious(value)` | Returns the previous render's value of a state or prop |
49
+ | `useIsomorphicLayoutEffect` | `useLayoutEffect` on the client, `useEffect` on the server — eliminates SSR warnings |
66
50
 
67
- ```tsx
68
- import { useHealthcheck } from '@teo-garcia/react-shared/hooks'
51
+ Import paths: `@teo-garcia/react-shared/hooks/<hook-name>`
69
52
 
70
- export function HealthStatus() {
71
- const { data, isLoading, error } = useHealthcheck({
72
- url: 'http://localhost:3000/api/healthcheck',
73
- })
53
+ ---
74
54
 
75
- if (isLoading) return <div>Checking...</div>
76
- if (error) return <div>Health check failed</div>
77
- return <div>Status: {data?.status}</div>
78
- }
79
- ```
55
+ ## Components
80
56
 
81
- ### Utilities
57
+ ### `ErrorBoundary`
82
58
 
83
- ```tsx
84
- import { isClient, isDevelopment } from '@teo-garcia/react-shared/utils'
59
+ Catches thrown errors in child trees and renders a fallback. Supports four
60
+ fallback patterns, auto-reset on prop changes, and focus-restoring "Try again"
61
+ in the default UI.
85
62
 
86
- if (isDevelopment()) {
87
- console.log('Running in dev mode')
88
- }
63
+ | Prop | Type | Description |
64
+ | ------------------- | --------------------------------------------- | ---------------------------------------------------------------------- |
65
+ | `FallbackComponent` | `ComponentType<{ error, resetError }>` | Highest-priority fallback — receives the error and a reset callback |
66
+ | `fallbackRender` | `(props: { error, resetError }) => ReactNode` | Render-prop fallback |
67
+ | `fallback` | `ReactNode \| (error) => ReactNode` | Static element or function fallback |
68
+ | `resetKeys` | `unknown[]` | When any value in the array changes, the boundary resets automatically |
69
+ | `onError` | `(error, errorInfo) => void` | Called after every caught error — use for logging or tracking |
70
+ | `onReset` | `() => void` | Called whenever the boundary resets |
89
71
 
90
- if (isClient()) {
91
- localStorage.setItem('key', 'value')
92
- }
93
- ```
72
+ Import path: `@teo-garcia/react-shared/components/error-boundary`
73
+
74
+ ---
94
75
 
95
- ### Adapters
76
+ ### `VisuallyHidden`
96
77
 
97
- ```tsx
98
- import { ViewportInfo } from '@teo-garcia/react-shared/components'
99
- import { viteEnvironmentAdapter } from '@teo-garcia/react-shared/adapters/environment'
78
+ Renders content that is invisible on screen but fully accessible to assistive
79
+ technologies. Zero dependencies, RSC-safe, no Tailwind required.
100
80
 
101
- export function App() {
102
- return <ViewportInfo environmentAdapter={viteEnvironmentAdapter} />
103
- }
104
- ```
81
+ Use for: icon-only button labels, skip-navigation links, form hints,
82
+ screen-reader-only status messages.
83
+
84
+ Import path: `@teo-garcia/react-shared/components/visually-hidden`
85
+
86
+ ---
87
+
88
+ ### `Skeleton`
89
+
90
+ A single-line loading placeholder using Tailwind's `animate-pulse`. Accepts
91
+ `className` for sizing so it adapts to any layout without wrapper divs.
92
+
93
+ Use for: card skeletons, text line placeholders, avatar placeholders, table row
94
+ stubs.
95
+
96
+ Import path: `@teo-garcia/react-shared/components/skeleton`
105
97
 
106
- ## Exports
98
+ ---
99
+
100
+ ### `Portal`
101
+
102
+ Renders children into a target DOM node outside the React tree via
103
+ `createPortal`. SSR-safe — returns `null` until mounted. Treats
104
+ `container={null}` as "not ready" and defers rendering, which pairs cleanly with
105
+ `useRef`.
106
+
107
+ Use for: modals, drawers, tooltips, toasts — anything that must escape
108
+ `overflow: hidden` or stacking context.
109
+
110
+ Import path: `@teo-garcia/react-shared/components/portal`
111
+
112
+ ---
113
+
114
+ ### `FocusTrap`
115
+
116
+ Traps keyboard focus within its container while `active`. Tab wraps from last to
117
+ first element; Shift+Tab wraps from first to last. Restores focus to the
118
+ previously focused element on deactivation or unmount.
107
119
 
108
- | Export | Description |
109
- | ----------------------------------------------- | ---------------------------- |
110
- | `@teo-garcia/react-shared/components` | Shared React UI components |
111
- | `@teo-garcia/react-shared/hooks` | Shared React hooks |
112
- | `@teo-garcia/react-shared/utils` | Framework-agnostic utilities |
113
- | `@teo-garcia/react-shared/adapters/theme` | Theme adapters |
114
- | `@teo-garcia/react-shared/adapters/environment` | Environment adapters |
120
+ Use for: modals, dialogs, drawers, dropdowns — any overlay where focus must not
121
+ escape.
115
122
 
116
- ## Included APIs
123
+ | Prop | Type | Default | Description |
124
+ | -------------- | --------- | ------- | ----------------------------------------------- |
125
+ | `active` | `boolean` | `true` | Enables or disables the trap |
126
+ | `initialFocus` | `boolean` | `true` | Focuses the first focusable child on activation |
117
127
 
118
- ### Components
128
+ Import path: `@teo-garcia/react-shared/components/focus-trap`
119
129
 
120
- - `ThemeSwitch` - Theme switcher button
121
- - `ViewportInfo` - Viewport dimensions display for development
122
- - `ErrorBoundary` - Error boundary component
130
+ ---
131
+
132
+ ## Utilities
133
+
134
+ ### `cn`
123
135
 
124
- ### Hooks
136
+ Merges Tailwind class names with conflict resolution — later classes win. Built
137
+ on `clsx` + `tailwind-merge`.
125
138
 
126
- - `useHealthcheck` - API health check hook
139
+ Import path: `@teo-garcia/react-shared/utils/cn`
140
+
141
+ ---
127
142
 
128
- ### Utilities
143
+ ## Test utilities
129
144
 
130
- - `isDevelopment()` - Check if running in development mode
131
- - `isProduction()` - Check if running in production mode
132
- - `isServer()` - Check if running on the server
133
- - `isClient()` - Check if running in the browser
134
- - `setupMSWBrowser()` - Setup MSW for browser usage
135
- - `setupMSWServer()` - Setup MSW for Node.js and test usage
145
+ Helpers for wrapping components under test with a `QueryClient` provider.
136
146
 
137
- ### Adapters
147
+ | Export | Description |
148
+ | ----------------------------------- | -------------------------------------------------------------------------------------------------- |
149
+ | `createWrapper(options?)` | Returns a `QueryClientProvider` wrapper component. Create at module level, not inside render calls |
150
+ | `renderWithProviders(ui, options?)` | Convenience wrapper around `@testing-library/react` `render` with `createWrapper` pre-applied |
138
151
 
139
- - `useNextThemesAdapter()` - Adapter for `next-themes`
140
- - `createCustomThemeAdapter()` - Adapter for custom theme providers
141
- - `nextEnvironmentAdapter` - Environment adapter for Next.js
142
- - `viteEnvironmentAdapter` - Environment adapter for Vite-based apps
152
+ Accepts an optional `queryClient` in options to inject a custom client (useful
153
+ for testing query state).
143
154
 
144
- ## Notes
155
+ Import path: `@teo-garcia/react-shared/test-utils`
145
156
 
146
- - Components assume Tailwind CSS is available in the consuming app.
147
- - Hooks and adapters are designed to be framework-agnostic where possible.
148
- - Consumers should import only the module paths they need.
157
+ ---
158
+
159
+ ## All export paths
160
+
161
+ | Path | Contents |
162
+ | ------------------------------------------------------------- | -------------------------------------- |
163
+ | `@teo-garcia/react-shared/components` | All components (barrel) |
164
+ | `@teo-garcia/react-shared/components/error-boundary` | `ErrorBoundary`, `FallbackProps` |
165
+ | `@teo-garcia/react-shared/components/focus-trap` | `FocusTrap` |
166
+ | `@teo-garcia/react-shared/components/portal` | `Portal` |
167
+ | `@teo-garcia/react-shared/components/skeleton` | `Skeleton` |
168
+ | `@teo-garcia/react-shared/components/visually-hidden` | `VisuallyHidden` |
169
+ | `@teo-garcia/react-shared/hooks` | All hooks (barrel) |
170
+ | `@teo-garcia/react-shared/hooks/use-debounce` | `useDebounce` |
171
+ | `@teo-garcia/react-shared/hooks/use-isomorphic-layout-effect` | `useIsomorphicLayoutEffect` |
172
+ | `@teo-garcia/react-shared/hooks/use-local-storage` | `useLocalStorage` |
173
+ | `@teo-garcia/react-shared/hooks/use-media-query` | `useMediaQuery` |
174
+ | `@teo-garcia/react-shared/hooks/use-on-click-outside` | `useOnClickOutside` |
175
+ | `@teo-garcia/react-shared/hooks/use-previous` | `usePrevious` |
176
+ | `@teo-garcia/react-shared/utils/cn` | `cn` |
177
+ | `@teo-garcia/react-shared/test-utils` | `createWrapper`, `renderWithProviders` |
178
+
179
+ ---
149
180
 
150
- ## Related Packages
181
+ ## Related packages
151
182
 
152
183
  | Package | Description |
153
184
  | ------------------------------------------------------------------------------------------ | ------------------- |
@@ -0,0 +1,17 @@
1
+ import { type HTMLAttributes } from 'react';
2
+ interface AspectRatioProps extends HTMLAttributes<HTMLDivElement> {
3
+ /**
4
+ * Width-to-height ratio expressed as a number.
5
+ * Common values: `16/9`, `4/3`, `1` (square), `3/4` (portrait).
6
+ * Defaults to `16/9`.
7
+ */
8
+ ratio?: number;
9
+ }
10
+ /**
11
+ * Maintains a fixed aspect ratio for its content.
12
+ * RSC-safe. No Tailwind required.
13
+ * Use for responsive images, videos, iframes, and map embeds.
14
+ */
15
+ export declare function AspectRatio({ ratio, style, children, ...props }: AspectRatioProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
17
+ //# sourceMappingURL=aspect-ratio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aspect-ratio.d.ts","sourceRoot":"","sources":["../../../src/components/aspect-ratio/aspect-ratio.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C,UAAU,gBAAiB,SAAQ,cAAc,CAAC,cAAc,CAAC;IAC/D;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAc,EACd,KAAK,EACL,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,gBAAgB,2CAclB"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Maintains a fixed aspect ratio for its content.
4
+ * RSC-safe. No Tailwind required.
5
+ * Use for responsive images, videos, iframes, and map embeds.
6
+ */
7
+ export function AspectRatio({ ratio = 16 / 9, style, children, ...props }) {
8
+ return (_jsx("div", { style: {
9
+ position: 'relative',
10
+ width: '100%',
11
+ paddingBottom: `${(1 / ratio) * 100}%`,
12
+ ...style,
13
+ }, ...props, children: _jsx("div", { style: { position: 'absolute', inset: 0 }, children: children }) }));
14
+ }
@@ -0,0 +1,2 @@
1
+ export { AspectRatio } from './aspect-ratio.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/aspect-ratio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1 @@
1
+ export { AspectRatio } from './aspect-ratio.js';
@@ -0,0 +1,14 @@
1
+ interface DebugJSONProps {
2
+ value: unknown;
3
+ label?: string;
4
+ /** Expand the details element by default. Defaults to `false`. */
5
+ open?: boolean;
6
+ }
7
+ /**
8
+ * Renders any value as pretty-printed JSON inside a collapsible `<details>`.
9
+ * Dev-only: returns `null` in production.
10
+ * Better than `console.log` during development — stays on screen and updates reactively.
11
+ */
12
+ export declare function DebugJSON({ value, label, open }: DebugJSONProps): import("react/jsx-runtime").JSX.Element | null;
13
+ export {};
14
+ //# sourceMappingURL=debug-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-json.d.ts","sourceRoot":"","sources":["../../../src/components/debug-json/debug-json.tsx"],"names":[],"mappings":"AAEA,UAAU,cAAc;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,cAAc,kDAwCvE"}
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Renders any value as pretty-printed JSON inside a collapsible `<details>`.
5
+ * Dev-only: returns `null` in production.
6
+ * Better than `console.log` during development — stays on screen and updates reactively.
7
+ */
8
+ export function DebugJSON({ value, label, open = false }) {
9
+ if (process.env.NODE_ENV === 'production')
10
+ return null;
11
+ return (_jsxs("details", { open: open, style: {
12
+ fontFamily: 'ui-monospace, monospace',
13
+ fontSize: '0.75rem',
14
+ border: '1px solid rgba(0,0,0,0.1)',
15
+ borderRadius: '6px',
16
+ padding: '0.5rem 0.625rem',
17
+ margin: '0.5rem 0',
18
+ background: 'rgba(0,0,0,0.03)',
19
+ maxWidth: '100%',
20
+ overflow: 'auto',
21
+ }, children: [_jsx("summary", { style: {
22
+ cursor: 'pointer',
23
+ userSelect: 'none',
24
+ color: '#64748b',
25
+ fontWeight: 500,
26
+ }, children: label ?? 'debug' }), _jsx("pre", { style: {
27
+ margin: '0.5rem 0 0',
28
+ whiteSpace: 'pre-wrap',
29
+ wordBreak: 'break-all',
30
+ color: '#1e293b',
31
+ }, children: JSON.stringify(value, null, 2) })] }));
32
+ }
@@ -0,0 +1,2 @@
1
+ export { DebugJSON } from './debug-json.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/debug-json/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1 @@
1
+ export { DebugJSON } from './debug-json.js';
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface DevPanelProps {
3
+ /**
4
+ * Tailwind breakpoint map. Defaults to standard Tailwind v4 breakpoints.
5
+ * Pass your own if you've customized the theme.
6
+ */
7
+ breakpoints?: Record<string, number>;
8
+ /** Additional debug content rendered inside the panel. */
9
+ children?: ReactNode;
10
+ /**
11
+ * Keyboard shortcut to toggle the panel.
12
+ * Format: modifier keys joined by `+`, then the key. Defaults to `'shift+d'`.
13
+ */
14
+ shortcut?: string;
15
+ }
16
+ /**
17
+ * Fixed bottom-center overlay showing the current Tailwind breakpoint, viewport
18
+ * dimensions, and color scheme. Dev-only — renders nothing in production.
19
+ *
20
+ * Positioned at bottom-center to avoid clashing with Next.js (top-left) and
21
+ * TanStack devtools (bottom-right). Collapses to a compact pill on close.
22
+ *
23
+ * Pass `children` to add custom debug rows (route, user ID, feature flags…).
24
+ * Toggle with the keyboard shortcut (default: Shift+D).
25
+ */
26
+ export declare function DevPanel(props: DevPanelProps): import("react/jsx-runtime").JSX.Element | null;
27
+ //# sourceMappingURL=dev-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-panel.d.ts","sourceRoot":"","sources":["../../../src/components/dev-panel/dev-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuB,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAA;AAiC/E,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AA8MD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,kDAG5C"}
@@ -0,0 +1,141 @@
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
+ }
19
+ function matchShortcut(e, shortcut) {
20
+ const parts = shortcut.toLowerCase().split('+');
21
+ const key = parts.at(-1) ?? '';
22
+ return (e.key.toLowerCase() === key &&
23
+ e.shiftKey === parts.includes('shift') &&
24
+ e.metaKey === parts.includes('meta') &&
25
+ e.ctrlKey === parts.includes('ctrl') &&
26
+ e.altKey === parts.includes('alt'));
27
+ }
28
+ const PILL_BASE = {
29
+ position: 'fixed',
30
+ bottom: '1rem',
31
+ left: '50%',
32
+ transform: 'translateX(-50%)',
33
+ 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)',
43
+ userSelect: 'none',
44
+ whiteSpace: 'nowrap',
45
+ };
46
+ const DIVIDER = {
47
+ width: '1px',
48
+ height: '0.7rem',
49
+ background: 'rgba(255,255,255,0.1)',
50
+ flexShrink: 0,
51
+ };
52
+ function DevPanelInner({ breakpoints = DEFAULT_BREAKPOINTS, children, shortcut = 'shift+d', }) {
53
+ const [open, setOpen] = useState(true);
54
+ const [viewport, setViewport] = useState({ w: 0, h: 0 });
55
+ const [dark, setDark] = useState(false);
56
+ 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
+ });
69
+ return () => {
70
+ window.removeEventListener('resize', update);
71
+ observer.disconnect();
72
+ };
73
+ }, []);
74
+ useEffect(() => {
75
+ function handleKey(e) {
76
+ if (matchShortcut(e, shortcut))
77
+ setOpen((v) => !v);
78
+ }
79
+ window.addEventListener('keydown', handleKey);
80
+ return () => window.removeEventListener('keydown', handleKey);
81
+ }, [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,
90
+ display: 'flex',
91
+ alignItems: 'center',
92
+ gap: '0.3rem',
93
+ padding: '0.35rem 0.6rem',
94
+ 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 })] }));
101
+ }
102
+ return (_jsxs("div", { style: {
103
+ ...PILL_BASE,
104
+ 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" })] }));
126
+ }
127
+ /**
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).
136
+ */
137
+ export function DevPanel(props) {
138
+ if (process.env.NODE_ENV === 'production')
139
+ return null;
140
+ return _jsx(DevPanelInner, { ...props });
141
+ }
@@ -0,0 +1,2 @@
1
+ export { DevPanel, type DevPanelProps } from './dev-panel.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/dev-panel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1 @@
1
+ export { DevPanel } from './dev-panel.js';
@@ -1,70 +1,15 @@
1
1
  import { Component, type ReactNode } from 'react';
2
- import type { ErrorBoundaryProps } from '../../types';
2
+ import type { ErrorBoundaryProps } from '../../types.js';
3
3
  interface ErrorBoundaryState {
4
4
  hasError: boolean;
5
5
  error: Error | null;
6
6
  }
7
- /**
8
- * ErrorBoundary component - Catches JavaScript errors in child components
9
- *
10
- * This component wraps your application (or part of it) to catch runtime errors
11
- * and display a fallback UI instead of crashing the entire app.
12
- *
13
- * Features:
14
- * - Catches errors in child component tree
15
- * - Displays custom fallback UI
16
- * - Optional error callback for logging
17
- * - Follows React error boundary best practices
18
- *
19
- * @example Basic usage
20
- * ```tsx
21
- * import { ErrorBoundary } from '@teo-garcia/react-shared/components'
22
- *
23
- * function App() {
24
- * return (
25
- * <ErrorBoundary fallback={<div>Something went wrong</div>}>
26
- * <YourApp />
27
- * </ErrorBoundary>
28
- * )
29
- * }
30
- * ```
31
- *
32
- * @example With dynamic fallback
33
- * ```tsx
34
- * import { ErrorBoundary } from '@teo-garcia/react-shared/components'
35
- *
36
- * function App() {
37
- * return (
38
- * <ErrorBoundary
39
- * fallback={(error) => (
40
- * <div>
41
- * <h1>Error: {error.message}</h1>
42
- * <button onClick={() => window.location.reload()}>Reload</button>
43
- * </div>
44
- * )}
45
- * onError={(error, errorInfo) => {
46
- * console.error('Error caught:', error, errorInfo)
47
- * // Send to error tracking service
48
- * }}
49
- * >
50
- * <YourApp />
51
- * </ErrorBoundary>
52
- * )
53
- * }
54
- * ```
55
- */
56
7
  export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
57
8
  constructor(props: ErrorBoundaryProps);
58
- /**
59
- * Static method called when an error is thrown in a child component
60
- * Updates state to trigger fallback UI rendering
61
- */
62
9
  static getDerivedStateFromError(error: Error): ErrorBoundaryState;
63
- /**
64
- * Called after an error is caught
65
- * Used for side effects like logging
66
- */
67
10
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
11
+ componentDidUpdate(prevProps: ErrorBoundaryProps): void;
12
+ resetError(): void;
68
13
  render(): ReactNode;
69
14
  }
70
15
  export {};