@teo-garcia/react-shared 0.1.9 → 1.2.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 (177) hide show
  1. package/README.md +127 -99
  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 +30 -0
  15. package/dist/components/dev-panel/dev-panel.d.ts.map +1 -0
  16. package/dist/components/dev-panel/dev-panel.js +155 -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 +51 -77
  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 -5
  32. package/dist/components/index.d.ts.map +1 -1
  33. package/dist/components/index.js +9 -5
  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 +15 -5
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +15 -4
  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-debounce.d.ts +2 -0
  71. package/dist/hooks/use-debounce.d.ts.map +1 -0
  72. package/dist/hooks/use-debounce.js +9 -0
  73. package/dist/hooks/use-event-listener.d.ts +4 -0
  74. package/dist/hooks/use-event-listener.d.ts.map +1 -0
  75. package/dist/hooks/use-event-listener.js +13 -0
  76. package/dist/hooks/use-idle.d.ts +7 -0
  77. package/dist/hooks/use-idle.d.ts.map +1 -0
  78. package/dist/hooks/use-idle.js +35 -0
  79. package/dist/hooks/use-intersection-observer.d.ts +15 -0
  80. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  81. package/dist/hooks/use-intersection-observer.js +22 -0
  82. package/dist/hooks/use-isomorphic-layout-effect.d.ts +3 -0
  83. package/dist/hooks/use-isomorphic-layout-effect.d.ts.map +1 -0
  84. package/dist/hooks/use-isomorphic-layout-effect.js +4 -0
  85. package/dist/hooks/use-latest.d.ts +7 -0
  86. package/dist/hooks/use-latest.d.ts.map +1 -0
  87. package/dist/hooks/use-latest.js +11 -0
  88. package/dist/hooks/use-local-storage.d.ts +2 -0
  89. package/dist/hooks/use-local-storage.d.ts.map +1 -0
  90. package/dist/hooks/use-local-storage.js +37 -0
  91. package/dist/hooks/use-media-query.d.ts +2 -0
  92. package/dist/hooks/use-media-query.d.ts.map +1 -0
  93. package/dist/hooks/use-media-query.js +18 -0
  94. package/dist/hooks/use-network-status.d.ts +10 -0
  95. package/dist/hooks/use-network-status.d.ts.map +1 -0
  96. package/dist/hooks/use-network-status.js +19 -0
  97. package/dist/hooks/use-on-click-outside.d.ts +3 -0
  98. package/dist/hooks/use-on-click-outside.d.ts.map +1 -0
  99. package/dist/hooks/use-on-click-outside.js +17 -0
  100. package/dist/hooks/use-previous.d.ts +2 -0
  101. package/dist/hooks/use-previous.d.ts.map +1 -0
  102. package/dist/hooks/use-previous.js +8 -0
  103. package/dist/hooks/use-render-count.d.ts +7 -0
  104. package/dist/hooks/use-render-count.d.ts.map +1 -0
  105. package/dist/hooks/use-render-count.js +15 -0
  106. package/dist/hooks/use-toggle.d.ts +6 -0
  107. package/dist/hooks/use-toggle.d.ts.map +1 -0
  108. package/dist/hooks/use-toggle.js +12 -0
  109. package/dist/hooks/use-why-did-you-render.d.ts +8 -0
  110. package/dist/hooks/use-why-did-you-render.d.ts.map +1 -0
  111. package/dist/hooks/use-why-did-you-render.js +38 -0
  112. package/dist/index.d.ts +6 -4
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +6 -9
  115. package/dist/test-utils/index.d.ts +14 -0
  116. package/dist/test-utils/index.d.ts.map +1 -0
  117. package/dist/test-utils/index.js +22 -0
  118. package/dist/types.d.ts +17 -36
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/utils/cn.d.ts +3 -0
  121. package/dist/utils/cn.d.ts.map +1 -0
  122. package/dist/utils/cn.js +5 -0
  123. package/dist/utils/format-date.d.ts +11 -0
  124. package/dist/utils/format-date.d.ts.map +1 -0
  125. package/dist/utils/format-date.js +12 -0
  126. package/dist/utils/format-number.d.ts +11 -0
  127. package/dist/utils/format-number.d.ts.map +1 -0
  128. package/dist/utils/format-number.js +12 -0
  129. package/dist/utils/index.d.ts +4 -5
  130. package/dist/utils/index.d.ts.map +1 -1
  131. package/dist/utils/index.js +4 -5
  132. package/dist/utils/truncate.d.ts +11 -0
  133. package/dist/utils/truncate.d.ts.map +1 -0
  134. package/dist/utils/truncate.js +14 -0
  135. package/package.json +141 -42
  136. package/dist/adapters/environment/index.d.ts +0 -6
  137. package/dist/adapters/environment/index.d.ts.map +0 -1
  138. package/dist/adapters/environment/index.js +0 -5
  139. package/dist/adapters/environment/next.d.ts +0 -17
  140. package/dist/adapters/environment/next.d.ts.map +0 -1
  141. package/dist/adapters/environment/next.js +0 -20
  142. package/dist/adapters/environment/vite.d.ts +0 -17
  143. package/dist/adapters/environment/vite.d.ts.map +0 -1
  144. package/dist/adapters/environment/vite.js +0 -20
  145. package/dist/adapters/index.d.ts +0 -9
  146. package/dist/adapters/index.d.ts.map +0 -1
  147. package/dist/adapters/index.js +0 -8
  148. package/dist/adapters/theme/custom.d.ts +0 -32
  149. package/dist/adapters/theme/custom.d.ts.map +0 -1
  150. package/dist/adapters/theme/custom.js +0 -26
  151. package/dist/adapters/theme/index.d.ts +0 -6
  152. package/dist/adapters/theme/index.d.ts.map +0 -1
  153. package/dist/adapters/theme/index.js +0 -5
  154. package/dist/adapters/theme/next-themes.d.ts +0 -18
  155. package/dist/adapters/theme/next-themes.d.ts.map +0 -1
  156. package/dist/adapters/theme/next-themes.js +0 -23
  157. package/dist/components/theme-switch/index.d.ts +0 -3
  158. package/dist/components/theme-switch/index.d.ts.map +0 -1
  159. package/dist/components/theme-switch/index.js +0 -1
  160. package/dist/components/theme-switch/theme-switch.d.ts +0 -36
  161. package/dist/components/theme-switch/theme-switch.d.ts.map +0 -1
  162. package/dist/components/theme-switch/theme-switch.js +0 -74
  163. package/dist/components/viewport-info/index.d.ts +0 -3
  164. package/dist/components/viewport-info/index.d.ts.map +0 -1
  165. package/dist/components/viewport-info/index.js +0 -1
  166. package/dist/components/viewport-info/viewport-info.d.ts +0 -40
  167. package/dist/components/viewport-info/viewport-info.d.ts.map +0 -1
  168. package/dist/components/viewport-info/viewport-info.js +0 -69
  169. package/dist/hooks/use-healthcheck.d.ts +0 -42
  170. package/dist/hooks/use-healthcheck.d.ts.map +0 -1
  171. package/dist/hooks/use-healthcheck.js +0 -53
  172. package/dist/utils/environment.d.ts +0 -71
  173. package/dist/utils/environment.d.ts.map +0 -1
  174. package/dist/utils/environment.js +0 -86
  175. package/dist/utils/msw.d.ts +0 -54
  176. package/dist/utils/msw.d.ts.map +0 -1
  177. package/dist/utils/msw.js +0 -62
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,141 +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 {
85
- isClient,
86
- isDevelopment,
87
- } 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.
88
62
 
89
- if (isDevelopment()) {
90
- console.log('Running in dev mode')
91
- }
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 |
92
71
 
93
- if (isClient()) {
94
- localStorage.setItem('key', 'value')
95
- }
96
- ```
72
+ Import path: `@teo-garcia/react-shared/components/error-boundary`
73
+
74
+ ---
97
75
 
98
- ### Adapters
76
+ ### `VisuallyHidden`
99
77
 
100
- ```tsx
101
- import { ViewportInfo } from '@teo-garcia/react-shared/components'
102
- 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.
103
80
 
104
- export function App() {
105
- return <ViewportInfo environmentAdapter={viteEnvironmentAdapter} />
106
- }
107
- ```
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`
108
97
 
109
- ## 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.
110
119
 
111
- | Export | Description |
112
- | ------ | ----------- |
113
- | `@teo-garcia/react-shared/components` | Shared React UI components |
114
- | `@teo-garcia/react-shared/hooks` | Shared React hooks |
115
- | `@teo-garcia/react-shared/utils` | Framework-agnostic utilities |
116
- | `@teo-garcia/react-shared/adapters/theme` | Theme adapters |
117
- | `@teo-garcia/react-shared/adapters/environment` | Environment adapters |
120
+ Use for: modals, dialogs, drawers, dropdowns — any overlay where focus must not
121
+ escape.
118
122
 
119
- ## 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 |
120
127
 
121
- ### Components
128
+ Import path: `@teo-garcia/react-shared/components/focus-trap`
122
129
 
123
- - `ThemeSwitch` - Theme switcher button
124
- - `ViewportInfo` - Viewport dimensions display for development
125
- - `ErrorBoundary` - Error boundary component
130
+ ---
131
+
132
+ ## Utilities
133
+
134
+ ### `cn`
126
135
 
127
- ### Hooks
136
+ Merges Tailwind class names with conflict resolution — later classes win. Built
137
+ on `clsx` + `tailwind-merge`.
128
138
 
129
- - `useHealthcheck` - API health check hook
139
+ Import path: `@teo-garcia/react-shared/utils/cn`
140
+
141
+ ---
130
142
 
131
- ### Utilities
143
+ ## Test utilities
132
144
 
133
- - `isDevelopment()` - Check if running in development mode
134
- - `isProduction()` - Check if running in production mode
135
- - `isServer()` - Check if running on the server
136
- - `isClient()` - Check if running in the browser
137
- - `setupMSWBrowser()` - Setup MSW for browser usage
138
- - `setupMSWServer()` - Setup MSW for Node.js and test usage
145
+ Helpers for wrapping components under test with a `QueryClient` provider.
139
146
 
140
- ### 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 |
141
151
 
142
- - `useNextThemesAdapter()` - Adapter for `next-themes`
143
- - `createCustomThemeAdapter()` - Adapter for custom theme providers
144
- - `nextEnvironmentAdapter` - Environment adapter for Next.js
145
- - `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).
146
154
 
147
- ## Notes
155
+ Import path: `@teo-garcia/react-shared/test-utils`
148
156
 
149
- - Components assume Tailwind CSS is available in the consuming app.
150
- - Hooks and adapters are designed to be framework-agnostic where possible.
151
- - 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
+ ---
152
180
 
153
- ## Related Packages
181
+ ## Related packages
154
182
 
155
183
  | Package | Description |
156
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,30 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface DevPanelProps {
3
+ /**
4
+ * Tailwind breakpoint map. Defaults to standard Tailwind v3/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
+ /** Corner to anchor the panel to. Defaults to `'bottom-left'`. */
11
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
12
+ /**
13
+ * Keyboard shortcut to toggle the panel.
14
+ * Format: modifier keys joined by `+`, then the key. Defaults to `'shift+d'`.
15
+ * Examples: `'shift+d'`, `'ctrl+shift+d'`, `'alt+d'`.
16
+ */
17
+ shortcut?: string;
18
+ }
19
+ /**
20
+ * Fixed overlay showing the current Tailwind breakpoint, viewport dimensions,
21
+ * and color scheme. Dev-only — renders nothing in production.
22
+ *
23
+ * Much better than raw px — shows the named breakpoint (`lg`) so you immediately
24
+ * know which Tailwind classes are active, not just "1124px".
25
+ *
26
+ * Pass children to add custom debug rows (route, user, feature flags, etc.).
27
+ * Toggle visibility with the keyboard shortcut (default: Shift+D).
28
+ */
29
+ export declare function DevPanel(props: DevPanelProps): import("react/jsx-runtime").JSX.Element | null;
30
+ //# 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;AAwC/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,kEAAkE;IAClE,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAA;IACpE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AA0LD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,kDAG5C"}
@@ -0,0 +1,155 @@
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
+ }
25
+ function matchShortcut(e, shortcut) {
26
+ const parts = shortcut.toLowerCase().split('+');
27
+ const key = parts.at(-1) ?? '';
28
+ return (e.key.toLowerCase() === key &&
29
+ e.shiftKey === parts.includes('shift') &&
30
+ e.metaKey === parts.includes('meta') &&
31
+ e.ctrlKey === parts.includes('ctrl') &&
32
+ e.altKey === parts.includes('alt'));
33
+ }
34
+ function DevPanelInner({ breakpoints = DEFAULT_BREAKPOINTS, children, position = 'bottom-left', shortcut = 'shift+d', }) {
35
+ const [open, setOpen] = useState(true);
36
+ const [viewport, setViewport] = useState({ w: 0, h: 0 });
37
+ const [dark, setDark] = useState(false);
38
+ 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
+ });
52
+ return () => {
53
+ window.removeEventListener('resize', update);
54
+ observer.disconnect();
55
+ };
56
+ }, []);
57
+ useEffect(() => {
58
+ function handleKey(e) {
59
+ if (matchShortcut(e, shortcut))
60
+ setOpen((v) => !v);
61
+ }
62
+ window.addEventListener('keydown', handleKey);
63
+ return () => window.removeEventListener('keydown', handleKey);
64
+ }, [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)',
78
+ };
79
+ 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',
87
+ cursor: 'pointer',
88
+ lineHeight: 1.5,
89
+ }, children: current }));
90
+ }
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: {
124
+ 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"] })] }));
140
+ }
141
+ /**
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).
150
+ */
151
+ export function DevPanel(props) {
152
+ if (process.env.NODE_ENV === 'production')
153
+ return null;
154
+ return _jsx(DevPanelInner, { ...props });
155
+ }
@@ -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';