@teo-garcia/react-shared 1.0.0 → 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.
- 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 +30 -0
- package/dist/components/dev-panel/dev-panel.d.ts.map +1 -0
- package/dist/components/dev-panel/dev-panel.js +155 -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 +85 -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,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 @@
|
|
|
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';
|