@qwickapps/react-framework 1.3.1 → 1.3.3
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 +123 -1
- package/dist/components/AccessibilityProvider.d.ts +64 -0
- package/dist/components/AccessibilityProvider.d.ts.map +1 -0
- package/dist/components/Breadcrumbs.d.ts +39 -0
- package/dist/components/Breadcrumbs.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +39 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/QwickApp.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.bundled.css +12 -0
- package/dist/index.esm.js +910 -44
- package/dist/index.js +916 -47
- package/dist/templates/TemplateResolver.d.ts.map +1 -1
- package/dist/utils/htmlTransform.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +15 -3
- package/dist/utils/logger.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/components/AccessibilityProvider.tsx +466 -0
- package/src/components/Breadcrumbs.tsx +223 -0
- package/src/components/ErrorBoundary.tsx +216 -0
- package/src/components/QwickApp.tsx +17 -11
- package/src/components/__tests__/AccessibilityProvider.test.tsx +330 -0
- package/src/components/__tests__/Breadcrumbs.test.tsx +268 -0
- package/src/components/__tests__/ErrorBoundary.test.tsx +163 -0
- package/src/components/index.ts +3 -0
- package/src/stories/AccessibilityProvider.stories.tsx +284 -0
- package/src/stories/Breadcrumbs.stories.tsx +304 -0
- package/src/stories/ErrorBoundary.stories.tsx +159 -0
- package/src/stories/{form/FormComponents.stories.tsx → FormComponents.stories.tsx} +8 -8
- package/src/templates/TemplateResolver.ts +2 -6
- package/src/utils/__tests__/nested-dom-fix.test.tsx +53 -0
- package/src/utils/__tests__/optional-logging.test.ts +83 -0
- package/src/utils/htmlTransform.tsx +69 -3
- package/src/utils/logger.ts +60 -5
- package/dist/schemas/Builders.d.ts +0 -7
- package/dist/schemas/Builders.d.ts.map +0 -1
- package/dist/schemas/types.d.ts +0 -7
- package/dist/schemas/types.d.ts.map +0 -1
- package/dist/types/DataBinding.d.ts +0 -7
- package/dist/types/DataBinding.d.ts.map +0 -1
- package/dist/types/DataProvider.d.ts +0 -7
- package/dist/types/DataProvider.d.ts.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { ErrorBoundary, withErrorBoundary } from '../ErrorBoundary';
|
|
4
|
+
|
|
5
|
+
// Test component that throws an error
|
|
6
|
+
const ThrowError = ({ shouldThrow }: { shouldThrow: boolean }) => {
|
|
7
|
+
if (shouldThrow) {
|
|
8
|
+
throw new Error('Test error');
|
|
9
|
+
}
|
|
10
|
+
return <div>No error</div>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Mock console.error to avoid noise in tests
|
|
14
|
+
const originalError = console.error;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
console.error = jest.fn();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
console.error = originalError;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('ErrorBoundary', () => {
|
|
24
|
+
it('renders children when there is no error', () => {
|
|
25
|
+
render(
|
|
26
|
+
<ErrorBoundary>
|
|
27
|
+
<div>Test content</div>
|
|
28
|
+
</ErrorBoundary>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders error UI when child component throws', () => {
|
|
35
|
+
render(
|
|
36
|
+
<ErrorBoundary>
|
|
37
|
+
<ThrowError shouldThrow={true} />
|
|
38
|
+
</ErrorBoundary>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText(/An unexpected error occurred/)).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByRole('button', { name: 'Try Again' })).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByRole('button', { name: 'Refresh Page' })).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders custom fallback UI when provided', () => {
|
|
48
|
+
const customFallback = <div>Custom error message</div>;
|
|
49
|
+
|
|
50
|
+
render(
|
|
51
|
+
<ErrorBoundary fallback={customFallback}>
|
|
52
|
+
<ThrowError shouldThrow={true} />
|
|
53
|
+
</ErrorBoundary>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(screen.getByText('Custom error message')).toBeInTheDocument();
|
|
57
|
+
expect(screen.queryByText('Something went wrong')).not.toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('calls onError callback when error occurs', () => {
|
|
61
|
+
const onError = jest.fn();
|
|
62
|
+
|
|
63
|
+
render(
|
|
64
|
+
<ErrorBoundary onError={onError}>
|
|
65
|
+
<ThrowError shouldThrow={true} />
|
|
66
|
+
</ErrorBoundary>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
70
|
+
expect(onError).toHaveBeenCalledWith(
|
|
71
|
+
expect.any(Error),
|
|
72
|
+
expect.objectContaining({
|
|
73
|
+
componentStack: expect.any(String)
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('shows error details in development mode', () => {
|
|
79
|
+
const originalEnv = process.env.NODE_ENV;
|
|
80
|
+
process.env.NODE_ENV = 'development';
|
|
81
|
+
|
|
82
|
+
render(
|
|
83
|
+
<ErrorBoundary>
|
|
84
|
+
<ThrowError shouldThrow={true} />
|
|
85
|
+
</ErrorBoundary>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(screen.getByText(/Error Details \(Development Mode\)/)).toBeInTheDocument();
|
|
89
|
+
|
|
90
|
+
// Cleanup
|
|
91
|
+
process.env.NODE_ENV = originalEnv;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('shows error details when showErrorDetails is true', () => {
|
|
95
|
+
render(
|
|
96
|
+
<ErrorBoundary showErrorDetails={true}>
|
|
97
|
+
<ThrowError shouldThrow={true} />
|
|
98
|
+
</ErrorBoundary>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(screen.getByText(/Error Details \(Development Mode\)/)).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('resets error state when retry button is clicked', () => {
|
|
105
|
+
const TestComponent = () => {
|
|
106
|
+
const [shouldThrow, setShouldThrow] = React.useState(true);
|
|
107
|
+
|
|
108
|
+
React.useEffect(() => {
|
|
109
|
+
// After a delay, stop throwing errors
|
|
110
|
+
const timer = setTimeout(() => setShouldThrow(false), 100);
|
|
111
|
+
return () => clearTimeout(timer);
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
return <ThrowError shouldThrow={shouldThrow} />;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
render(
|
|
118
|
+
<ErrorBoundary>
|
|
119
|
+
<TestComponent />
|
|
120
|
+
</ErrorBoundary>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Error UI should be shown
|
|
124
|
+
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
|
|
125
|
+
|
|
126
|
+
// Click retry
|
|
127
|
+
fireEvent.click(screen.getByRole('button', { name: 'Try Again' }));
|
|
128
|
+
|
|
129
|
+
// Should attempt to render children again
|
|
130
|
+
// Note: This test is limited because the component will likely throw again
|
|
131
|
+
// In a real scenario, you'd fix the underlying issue before retrying
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('withErrorBoundary HOC', () => {
|
|
136
|
+
it('wraps component with ErrorBoundary', () => {
|
|
137
|
+
const TestComponent = () => <div>Test content</div>;
|
|
138
|
+
const WrappedComponent = withErrorBoundary(TestComponent);
|
|
139
|
+
|
|
140
|
+
render(<WrappedComponent />);
|
|
141
|
+
|
|
142
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('passes errorBoundary props to ErrorBoundary', () => {
|
|
146
|
+
const onError = jest.fn();
|
|
147
|
+
const TestComponent = () => <ThrowError shouldThrow={true} />;
|
|
148
|
+
const WrappedComponent = withErrorBoundary(TestComponent, { onError });
|
|
149
|
+
|
|
150
|
+
render(<WrappedComponent />);
|
|
151
|
+
|
|
152
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('sets correct displayName', () => {
|
|
156
|
+
const TestComponent = () => <div>Test</div>;
|
|
157
|
+
TestComponent.displayName = 'TestComponent';
|
|
158
|
+
|
|
159
|
+
const WrappedComponent = withErrorBoundary(TestComponent);
|
|
160
|
+
|
|
161
|
+
expect(WrappedComponent.displayName).toBe('withErrorBoundary(TestComponent)');
|
|
162
|
+
});
|
|
163
|
+
});
|
package/src/components/index.ts
CHANGED
|
@@ -19,6 +19,9 @@ export * from './Scaffold';
|
|
|
19
19
|
export * from './ResponsiveMenu';
|
|
20
20
|
export * from './QwickApp';
|
|
21
21
|
export * from './AccessibilityChecker';
|
|
22
|
+
export * from './ErrorBoundary';
|
|
23
|
+
export * from './AccessibilityProvider';
|
|
24
|
+
export * from './Breadcrumbs';
|
|
22
25
|
// DataDrivenSafeSpan functionality is now integrated into SafeSpan
|
|
23
26
|
|
|
24
27
|
export { default as Logo } from './Logo';
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { AccessibilityProvider, useAccessibility } from '../components/AccessibilityProvider';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
// Demo component that uses accessibility features
|
|
6
|
+
const AccessibilityDemo = () => {
|
|
7
|
+
const {
|
|
8
|
+
highContrast,
|
|
9
|
+
reducedMotion,
|
|
10
|
+
largeText,
|
|
11
|
+
isKeyboardUser,
|
|
12
|
+
issues,
|
|
13
|
+
setHighContrast,
|
|
14
|
+
setReducedMotion,
|
|
15
|
+
setLargeText,
|
|
16
|
+
announce,
|
|
17
|
+
announcePolite,
|
|
18
|
+
announceAssertive,
|
|
19
|
+
runAudit,
|
|
20
|
+
clearIssues
|
|
21
|
+
} = useAccessibility();
|
|
22
|
+
|
|
23
|
+
const [message, setMessage] = React.useState('');
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
|
|
27
|
+
<h2>Accessibility Provider Demo</h2>
|
|
28
|
+
|
|
29
|
+
{/* System Status */}
|
|
30
|
+
<div style={{
|
|
31
|
+
marginBottom: '2rem',
|
|
32
|
+
padding: '1rem',
|
|
33
|
+
background: '#f5f5f5',
|
|
34
|
+
borderRadius: '8px'
|
|
35
|
+
}}>
|
|
36
|
+
<h3>System Status</h3>
|
|
37
|
+
<ul style={{ listStyle: 'none', padding: 0 }}>
|
|
38
|
+
<li>🎨 High Contrast: {highContrast ? '✅ Enabled' : '❌ Disabled'}</li>
|
|
39
|
+
<li>⚡ Reduced Motion: {reducedMotion ? '✅ Enabled' : '❌ Disabled'}</li>
|
|
40
|
+
<li>🔤 Large Text: {largeText ? '✅ Enabled' : '❌ Disabled'}</li>
|
|
41
|
+
<li>⌨️ Keyboard User: {isKeyboardUser ? '✅ Yes' : '❌ No'}</li>
|
|
42
|
+
<li>🚨 Issues Found: {issues.length}</li>
|
|
43
|
+
</ul>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* Controls */}
|
|
47
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
48
|
+
<h3>Accessibility Controls</h3>
|
|
49
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
|
50
|
+
<button
|
|
51
|
+
onClick={() => setHighContrast(!highContrast)}
|
|
52
|
+
style={{
|
|
53
|
+
padding: '0.5rem 1rem',
|
|
54
|
+
background: highContrast ? '#000' : '#007cba',
|
|
55
|
+
color: 'white',
|
|
56
|
+
border: 'none',
|
|
57
|
+
borderRadius: '4px'
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
Toggle High Contrast
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
<button
|
|
64
|
+
onClick={() => setReducedMotion(!reducedMotion)}
|
|
65
|
+
style={{
|
|
66
|
+
padding: '0.5rem 1rem',
|
|
67
|
+
background: reducedMotion ? '#666' : '#007cba',
|
|
68
|
+
color: 'white',
|
|
69
|
+
border: 'none',
|
|
70
|
+
borderRadius: '4px'
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
Toggle Reduced Motion
|
|
74
|
+
</button>
|
|
75
|
+
|
|
76
|
+
<button
|
|
77
|
+
onClick={() => setLargeText(!largeText)}
|
|
78
|
+
style={{
|
|
79
|
+
padding: '0.5rem 1rem',
|
|
80
|
+
background: largeText ? '#0073aa' : '#007cba',
|
|
81
|
+
color: 'white',
|
|
82
|
+
border: 'none',
|
|
83
|
+
borderRadius: '4px',
|
|
84
|
+
fontSize: largeText ? '1.2em' : '1em'
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
Toggle Large Text
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Announcements */}
|
|
93
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
94
|
+
<h3>Screen Reader Announcements</h3>
|
|
95
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
96
|
+
<input
|
|
97
|
+
type="text"
|
|
98
|
+
value={message}
|
|
99
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
100
|
+
placeholder="Type a message to announce..."
|
|
101
|
+
style={{
|
|
102
|
+
padding: '0.5rem',
|
|
103
|
+
marginRight: '0.5rem',
|
|
104
|
+
width: '300px',
|
|
105
|
+
border: '1px solid #ccc',
|
|
106
|
+
borderRadius: '4px'
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
111
|
+
<button
|
|
112
|
+
onClick={() => announcePolite(message || 'Polite announcement test')}
|
|
113
|
+
style={{
|
|
114
|
+
padding: '0.5rem 1rem',
|
|
115
|
+
background: '#28a745',
|
|
116
|
+
color: 'white',
|
|
117
|
+
border: 'none',
|
|
118
|
+
borderRadius: '4px'
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
Announce Polite
|
|
122
|
+
</button>
|
|
123
|
+
|
|
124
|
+
<button
|
|
125
|
+
onClick={() => announceAssertive(message || 'Assertive announcement test')}
|
|
126
|
+
style={{
|
|
127
|
+
padding: '0.5rem 1rem',
|
|
128
|
+
background: '#dc3545',
|
|
129
|
+
color: 'white',
|
|
130
|
+
border: 'none',
|
|
131
|
+
borderRadius: '4px'
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
Announce Assertive
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Audit Controls */}
|
|
140
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
141
|
+
<h3>Accessibility Audit</h3>
|
|
142
|
+
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
|
|
143
|
+
<button
|
|
144
|
+
onClick={runAudit}
|
|
145
|
+
style={{
|
|
146
|
+
padding: '0.5rem 1rem',
|
|
147
|
+
background: '#6f42c1',
|
|
148
|
+
color: 'white',
|
|
149
|
+
border: 'none',
|
|
150
|
+
borderRadius: '4px'
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
Run Audit
|
|
154
|
+
</button>
|
|
155
|
+
|
|
156
|
+
<button
|
|
157
|
+
onClick={clearIssues}
|
|
158
|
+
style={{
|
|
159
|
+
padding: '0.5rem 1rem',
|
|
160
|
+
background: '#6c757d',
|
|
161
|
+
color: 'white',
|
|
162
|
+
border: 'none',
|
|
163
|
+
borderRadius: '4px'
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
Clear Issues
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{issues.length > 0 && (
|
|
171
|
+
<div style={{
|
|
172
|
+
padding: '1rem',
|
|
173
|
+
background: '#fff3cd',
|
|
174
|
+
border: '1px solid #ffeaa7',
|
|
175
|
+
borderRadius: '8px'
|
|
176
|
+
}}>
|
|
177
|
+
<h4>Accessibility Issues ({issues.length})</h4>
|
|
178
|
+
<ul>
|
|
179
|
+
{issues.map((issue, index) => (
|
|
180
|
+
<li key={index} style={{ color: issue.level === 'error' ? '#dc3545' : '#856404' }}>
|
|
181
|
+
<strong>{issue.type}</strong>: {issue.message}
|
|
182
|
+
</li>
|
|
183
|
+
))}
|
|
184
|
+
</ul>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Test elements that might have accessibility issues */}
|
|
190
|
+
<div style={{ marginTop: '2rem', padding: '1rem', background: '#e9ecef', borderRadius: '8px' }}>
|
|
191
|
+
<h3>Test Elements for Audit</h3>
|
|
192
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='50'%3E%3Crect width='100' height='50' fill='%23ddd'/%3E%3C/svg%3E" />
|
|
193
|
+
<button>Unlabeled Button</button>
|
|
194
|
+
<input type="text" placeholder="Unlabeled Input" />
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const meta: Meta<typeof AccessibilityProvider> = {
|
|
201
|
+
title: 'Framework/AccessibilityProvider',
|
|
202
|
+
component: AccessibilityProvider,
|
|
203
|
+
parameters: {
|
|
204
|
+
layout: 'fullscreen',
|
|
205
|
+
docs: {
|
|
206
|
+
description: {
|
|
207
|
+
component: 'Provides comprehensive accessibility context and utilities including system preference detection, keyboard navigation, ARIA announcements, and accessibility auditing.',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
tags: ['autodocs'],
|
|
212
|
+
argTypes: {
|
|
213
|
+
enableAudit: {
|
|
214
|
+
description: 'Whether to enable automatic accessibility auditing (defaults to development mode)',
|
|
215
|
+
control: 'boolean',
|
|
216
|
+
},
|
|
217
|
+
children: {
|
|
218
|
+
description: 'Child components that will have access to accessibility context',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export default meta;
|
|
224
|
+
type Story = StoryObj<typeof meta>;
|
|
225
|
+
|
|
226
|
+
// Default story with full demo
|
|
227
|
+
export const Default: Story = {
|
|
228
|
+
args: {
|
|
229
|
+
children: <AccessibilityDemo />,
|
|
230
|
+
enableAudit: true,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Story showing basic usage without audit
|
|
235
|
+
export const BasicUsage: Story = {
|
|
236
|
+
args: {
|
|
237
|
+
enableAudit: false,
|
|
238
|
+
children: (
|
|
239
|
+
<div style={{ padding: '2rem' }}>
|
|
240
|
+
<h2>Basic AccessibilityProvider Usage</h2>
|
|
241
|
+
<p>This provider automatically detects system preferences and manages keyboard navigation.</p>
|
|
242
|
+
<button>Try tabbing to this button</button>
|
|
243
|
+
<br /><br />
|
|
244
|
+
<input type="text" placeholder="Type here and use Tab key" />
|
|
245
|
+
</div>
|
|
246
|
+
),
|
|
247
|
+
},
|
|
248
|
+
parameters: {
|
|
249
|
+
docs: {
|
|
250
|
+
description: {
|
|
251
|
+
story: 'Basic usage of AccessibilityProvider without auditing enabled.',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Story demonstrating keyboard navigation
|
|
258
|
+
export const KeyboardNavigation: Story = {
|
|
259
|
+
args: {
|
|
260
|
+
children: (
|
|
261
|
+
<div style={{ padding: '2rem' }}>
|
|
262
|
+
<h2>Keyboard Navigation Test</h2>
|
|
263
|
+
<p>Press Tab to navigate between elements. When using keyboard, focus indicators will be enhanced.</p>
|
|
264
|
+
<button>Button 1</button>
|
|
265
|
+
<br /><br />
|
|
266
|
+
<input type="text" placeholder="Input field" />
|
|
267
|
+
<br /><br />
|
|
268
|
+
<select>
|
|
269
|
+
<option>Option 1</option>
|
|
270
|
+
<option>Option 2</option>
|
|
271
|
+
</select>
|
|
272
|
+
<br /><br />
|
|
273
|
+
<a href="#" onClick={(e) => e.preventDefault()}>Link element</a>
|
|
274
|
+
</div>
|
|
275
|
+
),
|
|
276
|
+
},
|
|
277
|
+
parameters: {
|
|
278
|
+
docs: {
|
|
279
|
+
description: {
|
|
280
|
+
story: 'Test keyboard navigation to see enhanced focus indicators when using Tab key.',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
};
|