@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +23 -0
  3. package/README.md +151 -3
  4. package/dist/components/index.d.mts +71 -0
  5. package/dist/components/index.d.ts +71 -0
  6. package/dist/components/index.js +268 -0
  7. package/dist/components/index.js.map +1 -0
  8. package/dist/components/index.mjs +262 -0
  9. package/dist/components/index.mjs.map +1 -0
  10. package/dist/hooks/index.d.mts +316 -0
  11. package/dist/hooks/index.d.ts +316 -0
  12. package/dist/hooks/index.js +638 -0
  13. package/dist/hooks/index.js.map +1 -0
  14. package/dist/hooks/index.mjs +615 -0
  15. package/dist/hooks/index.mjs.map +1 -0
  16. package/dist/hooks/storage/index.d.mts +40 -0
  17. package/dist/hooks/storage/index.d.ts +40 -0
  18. package/dist/hooks/storage/index.js +119 -0
  19. package/dist/hooks/storage/index.js.map +1 -0
  20. package/dist/hooks/storage/index.mjs +116 -0
  21. package/dist/hooks/storage/index.mjs.map +1 -0
  22. package/dist/index.d.mts +16 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.js +1066 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/index.mjs +974 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/providers/LogxProvider.d.mts +134 -0
  29. package/dist/providers/LogxProvider.d.ts +134 -0
  30. package/dist/providers/LogxProvider.js +117 -0
  31. package/dist/providers/LogxProvider.js.map +1 -0
  32. package/dist/providers/LogxProvider.mjs +114 -0
  33. package/dist/providers/LogxProvider.mjs.map +1 -0
  34. package/dist/types/index.d.mts +87 -0
  35. package/dist/types/index.d.ts +87 -0
  36. package/dist/types/index.js +4 -0
  37. package/dist/types/index.js.map +1 -0
  38. package/dist/types/index.mjs +3 -0
  39. package/dist/types/index.mjs.map +1 -0
  40. package/dist/utilities/index.d.mts +140 -0
  41. package/dist/utilities/index.d.ts +140 -0
  42. package/dist/utilities/index.js +167 -0
  43. package/dist/utilities/index.js.map +1 -0
  44. package/dist/utilities/index.mjs +104 -0
  45. package/dist/utilities/index.mjs.map +1 -0
  46. 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
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ [![npm version](https://img.shields.io/npm/v/@mwakidenis/logx-react.svg)](https://www.npmjs.com/package/@mwakidenis/logx-react)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@mwakidenis/logx-react)](https://www.npmjs.com/package/@mwakidenis/logx-react)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](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"]}