@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
|
@@ -1,70 +1,15 @@
|
|
|
1
1
|
import { Component, type ReactNode } from 'react';
|
|
2
|
-
import type { ErrorBoundaryProps } from '../../types';
|
|
2
|
+
import type { ErrorBoundaryProps } from '../../types.js';
|
|
3
3
|
interface ErrorBoundaryState {
|
|
4
4
|
hasError: boolean;
|
|
5
5
|
error: Error | null;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
* ErrorBoundary component - Catches JavaScript errors in child components
|
|
9
|
-
*
|
|
10
|
-
* This component wraps your application (or part of it) to catch runtime errors
|
|
11
|
-
* and display a fallback UI instead of crashing the entire app.
|
|
12
|
-
*
|
|
13
|
-
* Features:
|
|
14
|
-
* - Catches errors in child component tree
|
|
15
|
-
* - Displays custom fallback UI
|
|
16
|
-
* - Optional error callback for logging
|
|
17
|
-
* - Follows React error boundary best practices
|
|
18
|
-
*
|
|
19
|
-
* @example Basic usage
|
|
20
|
-
* ```tsx
|
|
21
|
-
* import { ErrorBoundary } from '@teo-garcia/react-shared/components'
|
|
22
|
-
*
|
|
23
|
-
* function App() {
|
|
24
|
-
* return (
|
|
25
|
-
* <ErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
26
|
-
* <YourApp />
|
|
27
|
-
* </ErrorBoundary>
|
|
28
|
-
* )
|
|
29
|
-
* }
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @example With dynamic fallback
|
|
33
|
-
* ```tsx
|
|
34
|
-
* import { ErrorBoundary } from '@teo-garcia/react-shared/components'
|
|
35
|
-
*
|
|
36
|
-
* function App() {
|
|
37
|
-
* return (
|
|
38
|
-
* <ErrorBoundary
|
|
39
|
-
* fallback={(error) => (
|
|
40
|
-
* <div>
|
|
41
|
-
* <h1>Error: {error.message}</h1>
|
|
42
|
-
* <button onClick={() => window.location.reload()}>Reload</button>
|
|
43
|
-
* </div>
|
|
44
|
-
* )}
|
|
45
|
-
* onError={(error, errorInfo) => {
|
|
46
|
-
* console.error('Error caught:', error, errorInfo)
|
|
47
|
-
* // Send to error tracking service
|
|
48
|
-
* }}
|
|
49
|
-
* >
|
|
50
|
-
* <YourApp />
|
|
51
|
-
* </ErrorBoundary>
|
|
52
|
-
* )
|
|
53
|
-
* }
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
7
|
export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
57
8
|
constructor(props: ErrorBoundaryProps);
|
|
58
|
-
/**
|
|
59
|
-
* Static method called when an error is thrown in a child component
|
|
60
|
-
* Updates state to trigger fallback UI rendering
|
|
61
|
-
*/
|
|
62
9
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
63
|
-
/**
|
|
64
|
-
* Called after an error is caught
|
|
65
|
-
* Used for side effects like logging
|
|
66
|
-
*/
|
|
67
10
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
|
|
11
|
+
componentDidUpdate(prevProps: ErrorBoundaryProps): void;
|
|
12
|
+
resetError(): void;
|
|
68
13
|
render(): ReactNode;
|
|
69
14
|
}
|
|
70
15
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../../src/components/error-boundary/error-boundary.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjD,OAAO,KAAK,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../../src/components/error-boundary/error-boundary.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,gBAAgB,CAAA;AAEvE,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CACpB;AAED,qBAAa,aAAc,SAAQ,SAAS,CAC1C,kBAAkB,EAClB,kBAAkB,CACnB;gBACa,KAAK,EAAE,kBAAkB;IAMrC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI;IAIjE,kBAAkB,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI;IAQvD,UAAU,IAAI,IAAI;IAKlB,MAAM,IAAI,SAAS;CA2EpB"}
|
|
@@ -1,100 +1,74 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Component } from 'react';
|
|
4
|
-
/**
|
|
5
|
-
* ErrorBoundary component - Catches JavaScript errors in child components
|
|
6
|
-
*
|
|
7
|
-
* This component wraps your application (or part of it) to catch runtime errors
|
|
8
|
-
* and display a fallback UI instead of crashing the entire app.
|
|
9
|
-
*
|
|
10
|
-
* Features:
|
|
11
|
-
* - Catches errors in child component tree
|
|
12
|
-
* - Displays custom fallback UI
|
|
13
|
-
* - Optional error callback for logging
|
|
14
|
-
* - Follows React error boundary best practices
|
|
15
|
-
*
|
|
16
|
-
* @example Basic usage
|
|
17
|
-
* ```tsx
|
|
18
|
-
* import { ErrorBoundary } from '@teo-garcia/react-shared/components'
|
|
19
|
-
*
|
|
20
|
-
* function App() {
|
|
21
|
-
* return (
|
|
22
|
-
* <ErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
23
|
-
* <YourApp />
|
|
24
|
-
* </ErrorBoundary>
|
|
25
|
-
* )
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @example With dynamic fallback
|
|
30
|
-
* ```tsx
|
|
31
|
-
* import { ErrorBoundary } from '@teo-garcia/react-shared/components'
|
|
32
|
-
*
|
|
33
|
-
* function App() {
|
|
34
|
-
* return (
|
|
35
|
-
* <ErrorBoundary
|
|
36
|
-
* fallback={(error) => (
|
|
37
|
-
* <div>
|
|
38
|
-
* <h1>Error: {error.message}</h1>
|
|
39
|
-
* <button onClick={() => window.location.reload()}>Reload</button>
|
|
40
|
-
* </div>
|
|
41
|
-
* )}
|
|
42
|
-
* onError={(error, errorInfo) => {
|
|
43
|
-
* console.error('Error caught:', error, errorInfo)
|
|
44
|
-
* // Send to error tracking service
|
|
45
|
-
* }}
|
|
46
|
-
* >
|
|
47
|
-
* <YourApp />
|
|
48
|
-
* </ErrorBoundary>
|
|
49
|
-
* )
|
|
50
|
-
* }
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
4
|
export class ErrorBoundary extends Component {
|
|
54
5
|
constructor(props) {
|
|
55
6
|
super(props);
|
|
56
7
|
this.state = { hasError: false, error: null };
|
|
8
|
+
this.resetError = this.resetError.bind(this);
|
|
57
9
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Static method called when an error is thrown in a child component
|
|
60
|
-
* Updates state to trigger fallback UI rendering
|
|
61
|
-
*/
|
|
62
10
|
static getDerivedStateFromError(error) {
|
|
63
11
|
return { hasError: true, error };
|
|
64
12
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Called after an error is caught
|
|
67
|
-
* Used for side effects like logging
|
|
68
|
-
*/
|
|
69
13
|
componentDidCatch(error, errorInfo) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
14
|
+
this.props.onError?.(error, errorInfo);
|
|
15
|
+
}
|
|
16
|
+
componentDidUpdate(prevProps) {
|
|
17
|
+
if (!this.state.hasError)
|
|
18
|
+
return;
|
|
19
|
+
const prevKeys = prevProps.resetKeys ?? [];
|
|
20
|
+
const nextKeys = this.props.resetKeys ?? [];
|
|
21
|
+
const changed = nextKeys.some((key, i) => key !== prevKeys[i]);
|
|
22
|
+
if (changed)
|
|
23
|
+
this.resetError();
|
|
24
|
+
}
|
|
25
|
+
resetError() {
|
|
26
|
+
this.props.onReset?.();
|
|
27
|
+
this.setState({ hasError: false, error: null });
|
|
74
28
|
}
|
|
75
29
|
render() {
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
const { fallback } = this.props;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
30
|
+
const { hasError, error } = this.state;
|
|
31
|
+
if (hasError && error) {
|
|
32
|
+
const { FallbackComponent, fallbackRender, fallback } = this.props;
|
|
33
|
+
const fallbackProps = {
|
|
34
|
+
error,
|
|
35
|
+
resetError: this.resetError,
|
|
36
|
+
};
|
|
37
|
+
if (FallbackComponent)
|
|
38
|
+
return _jsx(FallbackComponent, { ...fallbackProps });
|
|
39
|
+
if (fallbackRender)
|
|
40
|
+
return fallbackRender(fallbackProps);
|
|
41
|
+
if (typeof fallback === 'function')
|
|
42
|
+
return fallback(error);
|
|
43
|
+
if (fallback != null)
|
|
85
44
|
return fallback;
|
|
86
|
-
}
|
|
87
|
-
// Default fallback UI if none provided
|
|
88
45
|
return (_jsxs("div", { role: 'alert', style: {
|
|
89
|
-
padding: '
|
|
90
|
-
margin: '20px',
|
|
46
|
+
padding: '1rem',
|
|
91
47
|
border: '1px solid #f5c6cb',
|
|
92
48
|
borderRadius: '4px',
|
|
93
49
|
backgroundColor: '#f8d7da',
|
|
94
50
|
color: '#721c24',
|
|
95
|
-
|
|
51
|
+
fontFamily: 'inherit',
|
|
52
|
+
}, children: [_jsx("p", { style: { margin: 0, fontWeight: 600 }, children: "Something went wrong" }), _jsx("p", { style: { margin: '0.5rem 0 0', fontSize: '0.875rem' }, children: error.message }), error.stack && (_jsxs("details", { style: { marginTop: '0.5rem' }, children: [_jsx("summary", { style: {
|
|
53
|
+
cursor: 'pointer',
|
|
54
|
+
fontSize: '0.875rem',
|
|
55
|
+
userSelect: 'none',
|
|
56
|
+
}, children: "Stack trace" }), _jsx("pre", { style: {
|
|
57
|
+
marginTop: '0.5rem',
|
|
58
|
+
whiteSpace: 'pre-wrap',
|
|
59
|
+
fontSize: '0.75rem',
|
|
60
|
+
overflowX: 'auto',
|
|
61
|
+
}, children: error.stack })] })), _jsx("button", { onClick: this.resetError, style: {
|
|
62
|
+
marginTop: '0.75rem',
|
|
63
|
+
padding: '0.25rem 0.75rem',
|
|
64
|
+
cursor: 'pointer',
|
|
65
|
+
fontSize: '0.875rem',
|
|
66
|
+
border: '1px solid #721c24',
|
|
67
|
+
borderRadius: '4px',
|
|
68
|
+
background: 'transparent',
|
|
69
|
+
color: '#721c24',
|
|
70
|
+
}, children: "Try again" })] }));
|
|
96
71
|
}
|
|
97
|
-
// No error, render children normally
|
|
98
72
|
return this.props.children;
|
|
99
73
|
}
|
|
100
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/error-boundary/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/error-boundary/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
interface FocusTrapProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/** Whether the trap is active. Defaults to `true`. */
|
|
5
|
+
active?: boolean;
|
|
6
|
+
/** Focus the first focusable element on activation. Defaults to `true`. */
|
|
7
|
+
initialFocus?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Traps keyboard focus within its container while active.
|
|
11
|
+
* Restores focus to the previously focused element on deactivation or unmount.
|
|
12
|
+
* Required for accessible modals, dialogs, and drawers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function FocusTrap({ children, active, initialFocus, }: FocusTrapProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=focus-trap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-trap.d.ts","sourceRoot":"","sources":["../../../src/components/focus-trap/focus-trap.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAYzD,UAAU,cAAc;IACtB,QAAQ,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,MAAa,EACb,YAAmB,GACpB,EAAE,cAAc,2CA8ChB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
const FOCUSABLE_SELECTORS = [
|
|
5
|
+
'a[href]',
|
|
6
|
+
'area[href]',
|
|
7
|
+
'button:not([disabled])',
|
|
8
|
+
'input:not([disabled])',
|
|
9
|
+
'select:not([disabled])',
|
|
10
|
+
'textarea:not([disabled])',
|
|
11
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
12
|
+
].join(', ');
|
|
13
|
+
/**
|
|
14
|
+
* Traps keyboard focus within its container while active.
|
|
15
|
+
* Restores focus to the previously focused element on deactivation or unmount.
|
|
16
|
+
* Required for accessible modals, dialogs, and drawers.
|
|
17
|
+
*/
|
|
18
|
+
export function FocusTrap({ children, active = true, initialFocus = true, }) {
|
|
19
|
+
const containerRef = useRef(null);
|
|
20
|
+
const previousFocusRef = useRef(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!active || !containerRef.current)
|
|
23
|
+
return;
|
|
24
|
+
previousFocusRef.current = document.activeElement;
|
|
25
|
+
const container = containerRef.current;
|
|
26
|
+
const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS));
|
|
27
|
+
if (initialFocus) {
|
|
28
|
+
getFocusable()[0]?.focus();
|
|
29
|
+
}
|
|
30
|
+
function handleKeyDown(event) {
|
|
31
|
+
if (event.key !== 'Tab')
|
|
32
|
+
return;
|
|
33
|
+
const focusable = getFocusable();
|
|
34
|
+
const first = focusable[0];
|
|
35
|
+
const last = focusable.at(-1);
|
|
36
|
+
const current = document.activeElement;
|
|
37
|
+
if (event.shiftKey) {
|
|
38
|
+
if (current === first || !container.contains(current)) {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
last?.focus();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (current === last || !container.contains(current)) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
first?.focus();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
51
|
+
return () => {
|
|
52
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
53
|
+
previousFocusRef.current?.focus();
|
|
54
|
+
};
|
|
55
|
+
}, [active, initialFocus]);
|
|
56
|
+
return _jsx("div", { ref: containerRef, children: children });
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/focus-trap/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FocusTrap } from './focus-trap.js';
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
export * from './aspect-ratio/index.js';
|
|
2
|
+
export * from './debug-json/index.js';
|
|
3
|
+
export * from './dev-panel/index.js';
|
|
1
4
|
export * from './error-boundary/index.js';
|
|
5
|
+
export * from './focus-trap/index.js';
|
|
6
|
+
export * from './portal/index.js';
|
|
7
|
+
export * from './separator/index.js';
|
|
8
|
+
export * from './skeleton/index.js';
|
|
9
|
+
export * from './skip-link/index.js';
|
|
10
|
+
export * from './visually-hidden/index.js';
|
|
2
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA"}
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
+
export * from './aspect-ratio/index.js';
|
|
2
|
+
export * from './debug-json/index.js';
|
|
3
|
+
export * from './dev-panel/index.js';
|
|
1
4
|
export * from './error-boundary/index.js';
|
|
5
|
+
export * from './focus-trap/index.js';
|
|
6
|
+
export * from './portal/index.js';
|
|
7
|
+
export * from './separator/index.js';
|
|
8
|
+
export * from './skeleton/index.js';
|
|
9
|
+
export * from './skip-link/index.js';
|
|
10
|
+
export * from './visually-hidden/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/portal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Portal } from './portal.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
interface PortalProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/** Target DOM element. Defaults to `document.body`. */
|
|
5
|
+
container?: Element | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Renders children into a DOM node outside the component tree.
|
|
9
|
+
* SSR-safe: returns null on the server and on first paint to avoid hydration
|
|
10
|
+
* mismatch.
|
|
11
|
+
*/
|
|
12
|
+
export declare function Portal({ children, container }: PortalProps): import("react").ReactPortal | null;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=portal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/components/portal/portal.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAG3D,UAAU,WAAW;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,WAAW,sCAY1D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
/**
|
|
5
|
+
* Renders children into a DOM node outside the component tree.
|
|
6
|
+
* SSR-safe: returns null on the server and on first paint to avoid hydration
|
|
7
|
+
* mismatch.
|
|
8
|
+
*/
|
|
9
|
+
export function Portal({ children, container }) {
|
|
10
|
+
const [mounted, setMounted] = useState(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
setMounted(true);
|
|
13
|
+
return () => setMounted(false);
|
|
14
|
+
}, []);
|
|
15
|
+
if (!mounted)
|
|
16
|
+
return null;
|
|
17
|
+
// null means the container element is not yet available — do not render
|
|
18
|
+
if (container === null)
|
|
19
|
+
return null;
|
|
20
|
+
return createPortal(children, container ?? document.body);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/separator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Separator } from './separator.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface SeparatorProps {
|
|
2
|
+
orientation?: 'horizontal' | 'vertical';
|
|
3
|
+
className?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Semantic separator with Tailwind styling.
|
|
7
|
+
* RSC-safe. Use `orientation="vertical"` in flex rows.
|
|
8
|
+
*/
|
|
9
|
+
export declare function Separator({ orientation, className, }: SeparatorProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=separator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"separator.d.ts","sourceRoot":"","sources":["../../../src/components/separator/separator.tsx"],"names":[],"mappings":"AAEA,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAA0B,EAC1B,SAAS,GACV,EAAE,cAAc,2CAchB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
/**
|
|
4
|
+
* Semantic separator with Tailwind styling.
|
|
5
|
+
* RSC-safe. Use `orientation="vertical"` in flex rows.
|
|
6
|
+
*/
|
|
7
|
+
export function Separator({ orientation = 'horizontal', className, }) {
|
|
8
|
+
return (_jsx("hr", { role: 'separator', "aria-orientation": orientation, className: cn(orientation === 'horizontal'
|
|
9
|
+
? 'my-2 w-full border-t'
|
|
10
|
+
: 'mx-2 h-full border-l', 'border-black/10 dark:border-white/10', className) }));
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/skeleton/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Skeleton } from './skeleton.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skeleton.d.ts","sourceRoot":"","sources":["../../../src/components/skeleton/skeleton.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3C,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,cAAc,CAAC,2CAUhC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/skip-link/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SkipLink } from './skip-link.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnchorHTMLAttributes } from 'react';
|
|
2
|
+
interface SkipLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
3
|
+
/** The `id` of the main content element to jump to, e.g. `"main-content"`. */
|
|
4
|
+
href: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Visually hidden link that appears on focus.
|
|
8
|
+
* Required by WCAG 2.4.1 — every WCAG audit flags its absence.
|
|
9
|
+
* Renders before any other content in the document.
|
|
10
|
+
*/
|
|
11
|
+
export declare function SkipLink({ href, children, ...props }: SkipLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=skip-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skip-link.d.ts","sourceRoot":"","sources":["../../../src/components/skip-link/skip-link.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE3D,UAAU,aAAc,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IACrE,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,QAAiC,EACjC,GAAG,KAAK,EACT,EAAE,aAAa,2CA4Bf"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Visually hidden link that appears on focus.
|
|
6
|
+
* Required by WCAG 2.4.1 — every WCAG audit flags its absence.
|
|
7
|
+
* Renders before any other content in the document.
|
|
8
|
+
*/
|
|
9
|
+
export function SkipLink({ href, children = 'Skip to main content', ...props }) {
|
|
10
|
+
const [focused, setFocused] = useState(false);
|
|
11
|
+
return (_jsx("a", { href: href, onFocus: () => setFocused(true), onBlur: () => setFocused(false), style: {
|
|
12
|
+
position: 'absolute',
|
|
13
|
+
top: focused ? '0.5rem' : '-100%',
|
|
14
|
+
left: '0.5rem',
|
|
15
|
+
zIndex: 9999,
|
|
16
|
+
padding: '0.5rem 1rem',
|
|
17
|
+
background: '#000',
|
|
18
|
+
color: '#fff',
|
|
19
|
+
textDecoration: 'none',
|
|
20
|
+
borderRadius: '4px',
|
|
21
|
+
fontSize: '0.875rem',
|
|
22
|
+
fontWeight: 500,
|
|
23
|
+
transition: 'top 0.1s',
|
|
24
|
+
outline: '2px solid transparent',
|
|
25
|
+
}, ...props, children: children }));
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/visually-hidden/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { VisuallyHidden } from './visually-hidden.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visually-hidden.d.ts","sourceRoot":"","sources":["../../../src/components/visually-hidden/visually-hidden.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAA;AAc3C,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,eAAe,CAAC,2CAMjC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
const style = {
|
|
3
|
+
position: 'absolute',
|
|
4
|
+
width: '1px',
|
|
5
|
+
height: '1px',
|
|
6
|
+
padding: 0,
|
|
7
|
+
margin: '-1px',
|
|
8
|
+
overflow: 'hidden',
|
|
9
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
10
|
+
whiteSpace: 'nowrap',
|
|
11
|
+
borderWidth: 0,
|
|
12
|
+
};
|
|
13
|
+
export function VisuallyHidden({ children, ...props }) {
|
|
14
|
+
return (_jsx("span", { style: style, ...props, children: children }));
|
|
15
|
+
}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
export { useCopyToClipboard } from './use-copy-to-clipboard.js';
|
|
1
2
|
export { useDebounce } from './use-debounce.js';
|
|
3
|
+
export { useEventListener } from './use-event-listener.js';
|
|
4
|
+
export { useIdle } from './use-idle.js';
|
|
5
|
+
export { useIntersectionObserver } from './use-intersection-observer.js';
|
|
2
6
|
export { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect.js';
|
|
7
|
+
export { useLatest } from './use-latest.js';
|
|
3
8
|
export { useLocalStorage } from './use-local-storage.js';
|
|
4
9
|
export { useMediaQuery } from './use-media-query.js';
|
|
10
|
+
export { useNetworkStatus } from './use-network-status.js';
|
|
5
11
|
export { useOnClickOutside } from './use-on-click-outside.js';
|
|
6
12
|
export { usePrevious } from './use-previous.js';
|
|
13
|
+
export { useRenderCount } from './use-render-count.js';
|
|
14
|
+
export { useToggle } from './use-toggle.js';
|
|
15
|
+
export { useWhyDidYouRender } from './use-why-did-you-render.js';
|
|
7
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
export { useCopyToClipboard } from './use-copy-to-clipboard.js';
|
|
1
2
|
export { useDebounce } from './use-debounce.js';
|
|
3
|
+
export { useEventListener } from './use-event-listener.js';
|
|
4
|
+
export { useIdle } from './use-idle.js';
|
|
5
|
+
export { useIntersectionObserver } from './use-intersection-observer.js';
|
|
2
6
|
export { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect.js';
|
|
7
|
+
export { useLatest } from './use-latest.js';
|
|
3
8
|
export { useLocalStorage } from './use-local-storage.js';
|
|
4
9
|
export { useMediaQuery } from './use-media-query.js';
|
|
10
|
+
export { useNetworkStatus } from './use-network-status.js';
|
|
5
11
|
export { useOnClickOutside } from './use-on-click-outside.js';
|
|
6
12
|
export { usePrevious } from './use-previous.js';
|
|
13
|
+
export { useRenderCount } from './use-render-count.js';
|
|
14
|
+
export { useToggle } from './use-toggle.js';
|
|
15
|
+
export { useWhyDidYouRender } from './use-why-did-you-render.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `[copied, copy]`.
|
|
3
|
+
* `copy(text)` writes to the clipboard and returns a boolean indicating success.
|
|
4
|
+
* `copied` resets to `false` after `resetDelay` ms.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useCopyToClipboard(resetDelay?: number): [boolean, (text: string) => Promise<boolean>];
|
|
7
|
+
//# sourceMappingURL=use-copy-to-clipboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/hooks/use-copy-to-clipboard.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,SAAO,GAChB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAoB/C"}
|