@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.
- package/README.md +127 -96
- package/dist/components/aspect-ratio/aspect-ratio.d.ts +17 -0
- package/dist/components/aspect-ratio/aspect-ratio.d.ts.map +1 -0
- package/dist/components/aspect-ratio/aspect-ratio.js +14 -0
- package/dist/components/aspect-ratio/index.d.ts +2 -0
- package/dist/components/aspect-ratio/index.d.ts.map +1 -0
- package/dist/components/aspect-ratio/index.js +1 -0
- package/dist/components/debug-json/debug-json.d.ts +14 -0
- package/dist/components/debug-json/debug-json.d.ts.map +1 -0
- package/dist/components/debug-json/debug-json.js +32 -0
- package/dist/components/debug-json/index.d.ts +2 -0
- package/dist/components/debug-json/index.d.ts.map +1 -0
- package/dist/components/debug-json/index.js +1 -0
- package/dist/components/dev-panel/dev-panel.d.ts +27 -0
- package/dist/components/dev-panel/dev-panel.d.ts.map +1 -0
- package/dist/components/dev-panel/dev-panel.js +141 -0
- package/dist/components/dev-panel/index.d.ts +2 -0
- package/dist/components/dev-panel/index.d.ts.map +1 -0
- package/dist/components/dev-panel/index.js +1 -0
- package/dist/components/error-boundary/error-boundary.d.ts +3 -58
- package/dist/components/error-boundary/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary/error-boundary.js +50 -76
- package/dist/components/error-boundary/index.d.ts +1 -0
- package/dist/components/error-boundary/index.d.ts.map +1 -1
- package/dist/components/focus-trap/focus-trap.d.ts +16 -0
- package/dist/components/focus-trap/focus-trap.d.ts.map +1 -0
- package/dist/components/focus-trap/focus-trap.js +57 -0
- package/dist/components/focus-trap/index.d.ts +2 -0
- package/dist/components/focus-trap/index.d.ts.map +1 -0
- package/dist/components/focus-trap/index.js +1 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +9 -0
- package/dist/components/portal/index.d.ts +2 -0
- package/dist/components/portal/index.d.ts.map +1 -0
- package/dist/components/portal/index.js +1 -0
- package/dist/components/portal/portal.d.ts +14 -0
- package/dist/components/portal/portal.d.ts.map +1 -0
- package/dist/components/portal/portal.js +21 -0
- package/dist/components/separator/index.d.ts +2 -0
- package/dist/components/separator/index.d.ts.map +1 -0
- package/dist/components/separator/index.js +1 -0
- package/dist/components/separator/separator.d.ts +11 -0
- package/dist/components/separator/separator.d.ts.map +1 -0
- package/dist/components/separator/separator.js +11 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +1 -0
- package/dist/components/skeleton/skeleton.d.ts +3 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +5 -0
- package/dist/components/skip-link/index.d.ts +2 -0
- package/dist/components/skip-link/index.d.ts.map +1 -0
- package/dist/components/skip-link/index.js +1 -0
- package/dist/components/skip-link/skip-link.d.ts +13 -0
- package/dist/components/skip-link/skip-link.d.ts.map +1 -0
- package/dist/components/skip-link/skip-link.js +26 -0
- package/dist/components/visually-hidden/index.d.ts +2 -0
- package/dist/components/visually-hidden/index.d.ts.map +1 -0
- package/dist/components/visually-hidden/index.js +1 -0
- package/dist/components/visually-hidden/visually-hidden.d.ts +3 -0
- package/dist/components/visually-hidden/visually-hidden.d.ts.map +1 -0
- package/dist/components/visually-hidden/visually-hidden.js +15 -0
- package/dist/hooks/index.d.ts +9 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts +7 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
- package/dist/hooks/use-copy-to-clipboard.js +23 -0
- package/dist/hooks/use-event-listener.d.ts +4 -0
- package/dist/hooks/use-event-listener.d.ts.map +1 -0
- package/dist/hooks/use-event-listener.js +13 -0
- package/dist/hooks/use-idle.d.ts +7 -0
- package/dist/hooks/use-idle.d.ts.map +1 -0
- package/dist/hooks/use-idle.js +35 -0
- package/dist/hooks/use-intersection-observer.d.ts +15 -0
- package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
- package/dist/hooks/use-intersection-observer.js +22 -0
- package/dist/hooks/use-latest.d.ts +7 -0
- package/dist/hooks/use-latest.d.ts.map +1 -0
- package/dist/hooks/use-latest.js +11 -0
- package/dist/hooks/use-network-status.d.ts +10 -0
- package/dist/hooks/use-network-status.d.ts.map +1 -0
- package/dist/hooks/use-network-status.js +19 -0
- package/dist/hooks/use-render-count.d.ts +7 -0
- package/dist/hooks/use-render-count.d.ts.map +1 -0
- package/dist/hooks/use-render-count.js +15 -0
- package/dist/hooks/use-toggle.d.ts +6 -0
- package/dist/hooks/use-toggle.d.ts.map +1 -0
- package/dist/hooks/use-toggle.js +12 -0
- package/dist/hooks/use-why-did-you-render.d.ts +8 -0
- package/dist/hooks/use-why-did-you-render.d.ts.map +1 -0
- package/dist/hooks/use-why-did-you-render.js +38 -0
- package/dist/types.d.ts +18 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/format-date.d.ts +11 -0
- package/dist/utils/format-date.d.ts.map +1 -0
- package/dist/utils/format-date.js +12 -0
- package/dist/utils/format-number.d.ts +11 -0
- package/dist/utils/format-number.d.ts.map +1 -0
- package/dist/utils/format-number.js +12 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -0
- package/dist/utils/truncate.d.ts +11 -0
- package/dist/utils/truncate.d.ts.map +1 -0
- package/dist/utils/truncate.js +14 -0
- 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
|
|
6
|
-
templates**
|
|
5
|
+
**Shared React hooks, utilities, and components for fullstack web templates**
|
|
7
6
|
|
|
8
7
|
[](LICENSE)
|
|
9
8
|
[](https://www.npmjs.com/package/@teo-garcia/react-shared)
|
|
10
|
-
[](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
|
|
20
|
+
- React 19+
|
|
34
21
|
- TypeScript
|
|
35
|
-
- Tailwind CSS for
|
|
22
|
+
- Tailwind CSS in the consuming app (for `Skeleton` and `cn`)
|
|
36
23
|
|
|
37
|
-
##
|
|
24
|
+
## Installation
|
|
38
25
|
|
|
39
26
|
```bash
|
|
40
|
-
# Install the package
|
|
41
27
|
pnpm add @teo-garcia/react-shared
|
|
42
28
|
|
|
43
|
-
#
|
|
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
|
|
50
|
-
pnpm add -D
|
|
32
|
+
# Optional: test utilities
|
|
33
|
+
pnpm add -D @testing-library/react
|
|
51
34
|
```
|
|
52
35
|
|
|
53
|
-
|
|
36
|
+
---
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
import { ThemeSwitch } from '@teo-garcia/react-shared/components'
|
|
57
|
-
import { useNextThemesAdapter } from '@teo-garcia/react-shared/adapters/theme'
|
|
38
|
+
## Hooks
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
const themeAdapter = useNextThemesAdapter()
|
|
61
|
-
return <ThemeSwitch themeAdapter={themeAdapter} />
|
|
62
|
-
}
|
|
63
|
-
```
|
|
40
|
+
All hooks are framework-agnostic and SSR-safe.
|
|
64
41
|
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
import { useHealthcheck } from '@teo-garcia/react-shared/hooks'
|
|
51
|
+
Import paths: `@teo-garcia/react-shared/hooks/<hook-name>`
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
const { data, isLoading, error } = useHealthcheck({
|
|
72
|
-
url: 'http://localhost:3000/api/healthcheck',
|
|
73
|
-
})
|
|
53
|
+
---
|
|
74
54
|
|
|
75
|
-
|
|
76
|
-
if (error) return <div>Health check failed</div>
|
|
77
|
-
return <div>Status: {data?.status}</div>
|
|
78
|
-
}
|
|
79
|
-
```
|
|
55
|
+
## Components
|
|
80
56
|
|
|
81
|
-
###
|
|
57
|
+
### `ErrorBoundary`
|
|
82
58
|
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```
|
|
72
|
+
Import path: `@teo-garcia/react-shared/components/error-boundary`
|
|
73
|
+
|
|
74
|
+
---
|
|
94
75
|
|
|
95
|
-
###
|
|
76
|
+
### `VisuallyHidden`
|
|
96
77
|
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
+
Import path: `@teo-garcia/react-shared/components/focus-trap`
|
|
119
129
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Utilities
|
|
133
|
+
|
|
134
|
+
### `cn`
|
|
123
135
|
|
|
124
|
-
|
|
136
|
+
Merges Tailwind class names with conflict resolution — later classes win. Built
|
|
137
|
+
on `clsx` + `tailwind-merge`.
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
Import path: `@teo-garcia/react-shared/utils/cn`
|
|
140
|
+
|
|
141
|
+
---
|
|
127
142
|
|
|
128
|
-
|
|
143
|
+
## Test utilities
|
|
129
144
|
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
155
|
+
Import path: `@teo-garcia/react-shared/test-utils`
|
|
145
156
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 {};
|