@qwickapps/react-framework 1.3.2 → 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 +106 -0
- 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 +795 -12
- package/dist/index.js +800 -10
- package/package.json +1 -1
- 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/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,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
|
+
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Breadcrumbs, useBreadcrumbs, type BreadcrumbItem } from '../components/Breadcrumbs';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
// Icons for demo
|
|
6
|
+
const HomeIcon = () => (
|
|
7
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
8
|
+
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
|
9
|
+
</svg>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const FolderIcon = () => (
|
|
13
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
14
|
+
<path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const FileIcon = () => (
|
|
19
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
20
|
+
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Demo component with navigation simulation
|
|
25
|
+
const BreadcrumbDemo = () => {
|
|
26
|
+
const { breadcrumbs, setBreadcrumbs, addBreadcrumb, removeBreadcrumb, clearBreadcrumbs } = useBreadcrumbs();
|
|
27
|
+
const [currentPath, setCurrentPath] = React.useState('Home > Documents > Projects');
|
|
28
|
+
|
|
29
|
+
const samplePaths = [
|
|
30
|
+
'Home',
|
|
31
|
+
'Home > Documents',
|
|
32
|
+
'Home > Documents > Projects',
|
|
33
|
+
'Home > Documents > Projects > QwickApps',
|
|
34
|
+
'Home > Documents > Projects > QwickApps > Framework',
|
|
35
|
+
'Home > Store > Products',
|
|
36
|
+
'Home > Store > Products > Electronics > Computers',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const pathToBreadcrumbs = (path: string): BreadcrumbItem[] => {
|
|
40
|
+
const parts = path.split(' > ');
|
|
41
|
+
return parts.map((part, index) => ({
|
|
42
|
+
label: part,
|
|
43
|
+
href: `/${part.toLowerCase().replace(/\s+/g, '-')}`,
|
|
44
|
+
icon: index === 0 ? <HomeIcon /> :
|
|
45
|
+
index === parts.length - 1 ? <FileIcon /> :
|
|
46
|
+
<FolderIcon />,
|
|
47
|
+
current: index === parts.length - 1
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
setBreadcrumbs(pathToBreadcrumbs(currentPath));
|
|
53
|
+
}, [currentPath, setBreadcrumbs]);
|
|
54
|
+
|
|
55
|
+
const handleNavigate = (item: BreadcrumbItem, index: number) => {
|
|
56
|
+
const newPath = breadcrumbs.slice(0, index + 1).map(b => b.label).join(' > ');
|
|
57
|
+
setCurrentPath(newPath);
|
|
58
|
+
alert(`Navigated to: ${item.label}`);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div style={{ padding: '2rem' }}>
|
|
63
|
+
<h2>Interactive Breadcrumbs Demo</h2>
|
|
64
|
+
|
|
65
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
66
|
+
<h3>Current Navigation:</h3>
|
|
67
|
+
<Breadcrumbs items={breadcrumbs} onNavigate={handleNavigate} />
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
71
|
+
<h3>Navigate to Different Paths:</h3>
|
|
72
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
|
73
|
+
{samplePaths.map((path) => (
|
|
74
|
+
<button
|
|
75
|
+
key={path}
|
|
76
|
+
onClick={() => setCurrentPath(path)}
|
|
77
|
+
style={{
|
|
78
|
+
padding: '0.5rem 1rem',
|
|
79
|
+
background: path === currentPath ? '#007cba' : '#f0f0f0',
|
|
80
|
+
color: path === currentPath ? 'white' : 'black',
|
|
81
|
+
border: '1px solid #ddd',
|
|
82
|
+
borderRadius: '4px',
|
|
83
|
+
textAlign: 'left',
|
|
84
|
+
cursor: 'pointer'
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{path}
|
|
88
|
+
</button>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div>
|
|
94
|
+
<h3>Breadcrumb Controls:</h3>
|
|
95
|
+
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
|
96
|
+
<button
|
|
97
|
+
onClick={() => addBreadcrumb({ label: 'New Item', href: '/new' })}
|
|
98
|
+
style={{ padding: '0.5rem 1rem', background: '#28a745', color: 'white', border: 'none', borderRadius: '4px' }}
|
|
99
|
+
>
|
|
100
|
+
Add Item
|
|
101
|
+
</button>
|
|
102
|
+
<button
|
|
103
|
+
onClick={() => removeBreadcrumb(breadcrumbs.length - 1)}
|
|
104
|
+
style={{ padding: '0.5rem 1rem', background: '#dc3545', color: 'white', border: 'none', borderRadius: '4px' }}
|
|
105
|
+
>
|
|
106
|
+
Remove Last
|
|
107
|
+
</button>
|
|
108
|
+
<button
|
|
109
|
+
onClick={clearBreadcrumbs}
|
|
110
|
+
style={{ padding: '0.5rem 1rem', background: '#6c757d', color: 'white', border: 'none', borderRadius: '4px' }}
|
|
111
|
+
>
|
|
112
|
+
Clear All
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const meta: Meta<typeof Breadcrumbs> = {
|
|
121
|
+
title: 'Components/Breadcrumbs',
|
|
122
|
+
component: Breadcrumbs,
|
|
123
|
+
parameters: {
|
|
124
|
+
layout: 'centered',
|
|
125
|
+
docs: {
|
|
126
|
+
description: {
|
|
127
|
+
component: 'A navigation breadcrumb component that shows the current page location within the application hierarchy. Supports accessibility, keyboard navigation, and customization.',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
tags: ['autodocs'],
|
|
132
|
+
argTypes: {
|
|
133
|
+
items: {
|
|
134
|
+
description: 'Array of breadcrumb items to display',
|
|
135
|
+
},
|
|
136
|
+
separator: {
|
|
137
|
+
description: 'Custom separator between breadcrumb items',
|
|
138
|
+
control: 'text',
|
|
139
|
+
},
|
|
140
|
+
className: {
|
|
141
|
+
description: 'Additional CSS classes to apply',
|
|
142
|
+
control: 'text',
|
|
143
|
+
},
|
|
144
|
+
onNavigate: {
|
|
145
|
+
description: 'Callback function when a breadcrumb is clicked',
|
|
146
|
+
action: 'navigate',
|
|
147
|
+
},
|
|
148
|
+
maxItems: {
|
|
149
|
+
description: 'Maximum number of items to show (truncates with ellipsis)',
|
|
150
|
+
control: { type: 'number', min: 1, max: 10 },
|
|
151
|
+
},
|
|
152
|
+
showRoot: {
|
|
153
|
+
description: 'Whether to show the root (first) item',
|
|
154
|
+
control: 'boolean',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export default meta;
|
|
160
|
+
type Story = StoryObj<typeof meta>;
|
|
161
|
+
|
|
162
|
+
// Sample breadcrumb data
|
|
163
|
+
const sampleBreadcrumbs: BreadcrumbItem[] = [
|
|
164
|
+
{ label: 'Home', href: '/', icon: <HomeIcon /> },
|
|
165
|
+
{ label: 'Products', href: '/products', icon: <FolderIcon /> },
|
|
166
|
+
{ label: 'Electronics', href: '/products/electronics', icon: <FolderIcon /> },
|
|
167
|
+
{ label: 'Laptop', href: '/products/electronics/laptop', icon: <FileIcon />, current: true },
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
// Default story
|
|
171
|
+
export const Default: Story = {
|
|
172
|
+
args: {
|
|
173
|
+
items: sampleBreadcrumbs,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Story with custom separator
|
|
178
|
+
export const CustomSeparator: Story = {
|
|
179
|
+
args: {
|
|
180
|
+
items: sampleBreadcrumbs,
|
|
181
|
+
separator: '→',
|
|
182
|
+
},
|
|
183
|
+
parameters: {
|
|
184
|
+
docs: {
|
|
185
|
+
description: {
|
|
186
|
+
story: 'Breadcrumbs with a custom arrow separator instead of the default slash.',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Story with emoji separator
|
|
193
|
+
export const EmojiSeparator: Story = {
|
|
194
|
+
args: {
|
|
195
|
+
items: sampleBreadcrumbs,
|
|
196
|
+
separator: '🏠',
|
|
197
|
+
},
|
|
198
|
+
parameters: {
|
|
199
|
+
docs: {
|
|
200
|
+
description: {
|
|
201
|
+
story: 'Breadcrumbs with an emoji separator for a more playful look.',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Story with max items (truncation)
|
|
208
|
+
export const TruncatedItems: Story = {
|
|
209
|
+
args: {
|
|
210
|
+
items: [
|
|
211
|
+
{ label: 'Home', href: '/' },
|
|
212
|
+
{ label: 'Level 1', href: '/level1' },
|
|
213
|
+
{ label: 'Level 2', href: '/level1/level2' },
|
|
214
|
+
{ label: 'Level 3', href: '/level1/level2/level3' },
|
|
215
|
+
{ label: 'Level 4', href: '/level1/level2/level3/level4' },
|
|
216
|
+
{ label: 'Current Page', href: '/level1/level2/level3/level4/current', current: true },
|
|
217
|
+
],
|
|
218
|
+
maxItems: 4,
|
|
219
|
+
},
|
|
220
|
+
parameters: {
|
|
221
|
+
docs: {
|
|
222
|
+
description: {
|
|
223
|
+
story: 'When there are too many items, middle items are replaced with ellipsis.',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Story without root item
|
|
230
|
+
export const WithoutRoot: Story = {
|
|
231
|
+
args: {
|
|
232
|
+
items: sampleBreadcrumbs,
|
|
233
|
+
showRoot: false,
|
|
234
|
+
},
|
|
235
|
+
parameters: {
|
|
236
|
+
docs: {
|
|
237
|
+
description: {
|
|
238
|
+
story: 'Breadcrumbs without showing the root (first) item.',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Story without icons
|
|
245
|
+
export const WithoutIcons: Story = {
|
|
246
|
+
args: {
|
|
247
|
+
items: [
|
|
248
|
+
{ label: 'Home', href: '/' },
|
|
249
|
+
{ label: 'Products', href: '/products' },
|
|
250
|
+
{ label: 'Electronics', href: '/products/electronics' },
|
|
251
|
+
{ label: 'Laptop', href: '/products/electronics/laptop', current: true },
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
parameters: {
|
|
255
|
+
docs: {
|
|
256
|
+
description: {
|
|
257
|
+
story: 'Simple breadcrumbs without icons, just text navigation.',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Story with navigation handler
|
|
264
|
+
export const WithNavigation: Story = {
|
|
265
|
+
args: {
|
|
266
|
+
items: sampleBreadcrumbs,
|
|
267
|
+
onNavigate: (item, index) => {
|
|
268
|
+
alert(`Clicked on "${item.label}" at index ${index}`);
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
parameters: {
|
|
272
|
+
docs: {
|
|
273
|
+
description: {
|
|
274
|
+
story: 'Breadcrumbs with a navigation handler that prevents default link behavior.',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Interactive demo story
|
|
281
|
+
export const InteractiveDemo: Story = {
|
|
282
|
+
render: () => <BreadcrumbDemo />,
|
|
283
|
+
parameters: {
|
|
284
|
+
docs: {
|
|
285
|
+
description: {
|
|
286
|
+
story: 'Interactive demo showing breadcrumb navigation with the useBreadcrumbs hook.',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Accessibility showcase
|
|
293
|
+
export const AccessibilityShowcase: Story = {
|
|
294
|
+
args: {
|
|
295
|
+
items: sampleBreadcrumbs,
|
|
296
|
+
},
|
|
297
|
+
parameters: {
|
|
298
|
+
docs: {
|
|
299
|
+
description: {
|
|
300
|
+
story: 'This breadcrumb component includes proper ARIA labels, keyboard navigation, and screen reader support. Try using Tab and Enter keys to navigate.',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
};
|