@mwakidenis/logx-react 0.0.0-stage → 0.1.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/CHANGELOG.md +18 -0
- package/LICENSE +23 -0
- package/README.md +151 -3
- package/dist/components/index.d.mts +71 -0
- package/dist/components/index.d.ts +71 -0
- package/dist/components/index.js +268 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +262 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +316 -0
- package/dist/hooks/index.d.ts +316 -0
- package/dist/hooks/index.js +638 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +615 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/hooks/storage/index.d.mts +40 -0
- package/dist/hooks/storage/index.d.ts +40 -0
- package/dist/hooks/storage/index.js +119 -0
- package/dist/hooks/storage/index.js.map +1 -0
- package/dist/hooks/storage/index.mjs +116 -0
- package/dist/hooks/storage/index.mjs.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1066 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +974 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/LogxProvider.d.mts +134 -0
- package/dist/providers/LogxProvider.d.ts +134 -0
- package/dist/providers/LogxProvider.js +117 -0
- package/dist/providers/LogxProvider.js.map +1 -0
- package/dist/providers/LogxProvider.mjs +114 -0
- package/dist/providers/LogxProvider.mjs.map +1 -0
- package/dist/types/index.d.mts +87 -0
- package/dist/types/index.d.ts +87 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +3 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utilities/index.d.mts +140 -0
- package/dist/utilities/index.d.ts +140 -0
- package/dist/utilities/index.js +167 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/index.mjs +104 -0
- package/dist/utilities/index.mjs.map +1 -0
- package/package.json +127 -6
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to @mwakidenis/logx-react will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-11-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Initial release of @mwakidenis/logx-react.
|
|
10
|
+
- Hooks: useToggle, useBoolean, useCounter, usePrevious, useLatest, useMounted, useArray, useObject, useDebouncedValue, useDebouncedCallback, useThrottle, useLocalStorage, useSessionStorage, useMediaQuery, useWindowSize, useClickOutside, useEventListener, useClipboard, useOnlineStatus, useTimeout, useInterval, useAsync.
|
|
11
|
+
- Components: CopyButton, DebouncedInput, PasswordInput, Responsive, ErrorBoundary.
|
|
12
|
+
- Providers: LogxProvider.
|
|
13
|
+
- Utilities: array, object, string, number, validation, date, browser.
|
|
14
|
+
- LOGX integration layer using @mwakidenis/logx at runtime.
|
|
15
|
+
- Vitest + React Testing Library test suite (75 tests).
|
|
16
|
+
- ESM, CommonJS, and TypeScript declaration builds via tsup.
|
|
17
|
+
- GitHub Actions CI workflow.
|
|
18
|
+
- Documentation and examples.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# LOGX React Toolkit
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Mwaki Denis
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @mwakidenis/logx-react
|
|
2
|
+
|
|
3
|
+
A modular, lightweight, SSR-safe React utility toolkit built on [@mwakidenis/logx](https://github.com/mwakidenis/logx). Provides hooks, components, utilities, and providers for production React applications.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@mwakidenis/logx-react)
|
|
6
|
+
[](https://www.npmjs.com/package/@mwakidenis/logx-react)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @mwakidenis/logx-react @mwakidenis/logx
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`@mwakidenis/logx` is a required peer/runtime dependency and must be installed alongside this package.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install --save react react-dom # peer dependencies (React 18 or 19)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useToggle, useDebouncedValue, CopyButton } from "@mwakidenis/logx-react";
|
|
26
|
+
|
|
27
|
+
function Demo() {
|
|
28
|
+
const [open] = useToggle(false);
|
|
29
|
+
const value = useDebouncedValue("initial", { delay: 300 });
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div>
|
|
33
|
+
<button aria-expanded={open}>{open ? "Open" : "Closed"}</button>
|
|
34
|
+
<CopyButton text={value} />
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Feature overview
|
|
41
|
+
|
|
42
|
+
**Hooks** — state, performance, browser, storage, and async utilities:
|
|
43
|
+
|
|
44
|
+
| Hook | Purpose |
|
|
45
|
+
|------|---------|
|
|
46
|
+
| `useToggle` / `useBoolean` | Boolean state with stable actions |
|
|
47
|
+
| `useCounter` | Numeric counter |
|
|
48
|
+
| `usePrevious` / `useLatest` | Track previous / latest values |
|
|
49
|
+
| `useMounted` | Mount status (SSR-safe) |
|
|
50
|
+
| `useArray` / `useObject` | Array and object state with LOGX helpers |
|
|
51
|
+
| `useDebouncedValue` | Debounce a changing value |
|
|
52
|
+
| `useDebouncedCallback` / `useThrottle` | Debounce/throttle callbacks via LOGX |
|
|
53
|
+
| `useLocalStorage` / `useSessionStorage` | Persisted state (exception-guarded) |
|
|
54
|
+
| `useMediaQuery` / `useWindowSize` | Browser environment (SSR-safe) |
|
|
55
|
+
| `useClickOutside` / `useEventListener` | DOM event handling |
|
|
56
|
+
| `useClipboard` / `useOnlineStatus` | Clipboard and connectivity |
|
|
57
|
+
| `useTimeout` / `useInterval` | Controllable timers |
|
|
58
|
+
| `useAsync` | Async state with cancellation |
|
|
59
|
+
|
|
60
|
+
**Components** — independently importable, no UI framework dependency:
|
|
61
|
+
|
|
62
|
+
| Component | Purpose |
|
|
63
|
+
|-----------|---------|
|
|
64
|
+
| `CopyButton` | Accessible copy-to-clipboard button |
|
|
65
|
+
| `DebouncedInput` | Input with immediate + debounced values |
|
|
66
|
+
| `PasswordInput` | Show/hide password input |
|
|
67
|
+
| `Responsive` | Media-query-based rendering (SSR-safe) |
|
|
68
|
+
| `ErrorBoundary` | Descendant error capture with fallback |
|
|
69
|
+
|
|
70
|
+
**Utilities** — array, object, string, number, validation, date, and browser helpers, many delegating to @mwakidenis/logx.
|
|
71
|
+
|
|
72
|
+
**Providers** — `LogxProvider` exposes the LOGX integration layer via React context (optional).
|
|
73
|
+
|
|
74
|
+
## Modular imports
|
|
75
|
+
|
|
76
|
+
Every export supports tree-shaking. Import from the root or from a subpath:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { useToggle } from "@mwakidenis/logx-react";
|
|
80
|
+
import { useDebouncedValue } from "@mwakidenis/logx-react/hooks";
|
|
81
|
+
import { useLocalStorage } from "@mwakidenis/logx-react/hooks/storage";
|
|
82
|
+
import { CopyButton } from "@mwakidenis/logx-react/components";
|
|
83
|
+
import { array } from "@mwakidenis/logx-react/utilities";
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
All advertised paths are wired through `package.json` `exports` with ESM, CommonJS, and TypeScript declarations.
|
|
87
|
+
|
|
88
|
+
## LOGX integration
|
|
89
|
+
|
|
90
|
+
This toolkit genuinely uses [`@mwakidenis/logx`](https://github.com/mwakidenis/logx) at runtime:
|
|
91
|
+
|
|
92
|
+
- `useDebouncedCallback` and `useThrottle` wrap LOGX's `debounce` and `throttle` (`@mwakidenis/logx/function`).
|
|
93
|
+
- `useArray` and `useObject` delegate pure transformations (unique, compact, pick, omit, merge, get, set, keys, values, entries, …) to LOGX's array and object modules.
|
|
94
|
+
- `useLocalStorage` / `useSessionStorage` use LOGX's `safeStorage`, `localStorageHelpers`, and `sessionStorageHelpers` (`@mwakidenis/logx/browser`).
|
|
95
|
+
- Validation and object helpers reuse LOGX's `validation`, `object`, `string`, `date`, `number`, and `async` modules.
|
|
96
|
+
|
|
97
|
+
A dedicated integration layer (`src/internal/logx.ts`) keeps React-specific behavior separate from core utility behavior. No LOGX source is copied into this project, and the parent repository is not modified.
|
|
98
|
+
|
|
99
|
+
See `docs/logx-integration.md` for details.
|
|
100
|
+
|
|
101
|
+
## TypeScript
|
|
102
|
+
|
|
103
|
+
Written in strict TypeScript (`strict`, `noImplicitAny`, `strictNullChecks`, declaration generation). All public APIs are generically typed. No `any` is used in public APIs.
|
|
104
|
+
|
|
105
|
+
## SSR compatibility
|
|
106
|
+
|
|
107
|
+
All hooks and components are safe to use during server-side rendering and hydration. Browser APIs (`window`, `document`, `localStorage`, `matchMedia`, `navigator`) are never accessed at module initialization; storage and media queries initialize after mount so server and client first renders match.
|
|
108
|
+
|
|
109
|
+
## React compatibility
|
|
110
|
+
|
|
111
|
+
`react` and `react-dom` are peer dependencies. Tested against React 18.2+ and React 19. Follows the Rules of Hooks and avoids deprecated React APIs.
|
|
112
|
+
|
|
113
|
+
## Performance philosophy
|
|
114
|
+
|
|
115
|
+
- Minimal runtime dependencies (`@mwakidenis/logx` only).
|
|
116
|
+
- Tree-shakable modular imports; `sideEffects: false`.
|
|
117
|
+
- No unnecessary re-renders; stable callbacks where appropriate.
|
|
118
|
+
- Proper timer and event-listener cleanup to avoid memory leaks.
|
|
119
|
+
|
|
120
|
+
No performance-superiority claims are made without benchmarks.
|
|
121
|
+
|
|
122
|
+
## Browser support
|
|
123
|
+
|
|
124
|
+
Modern browsers with React 18/19. Graceful degradation when browser APIs (localStorage, sessionStorage, Clipboard API, matchMedia) are unavailable.
|
|
125
|
+
|
|
126
|
+
## Testing
|
|
127
|
+
|
|
128
|
+
Tests use Vitest, React Testing Library, and jsdom. Run:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm test
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Contributing
|
|
135
|
+
|
|
136
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security issues: [SECURITY.md](SECURITY.md).
|
|
137
|
+
|
|
138
|
+
## Roadmap
|
|
139
|
+
|
|
140
|
+
See the [roadmap](docs/README.md) in the docs folder.
|
|
141
|
+
|
|
142
|
+
## Author
|
|
143
|
+
|
|
144
|
+
Mwaki Denis — https://github.com/mwakidenis
|
|
145
|
+
|
|
146
|
+
## Links
|
|
147
|
+
|
|
148
|
+
- Repository: https://github.com/mwakidenis/logx-react-toolkit
|
|
149
|
+
- npm package: https://www.npmjs.com/package/@mwakidenis/logx-react
|
|
150
|
+
- Parent library: https://github.com/mwakidenis/logx (@mwakidenis/logx)
|
|
151
|
+
- License: [MIT](LICENSE)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CopyButtonProps, DebouncedInputProps, PasswordInputProps, ResponsiveProps, ErrorBoundaryProps, ErrorBoundaryState } from '../types/index.mjs';
|
|
2
|
+
import { Component, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Accessible copy-to-clipboard button.
|
|
6
|
+
*
|
|
7
|
+
* - Copies the supplied `text` to the clipboard.
|
|
8
|
+
* - Shows idle / copying / success / error states.
|
|
9
|
+
* - Supports custom labels per state and a custom className.
|
|
10
|
+
* - Keyboard accessible (native button). No icon library dependency.
|
|
11
|
+
*
|
|
12
|
+
* @param props - Component props
|
|
13
|
+
*/
|
|
14
|
+
declare function CopyButton({ text, label, copyingLabel, successLabel, errorLabel, className, onCopy, onError, timeout, }: CopyButtonProps): JSX.Element;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Controlled input with a separate debounced value.
|
|
18
|
+
*
|
|
19
|
+
* - `value`/`defaultValue` for controlled/uncontrolled usage.
|
|
20
|
+
* - `delay` controls debounce duration.
|
|
21
|
+
* - `onChange` fires immediately; `onDebouncedChange` fires after debounce.
|
|
22
|
+
* - Accepts standard input attributes for form integration.
|
|
23
|
+
*
|
|
24
|
+
* @param props - Component props
|
|
25
|
+
*/
|
|
26
|
+
declare function DebouncedInput({ value, defaultValue, onChange, onDebouncedChange, delay, ...rest }: DebouncedInputProps): JSX.Element;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Accessible show/hide password input.
|
|
30
|
+
*
|
|
31
|
+
* - Controlled/uncontrolled value.
|
|
32
|
+
* - Accessible toggle button labels.
|
|
33
|
+
* - Keyboard accessible; no external UI framework dependency.
|
|
34
|
+
*
|
|
35
|
+
* @param props - Component props
|
|
36
|
+
*/
|
|
37
|
+
declare function PasswordInput({ value, defaultValue, onChange, label, placeholder, disabled, required, name, id, showToggle, className, }: PasswordInputProps): JSX.Element;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Conditionally render content based on a media query.
|
|
41
|
+
*
|
|
42
|
+
* SSR-safe: on the server and before mount, renders `fallback` (or null),
|
|
43
|
+
* avoiding hydration mismatches. After mount, renders `children(matches)` when
|
|
44
|
+
* the query matches, otherwise `fallback`.
|
|
45
|
+
*
|
|
46
|
+
* @param props - Component props
|
|
47
|
+
*/
|
|
48
|
+
declare function Responsive({ children, query, fallback }: ResponsiveProps): JSX.Element;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* React error boundary.
|
|
52
|
+
*
|
|
53
|
+
* Captures descendant rendering errors, supports a fallback UI (element or
|
|
54
|
+
* render function receiving the error and React error info), an optional
|
|
55
|
+
* onError callback, and optional reset via `resetKeys` changes.
|
|
56
|
+
*
|
|
57
|
+
* Limitations: does not catch errors in event handlers, asynchronous code
|
|
58
|
+
* (setTimeout, promises), or server-side rendering. Those must be handled
|
|
59
|
+
* separately (try/catch, error-first callbacks, etc.).
|
|
60
|
+
*
|
|
61
|
+
* @param props - Component props
|
|
62
|
+
*/
|
|
63
|
+
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
64
|
+
state: ErrorBoundaryState;
|
|
65
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
66
|
+
componentDidCatch(error: Error, info: React.ErrorInfo): void;
|
|
67
|
+
componentDidUpdate(prevProps: ErrorBoundaryProps): void;
|
|
68
|
+
render(): ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { CopyButton, DebouncedInput, ErrorBoundary, PasswordInput, Responsive };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CopyButtonProps, DebouncedInputProps, PasswordInputProps, ResponsiveProps, ErrorBoundaryProps, ErrorBoundaryState } from '../types/index.js';
|
|
2
|
+
import { Component, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Accessible copy-to-clipboard button.
|
|
6
|
+
*
|
|
7
|
+
* - Copies the supplied `text` to the clipboard.
|
|
8
|
+
* - Shows idle / copying / success / error states.
|
|
9
|
+
* - Supports custom labels per state and a custom className.
|
|
10
|
+
* - Keyboard accessible (native button). No icon library dependency.
|
|
11
|
+
*
|
|
12
|
+
* @param props - Component props
|
|
13
|
+
*/
|
|
14
|
+
declare function CopyButton({ text, label, copyingLabel, successLabel, errorLabel, className, onCopy, onError, timeout, }: CopyButtonProps): JSX.Element;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Controlled input with a separate debounced value.
|
|
18
|
+
*
|
|
19
|
+
* - `value`/`defaultValue` for controlled/uncontrolled usage.
|
|
20
|
+
* - `delay` controls debounce duration.
|
|
21
|
+
* - `onChange` fires immediately; `onDebouncedChange` fires after debounce.
|
|
22
|
+
* - Accepts standard input attributes for form integration.
|
|
23
|
+
*
|
|
24
|
+
* @param props - Component props
|
|
25
|
+
*/
|
|
26
|
+
declare function DebouncedInput({ value, defaultValue, onChange, onDebouncedChange, delay, ...rest }: DebouncedInputProps): JSX.Element;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Accessible show/hide password input.
|
|
30
|
+
*
|
|
31
|
+
* - Controlled/uncontrolled value.
|
|
32
|
+
* - Accessible toggle button labels.
|
|
33
|
+
* - Keyboard accessible; no external UI framework dependency.
|
|
34
|
+
*
|
|
35
|
+
* @param props - Component props
|
|
36
|
+
*/
|
|
37
|
+
declare function PasswordInput({ value, defaultValue, onChange, label, placeholder, disabled, required, name, id, showToggle, className, }: PasswordInputProps): JSX.Element;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Conditionally render content based on a media query.
|
|
41
|
+
*
|
|
42
|
+
* SSR-safe: on the server and before mount, renders `fallback` (or null),
|
|
43
|
+
* avoiding hydration mismatches. After mount, renders `children(matches)` when
|
|
44
|
+
* the query matches, otherwise `fallback`.
|
|
45
|
+
*
|
|
46
|
+
* @param props - Component props
|
|
47
|
+
*/
|
|
48
|
+
declare function Responsive({ children, query, fallback }: ResponsiveProps): JSX.Element;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* React error boundary.
|
|
52
|
+
*
|
|
53
|
+
* Captures descendant rendering errors, supports a fallback UI (element or
|
|
54
|
+
* render function receiving the error and React error info), an optional
|
|
55
|
+
* onError callback, and optional reset via `resetKeys` changes.
|
|
56
|
+
*
|
|
57
|
+
* Limitations: does not catch errors in event handlers, asynchronous code
|
|
58
|
+
* (setTimeout, promises), or server-side rendering. Those must be handled
|
|
59
|
+
* separately (try/catch, error-first callbacks, etc.).
|
|
60
|
+
*
|
|
61
|
+
* @param props - Component props
|
|
62
|
+
*/
|
|
63
|
+
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
64
|
+
state: ErrorBoundaryState;
|
|
65
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
66
|
+
componentDidCatch(error: Error, info: React.ErrorInfo): void;
|
|
67
|
+
componentDidUpdate(prevProps: ErrorBoundaryProps): void;
|
|
68
|
+
render(): ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { CopyButton, DebouncedInput, ErrorBoundary, PasswordInput, Responsive };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/components/CopyButton/index.tsx
|
|
7
|
+
function useLatest(value) {
|
|
8
|
+
const ref = react.useRef(value);
|
|
9
|
+
ref.current = value;
|
|
10
|
+
return ref;
|
|
11
|
+
}
|
|
12
|
+
function useDebouncedValue(value, options = {}) {
|
|
13
|
+
const { delay = 300, leading = false } = options;
|
|
14
|
+
const [debounced, setDebounced] = react.useState(value);
|
|
15
|
+
const latest = useLatest(value);
|
|
16
|
+
const timerRef = react.useRef(null);
|
|
17
|
+
const lastLeadingRef = react.useRef(void 0);
|
|
18
|
+
react.useEffect(() => {
|
|
19
|
+
if (timerRef.current !== null) {
|
|
20
|
+
clearTimeout(timerRef.current);
|
|
21
|
+
timerRef.current = null;
|
|
22
|
+
}
|
|
23
|
+
timerRef.current = setTimeout(() => {
|
|
24
|
+
setDebounced(latest.current);
|
|
25
|
+
timerRef.current = null;
|
|
26
|
+
}, delay);
|
|
27
|
+
return () => {
|
|
28
|
+
if (timerRef.current !== null) {
|
|
29
|
+
clearTimeout(timerRef.current);
|
|
30
|
+
timerRef.current = null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}, [value, delay]);
|
|
34
|
+
react.useEffect(() => {
|
|
35
|
+
if (!leading) return;
|
|
36
|
+
const current = latest.current;
|
|
37
|
+
if (lastLeadingRef.current !== current) {
|
|
38
|
+
lastLeadingRef.current = current;
|
|
39
|
+
setDebounced(current);
|
|
40
|
+
}
|
|
41
|
+
}, [value, leading]);
|
|
42
|
+
return debounced;
|
|
43
|
+
}
|
|
44
|
+
function useMediaQuery(query) {
|
|
45
|
+
const [matches, setMatches] = react.useState(false);
|
|
46
|
+
react.useEffect(() => {
|
|
47
|
+
if (typeof window === "undefined") return;
|
|
48
|
+
const media = window.matchMedia(query);
|
|
49
|
+
setMatches(media.matches);
|
|
50
|
+
const handler = (event) => setMatches(event.matches);
|
|
51
|
+
media.addEventListener("change", handler);
|
|
52
|
+
return () => {
|
|
53
|
+
media.removeEventListener("change", handler);
|
|
54
|
+
};
|
|
55
|
+
}, [query]);
|
|
56
|
+
return matches;
|
|
57
|
+
}
|
|
58
|
+
function useClipboard() {
|
|
59
|
+
const isSupported = typeof navigator !== "undefined" && !!navigator.clipboard && !!navigator.clipboard.writeText;
|
|
60
|
+
const copy = react.useCallback(async (text) => {
|
|
61
|
+
try {
|
|
62
|
+
if (isSupported && typeof navigator !== "undefined") {
|
|
63
|
+
await navigator.clipboard.writeText(text);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (typeof document !== "undefined") {
|
|
67
|
+
const textarea = document.createElement("textarea");
|
|
68
|
+
textarea.value = text;
|
|
69
|
+
textarea.setAttribute("readonly", "");
|
|
70
|
+
textarea.style.position = "absolute";
|
|
71
|
+
textarea.style.position = "fixed";
|
|
72
|
+
textarea.style.opacity = "0";
|
|
73
|
+
document.body.appendChild(textarea);
|
|
74
|
+
textarea.select();
|
|
75
|
+
try {
|
|
76
|
+
document.execCommand("copy");
|
|
77
|
+
} finally {
|
|
78
|
+
document.body.removeChild(textarea);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}, [isSupported]);
|
|
87
|
+
return { copy, isSupported };
|
|
88
|
+
}
|
|
89
|
+
function CopyButton({
|
|
90
|
+
text,
|
|
91
|
+
label = "Copy",
|
|
92
|
+
copyingLabel = "Copying...",
|
|
93
|
+
successLabel = "Copied!",
|
|
94
|
+
errorLabel = "Error",
|
|
95
|
+
className,
|
|
96
|
+
onCopy,
|
|
97
|
+
onError,
|
|
98
|
+
timeout = 2e3
|
|
99
|
+
}) {
|
|
100
|
+
const [status, setStatus] = react.useState("idle");
|
|
101
|
+
const { copy } = useClipboard();
|
|
102
|
+
const handleCopy = react.useCallback(async () => {
|
|
103
|
+
setStatus("copying");
|
|
104
|
+
try {
|
|
105
|
+
const ok = await copy(text);
|
|
106
|
+
if (ok) {
|
|
107
|
+
setStatus("success");
|
|
108
|
+
onCopy?.(text);
|
|
109
|
+
} else {
|
|
110
|
+
setStatus("error");
|
|
111
|
+
onError?.(new Error("Failed to copy to clipboard"));
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
setStatus("error");
|
|
115
|
+
onError?.(err);
|
|
116
|
+
}
|
|
117
|
+
setTimeout(() => setStatus("idle"), timeout);
|
|
118
|
+
}, [text, copy, onCopy, onError, timeout]);
|
|
119
|
+
const displayLabel = status === "copying" ? copyingLabel : status === "success" ? successLabel : status === "error" ? errorLabel : label;
|
|
120
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
121
|
+
"button",
|
|
122
|
+
{
|
|
123
|
+
type: "button",
|
|
124
|
+
className,
|
|
125
|
+
disabled: status === "copying",
|
|
126
|
+
onClick: handleCopy,
|
|
127
|
+
"aria-label": status === "success" ? "Copied to clipboard" : label,
|
|
128
|
+
"aria-pressed": status === "success",
|
|
129
|
+
children: displayLabel
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function DebouncedInput({
|
|
134
|
+
value,
|
|
135
|
+
defaultValue,
|
|
136
|
+
onChange,
|
|
137
|
+
onDebouncedChange,
|
|
138
|
+
delay = 300,
|
|
139
|
+
...rest
|
|
140
|
+
}) {
|
|
141
|
+
const [internalValue, setInternalValue] = react.useState(defaultValue ?? "");
|
|
142
|
+
const isControlled = value !== void 0;
|
|
143
|
+
const currentValue = isControlled ? value ?? "" : internalValue;
|
|
144
|
+
const handleChange = react.useCallback(
|
|
145
|
+
(event) => {
|
|
146
|
+
const next = event.target.value;
|
|
147
|
+
if (!isControlled) setInternalValue(next);
|
|
148
|
+
onChange?.(next);
|
|
149
|
+
},
|
|
150
|
+
[isControlled, onChange]
|
|
151
|
+
);
|
|
152
|
+
const debounced = useDebouncedValue(currentValue, { delay });
|
|
153
|
+
const initialized = react.useRef(false);
|
|
154
|
+
react.useEffect(() => {
|
|
155
|
+
if (!initialized.current) {
|
|
156
|
+
initialized.current = true;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
onDebouncedChange?.(debounced);
|
|
160
|
+
}, [debounced, onDebouncedChange]);
|
|
161
|
+
return /* @__PURE__ */ jsxRuntime.jsx("input", { ...rest, value: currentValue, onChange: handleChange });
|
|
162
|
+
}
|
|
163
|
+
function PasswordInput({
|
|
164
|
+
value,
|
|
165
|
+
defaultValue,
|
|
166
|
+
onChange,
|
|
167
|
+
label = "Password",
|
|
168
|
+
placeholder,
|
|
169
|
+
disabled = false,
|
|
170
|
+
required = false,
|
|
171
|
+
name,
|
|
172
|
+
id,
|
|
173
|
+
showToggle = true,
|
|
174
|
+
className
|
|
175
|
+
}) {
|
|
176
|
+
const [show, setShow] = react.useState(false);
|
|
177
|
+
const [internalValue, setInternalValue] = react.useState(defaultValue ?? "");
|
|
178
|
+
const isControlled = value !== void 0;
|
|
179
|
+
const currentValue = isControlled ? value ?? "" : internalValue;
|
|
180
|
+
const handleChange = react.useCallback(
|
|
181
|
+
(event) => {
|
|
182
|
+
const next = event.target.value;
|
|
183
|
+
if (!isControlled) setInternalValue(next);
|
|
184
|
+
onChange?.(next);
|
|
185
|
+
},
|
|
186
|
+
[isControlled, onChange]
|
|
187
|
+
);
|
|
188
|
+
const toggle = react.useCallback(() => setShow((current) => !current), []);
|
|
189
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
|
|
190
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, children: label }),
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
192
|
+
"input",
|
|
193
|
+
{
|
|
194
|
+
id,
|
|
195
|
+
name,
|
|
196
|
+
type: show ? "text" : "password",
|
|
197
|
+
value: currentValue,
|
|
198
|
+
onChange: handleChange,
|
|
199
|
+
placeholder,
|
|
200
|
+
disabled,
|
|
201
|
+
required,
|
|
202
|
+
"aria-label": label
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
showToggle && /* @__PURE__ */ jsxRuntime.jsx(
|
|
206
|
+
"button",
|
|
207
|
+
{
|
|
208
|
+
type: "button",
|
|
209
|
+
onClick: toggle,
|
|
210
|
+
"aria-label": show ? "Hide password" : "Show password",
|
|
211
|
+
"aria-pressed": show,
|
|
212
|
+
children: show ? "Hide password" : "Show password"
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
function Responsive({ children, query, fallback }) {
|
|
218
|
+
const matches = useMediaQuery(query);
|
|
219
|
+
const renderChildren = react.useCallback(
|
|
220
|
+
(m) => m ? children(m) : fallback ?? null,
|
|
221
|
+
[children, fallback]
|
|
222
|
+
);
|
|
223
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderChildren(matches) });
|
|
224
|
+
}
|
|
225
|
+
var ErrorBoundary = class extends react.Component {
|
|
226
|
+
constructor() {
|
|
227
|
+
super(...arguments);
|
|
228
|
+
this.state = { hasError: false, error: null, info: null };
|
|
229
|
+
}
|
|
230
|
+
static getDerivedStateFromError(error) {
|
|
231
|
+
return { hasError: true, error, info: null };
|
|
232
|
+
}
|
|
233
|
+
componentDidCatch(error, info) {
|
|
234
|
+
this.setState({ info });
|
|
235
|
+
this.props.onError?.(error, info);
|
|
236
|
+
}
|
|
237
|
+
componentDidUpdate(prevProps) {
|
|
238
|
+
if (this.props.resetKeys !== prevProps.resetKeys) {
|
|
239
|
+
const prev = JSON.stringify(prevProps.resetKeys);
|
|
240
|
+
const next = JSON.stringify(this.props.resetKeys);
|
|
241
|
+
if (prev !== next) {
|
|
242
|
+
this.setState({ hasError: false, error: null, info: null });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
render() {
|
|
247
|
+
if (this.state.hasError) {
|
|
248
|
+
const { fallback } = this.props;
|
|
249
|
+
if (fallback === void 0) return null;
|
|
250
|
+
if (typeof fallback === "function") {
|
|
251
|
+
return fallback(
|
|
252
|
+
this.state.error,
|
|
253
|
+
this.state.info
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return fallback;
|
|
257
|
+
}
|
|
258
|
+
return this.props.children;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
exports.CopyButton = CopyButton;
|
|
263
|
+
exports.DebouncedInput = DebouncedInput;
|
|
264
|
+
exports.ErrorBoundary = ErrorBoundary;
|
|
265
|
+
exports.PasswordInput = PasswordInput;
|
|
266
|
+
exports.Responsive = Responsive;
|
|
267
|
+
//# sourceMappingURL=index.js.map
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/useLatest.ts","../../src/hooks/useDebouncedValue.ts","../../src/hooks/useMediaQuery.ts","../../src/hooks/useClipboard.ts","../../src/components/CopyButton/index.tsx","../../src/components/DebouncedInput/index.tsx","../../src/components/PasswordInput/index.tsx","../../src/components/Responsive/index.tsx","../../src/components/ErrorBoundary/index.tsx"],"names":["useRef","useState","useEffect","useCallback","jsx","jsxs","Fragment","Component"],"mappings":"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","file":"index.js","sourcesContent":["import { useRef } from \"react\";\r\n\r\n/**\r\n * Returns a ref whose `.current` always holds the latest value without\r\n * triggering re-renders. Useful for accessing the most recent value inside\r\n * timers, callbacks or event listeners without stale closures.\r\n *\r\n * @param value - The value to track\r\n */\r\nexport function useLatest<T>(value: T): { current: T } {\r\n const ref = useRef(value);\r\n ref.current = value;\r\n return ref;\r\n}\r\n","import { useEffect, useRef, useState } from \"react\";\r\nimport { useLatest } from \"./useLatest\";\r\nimport type { DebouncedValueOptions } from \"../types\";\r\n\r\n/**\r\n * Debounce a value.\r\n *\r\n * Returns the value after it has stopped changing for `delay` milliseconds.\r\n * Uses reference equality to detect changes; for objects/arrays pass a new\r\n * reference only when you want a debounce to restart.\r\n *\r\n * LOGX provides function debouncing (@mwakidenis/logx/function). This hook\r\n * implements value debouncing (a React-specific behaviour) on top of timers;\r\n * it does not map to an existing LOGX function.\r\n *\r\n * @param value - The value to debounce\r\n * @param options - delay (ms, default 300) and leading (default false)\r\n */\r\nexport function useDebouncedValue<T>(\r\n value: T,\r\n options: DebouncedValueOptions = {},\r\n): T {\r\n const { delay = 300, leading = false } = options;\r\n const [debounced, setDebounced] = useState(value);\r\n const latest = useLatest(value);\r\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const lastLeadingRef = useRef<T | undefined>(undefined);\r\n\r\n useEffect(() => {\r\n if (timerRef.current !== null) {\r\n clearTimeout(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n timerRef.current = setTimeout(() => {\r\n setDebounced(latest.current);\r\n timerRef.current = null;\r\n }, delay);\r\n return () => {\r\n if (timerRef.current !== null) {\r\n clearTimeout(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n };\r\n }, [value, delay]);\r\n\r\n useEffect(() => {\r\n if (!leading) return;\r\n const current = latest.current;\r\n if (lastLeadingRef.current !== current) {\r\n lastLeadingRef.current = current;\r\n setDebounced(current);\r\n }\r\n }, [value, leading]);\r\n\r\n return debounced;\r\n}\r\n","import { useEffect, useState } from \"react\";\r\n\r\n/**\r\n * Track whether a media query matches.\r\n *\r\n * SSR-safe: returns `false` on the server and before mount, then reflects the\r\n * real match status after mount. Listens for `change` events and cleans up on\r\n * unmount.\r\n *\r\n * @param query - A CSS media query string\r\n */\r\nexport function useMediaQuery(query: string): boolean {\r\n const [matches, setMatches] = useState(false);\r\n\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n const media = window.matchMedia(query);\r\n setMatches(media.matches);\r\n const handler = (event: MediaQueryListEvent) => setMatches(event.matches);\r\n media.addEventListener(\"change\", handler);\r\n return () => {\r\n media.removeEventListener(\"change\", handler);\r\n };\r\n }, [query]);\r\n\r\n return matches;\r\n}\r\n","import { useCallback } from \"react\";\r\n\r\n/**\r\n * Copy text to the clipboard.\r\n *\r\n * Uses the Clipboard API when available, with a textarea fallback for older\r\n * browsers. Never throws; returns false on failure.\r\n */\r\nexport function useClipboard(): {\r\n copy: (text: string) => Promise<boolean>;\r\n isSupported: boolean;\r\n} {\r\n const isSupported =\r\n typeof navigator !== \"undefined\" && !!navigator.clipboard && !!navigator.clipboard.writeText;\r\n\r\n const copy = useCallback(async (text: string): Promise<boolean> => {\r\n try {\r\n if (isSupported && typeof navigator !== \"undefined\") {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n }\r\n // Fallback for environments without the Clipboard API.\r\n if (typeof document !== \"undefined\") {\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.value = text;\r\n textarea.setAttribute(\"readonly\", \"\");\r\n textarea.style.position = \"absolute\";\r\n textarea.style.position = \"fixed\";\r\n textarea.style.opacity = \"0\";\r\n document.body.appendChild(textarea);\r\n textarea.select();\r\n try {\r\n document.execCommand(\"copy\");\r\n } finally {\r\n document.body.removeChild(textarea);\r\n }\r\n return true;\r\n }\r\n return false;\r\n } catch {\r\n return false;\r\n }\r\n }, [isSupported]);\r\n\r\n return { copy, isSupported };\r\n}\r\n","import { useCallback, useState } from \"react\";\r\nimport { useClipboard } from \"../../hooks\";\r\nimport type { CopyButtonProps } from \"../../types\";\r\n\r\n/**\r\n * Accessible copy-to-clipboard button.\r\n *\r\n * - Copies the supplied `text` to the clipboard.\r\n * - Shows idle / copying / success / error states.\r\n * - Supports custom labels per state and a custom className.\r\n * - Keyboard accessible (native button). No icon library dependency.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function CopyButton({\r\n text,\r\n label = \"Copy\",\r\n copyingLabel = \"Copying...\",\r\n successLabel = \"Copied!\",\r\n errorLabel = \"Error\",\r\n className,\r\n onCopy,\r\n onError,\r\n timeout = 2000,\r\n}: CopyButtonProps): JSX.Element {\r\n const [status, setStatus] = useState<\"idle\" | \"copying\" | \"success\" | \"error\">(\"idle\");\r\n const { copy } = useClipboard();\r\n\r\n const handleCopy = useCallback(async () => {\r\n setStatus(\"copying\");\r\n try {\r\n const ok = await copy(text);\r\n if (ok) {\r\n setStatus(\"success\");\r\n onCopy?.(text);\r\n } else {\r\n setStatus(\"error\");\r\n onError?.(new Error(\"Failed to copy to clipboard\"));\r\n }\r\n } catch (err) {\r\n setStatus(\"error\");\r\n onError?.(err as Error);\r\n }\r\n setTimeout(() => setStatus(\"idle\"), timeout);\r\n }, [text, copy, onCopy, onError, timeout]);\r\n\r\n const displayLabel =\r\n status === \"copying\"\r\n ? copyingLabel\r\n : status === \"success\"\r\n ? successLabel\r\n : status === \"error\"\r\n ? errorLabel\r\n : label;\r\n\r\n return (\r\n <button\r\n type=\"button\"\r\n className={className}\r\n disabled={status === \"copying\"}\r\n onClick={handleCopy}\r\n aria-label={status === \"success\" ? \"Copied to clipboard\" : label}\r\n aria-pressed={status === \"success\"}\r\n >\r\n {displayLabel}\r\n </button>\r\n );\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { ChangeEvent } from \"react\";\r\nimport { useDebouncedValue } from \"../../hooks\";\r\nimport type { DebouncedInputProps } from \"../../types\";\r\n\r\n/**\r\n * Controlled input with a separate debounced value.\r\n *\r\n * - `value`/`defaultValue` for controlled/uncontrolled usage.\r\n * - `delay` controls debounce duration.\r\n * - `onChange` fires immediately; `onDebouncedChange` fires after debounce.\r\n * - Accepts standard input attributes for form integration.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function DebouncedInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n onDebouncedChange,\r\n delay = 300,\r\n ...rest\r\n}: DebouncedInputProps): JSX.Element {\r\n const [internalValue, setInternalValue] = useState(defaultValue ?? \"\");\r\n const isControlled = value !== undefined;\r\n const currentValue = isControlled ? (value ?? \"\") : internalValue;\r\n\r\n const handleChange = useCallback(\r\n (event: ChangeEvent<HTMLInputElement>) => {\r\n const next = event.target.value;\r\n if (!isControlled) setInternalValue(next);\r\n onChange?.(next);\r\n },\r\n [isControlled, onChange],\r\n );\r\n\r\n const debounced = useDebouncedValue(currentValue, { delay });\r\n const initialized = useRef(false);\r\n\r\n useEffect(() => {\r\n if (!initialized.current) {\r\n initialized.current = true;\r\n return;\r\n }\r\n onDebouncedChange?.(debounced);\r\n }, [debounced, onDebouncedChange]);\r\n\r\n return <input {...rest} value={currentValue} onChange={handleChange} />;\r\n}\r\n","import { useCallback, useState } from \"react\";\r\nimport type { ChangeEvent } from \"react\";\r\nimport type { PasswordInputProps } from \"../../types\";\r\n\r\n/**\r\n * Accessible show/hide password input.\r\n *\r\n * - Controlled/uncontrolled value.\r\n * - Accessible toggle button labels.\r\n * - Keyboard accessible; no external UI framework dependency.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function PasswordInput({\r\n value,\r\n defaultValue,\r\n onChange,\r\n label = \"Password\",\r\n placeholder,\r\n disabled = false,\r\n required = false,\r\n name,\r\n id,\r\n showToggle = true,\r\n className,\r\n}: PasswordInputProps): JSX.Element {\r\n const [show, setShow] = useState(false);\r\n const [internalValue, setInternalValue] = useState(defaultValue ?? \"\");\r\n const isControlled = value !== undefined;\r\n const currentValue = isControlled ? (value ?? \"\") : internalValue;\r\n\r\n const handleChange = useCallback(\r\n (event: ChangeEvent<HTMLInputElement>) => {\r\n const next = event.target.value;\r\n if (!isControlled) setInternalValue(next);\r\n onChange?.(next);\r\n },\r\n [isControlled, onChange],\r\n );\r\n\r\n const toggle = useCallback(() => setShow((current) => !current), []);\r\n\r\n return (\r\n <div className={className}>\r\n {label && <label htmlFor={id}>{label}</label>}\r\n <input\r\n id={id}\r\n name={name}\r\n type={show ? \"text\" : \"password\"}\r\n value={currentValue}\r\n onChange={handleChange}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n required={required}\r\n aria-label={label}\r\n />\r\n {showToggle && (\r\n <button\r\n type=\"button\"\r\n onClick={toggle}\r\n aria-label={show ? \"Hide password\" : \"Show password\"}\r\n aria-pressed={show}\r\n >\r\n {show ? \"Hide password\" : \"Show password\"}\r\n </button>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useCallback } from \"react\";\r\nimport { useMediaQuery } from \"../../hooks\";\r\nimport type { ResponsiveProps } from \"../../types\";\r\n\r\n/**\r\n * Conditionally render content based on a media query.\r\n *\r\n * SSR-safe: on the server and before mount, renders `fallback` (or null),\r\n * avoiding hydration mismatches. After mount, renders `children(matches)` when\r\n * the query matches, otherwise `fallback`.\r\n *\r\n * @param props - Component props\r\n */\r\nexport function Responsive({ children, query, fallback }: ResponsiveProps): JSX.Element {\r\n const matches = useMediaQuery(query);\r\n const renderChildren = useCallback(\r\n (m: boolean) => (m ? children(m) : fallback ?? null),\r\n [children, fallback],\r\n );\r\n return <>{renderChildren(matches)}</>;\r\n}\r\n","import { Component, ReactNode } from \"react\";\r\nimport type { ErrorBoundaryProps, ErrorBoundaryState } from \"../../types\";\r\n\r\n/**\r\n * React error boundary.\r\n *\r\n * Captures descendant rendering errors, supports a fallback UI (element or\r\n * render function receiving the error and React error info), an optional\r\n * onError callback, and optional reset via `resetKeys` changes.\r\n *\r\n * Limitations: does not catch errors in event handlers, asynchronous code\r\n * (setTimeout, promises), or server-side rendering. Those must be handled\r\n * separately (try/catch, error-first callbacks, etc.).\r\n *\r\n * @param props - Component props\r\n */\r\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\r\n state: ErrorBoundaryState = { hasError: false, error: null, info: null };\r\n\r\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\r\n return { hasError: true, error, info: null };\r\n }\r\n\r\n componentDidCatch(error: Error, info: React.ErrorInfo): void {\r\n this.setState({ info });\r\n this.props.onError?.(error, info);\r\n }\r\n\r\n componentDidUpdate(prevProps: ErrorBoundaryProps): void {\r\n if (this.props.resetKeys !== prevProps.resetKeys) {\r\n const prev = JSON.stringify(prevProps.resetKeys);\r\n const next = JSON.stringify(this.props.resetKeys);\r\n if (prev !== next) {\r\n this.setState({ hasError: false, error: null, info: null });\r\n }\r\n }\r\n }\r\n\r\n render(): ReactNode {\r\n if (this.state.hasError) {\r\n const { fallback } = this.props;\r\n if (fallback === undefined) return null;\r\n if (typeof fallback === \"function\") {\r\n return (fallback as (error: Error, info: React.ErrorInfo) => ReactNode)(\r\n this.state.error!,\r\n this.state.info!,\r\n );\r\n }\r\n return fallback;\r\n }\r\n return this.props.children;\r\n }\r\n}\r\n"]}
|