@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,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
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ErrorBoundary, withErrorBoundary } from '../components/ErrorBoundary';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
// Test component that throws an error
|
|
6
|
+
const ThrowError = ({ shouldThrow = false }: { shouldThrow?: boolean }) => {
|
|
7
|
+
if (shouldThrow) {
|
|
8
|
+
throw new Error('Intentional error for testing ErrorBoundary');
|
|
9
|
+
}
|
|
10
|
+
return <div>This component is working fine!</div>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof ErrorBoundary> = {
|
|
14
|
+
title: 'Framework/ErrorBoundary',
|
|
15
|
+
component: ErrorBoundary,
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'centered',
|
|
18
|
+
docs: {
|
|
19
|
+
description: {
|
|
20
|
+
component: 'A React Error Boundary component that catches JavaScript errors and displays a user-friendly fallback UI with recovery options.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
tags: ['autodocs'],
|
|
25
|
+
argTypes: {
|
|
26
|
+
fallback: {
|
|
27
|
+
description: 'Custom fallback UI to display when an error occurs',
|
|
28
|
+
},
|
|
29
|
+
onError: {
|
|
30
|
+
description: 'Callback function called when an error is caught',
|
|
31
|
+
action: 'onError',
|
|
32
|
+
},
|
|
33
|
+
showErrorDetails: {
|
|
34
|
+
description: 'Whether to show error details (overrides development mode detection)',
|
|
35
|
+
control: 'boolean',
|
|
36
|
+
},
|
|
37
|
+
children: {
|
|
38
|
+
description: 'Child components to wrap with error boundary',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default meta;
|
|
44
|
+
type Story = StoryObj<typeof meta>;
|
|
45
|
+
|
|
46
|
+
// Default story with working component
|
|
47
|
+
export const Default: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
children: <div>This is a working component wrapped in ErrorBoundary</div>,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Story that demonstrates error handling
|
|
54
|
+
export const WithError: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
children: <ThrowError shouldThrow={true} />,
|
|
57
|
+
},
|
|
58
|
+
parameters: {
|
|
59
|
+
docs: {
|
|
60
|
+
description: {
|
|
61
|
+
story: 'Demonstrates ErrorBoundary catching an error and displaying the fallback UI.',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Story with custom fallback UI
|
|
68
|
+
export const CustomFallback: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
children: <ThrowError shouldThrow={true} />,
|
|
71
|
+
fallback: (
|
|
72
|
+
<div style={{
|
|
73
|
+
padding: '2rem',
|
|
74
|
+
textAlign: 'center',
|
|
75
|
+
background: '#ffe6e6',
|
|
76
|
+
border: '2px solid #ff9999',
|
|
77
|
+
borderRadius: '8px',
|
|
78
|
+
color: '#cc0000'
|
|
79
|
+
}}>
|
|
80
|
+
<h3>🚨 Custom Error UI</h3>
|
|
81
|
+
<p>Something went wrong, but we have a custom fallback!</p>
|
|
82
|
+
</div>
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
parameters: {
|
|
86
|
+
docs: {
|
|
87
|
+
description: {
|
|
88
|
+
story: 'Shows how to provide a custom fallback UI instead of the default error message.',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Story showing error details in development mode
|
|
95
|
+
export const WithErrorDetails: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
children: <ThrowError shouldThrow={true} />,
|
|
98
|
+
showErrorDetails: true,
|
|
99
|
+
},
|
|
100
|
+
parameters: {
|
|
101
|
+
docs: {
|
|
102
|
+
description: {
|
|
103
|
+
story: 'Shows error details including stack trace (normally only shown in development mode).',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Story demonstrating the HOC
|
|
110
|
+
const TestComponent = () => <div>Component enhanced with ErrorBoundary HOC</div>;
|
|
111
|
+
const EnhancedComponent = withErrorBoundary(TestComponent);
|
|
112
|
+
|
|
113
|
+
export const HigherOrderComponent: Story = {
|
|
114
|
+
render: () => <EnhancedComponent />,
|
|
115
|
+
parameters: {
|
|
116
|
+
docs: {
|
|
117
|
+
description: {
|
|
118
|
+
story: 'Demonstrates using the withErrorBoundary Higher-Order Component to wrap any component with error handling.',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Interactive story for testing retry functionality
|
|
125
|
+
export const Interactive: Story = {
|
|
126
|
+
render: () => {
|
|
127
|
+
const [shouldThrow, setShouldThrow] = React.useState(false);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div style={{ padding: '2rem' }}>
|
|
131
|
+
<button
|
|
132
|
+
onClick={() => setShouldThrow(!shouldThrow)}
|
|
133
|
+
style={{
|
|
134
|
+
marginBottom: '1rem',
|
|
135
|
+
padding: '0.5rem 1rem',
|
|
136
|
+
background: shouldThrow ? '#ff4444' : '#44aa44',
|
|
137
|
+
color: 'white',
|
|
138
|
+
border: 'none',
|
|
139
|
+
borderRadius: '4px',
|
|
140
|
+
cursor: 'pointer'
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{shouldThrow ? 'Fix Component' : 'Break Component'}
|
|
144
|
+
</button>
|
|
145
|
+
|
|
146
|
+
<ErrorBoundary>
|
|
147
|
+
<ThrowError shouldThrow={shouldThrow} />
|
|
148
|
+
</ErrorBoundary>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
parameters: {
|
|
153
|
+
docs: {
|
|
154
|
+
description: {
|
|
155
|
+
story: 'Interactive demo where you can break and fix the component to see ErrorBoundary in action.',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { Box, Paper, Typography } from '@mui/material';
|
|
6
6
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
7
|
-
import { Button } from '
|
|
8
|
-
import { TextField } from '
|
|
9
|
-
import { GridCell } from '
|
|
10
|
-
import GridLayout from '
|
|
7
|
+
import { Button } from '../components/buttons/Button';
|
|
8
|
+
import { TextField } from '../components/input/TextField';
|
|
9
|
+
import { GridCell } from '../components/layout/GridCell';
|
|
10
|
+
import GridLayout from '../components/layout/GridLayout';
|
|
11
11
|
|
|
12
12
|
const meta: Meta = {
|
|
13
|
-
title: '
|
|
13
|
+
title: 'Forms/Enhanced Components',
|
|
14
14
|
parameters: {
|
|
15
15
|
layout: 'padded',
|
|
16
16
|
docs: {
|
|
@@ -43,14 +43,14 @@ export const BasicTextField: StoryObj = {
|
|
|
43
43
|
<TextField
|
|
44
44
|
label="Small Width"
|
|
45
45
|
width="small"
|
|
46
|
-
margin="
|
|
46
|
+
margin="normal"
|
|
47
47
|
padding="small"
|
|
48
48
|
/>
|
|
49
49
|
<br />
|
|
50
50
|
<TextField
|
|
51
51
|
label="Large Width"
|
|
52
52
|
width="large"
|
|
53
|
-
margin="
|
|
53
|
+
margin="normal"
|
|
54
54
|
padding="large"
|
|
55
55
|
background="primary.light"
|
|
56
56
|
/>
|
|
@@ -73,7 +73,7 @@ export const BasicButton: StoryObj = {
|
|
|
73
73
|
<Button
|
|
74
74
|
variant="contained"
|
|
75
75
|
width="large"
|
|
76
|
-
margin="
|
|
76
|
+
margin="normal"
|
|
77
77
|
padding="large"
|
|
78
78
|
>
|
|
79
79
|
Large Width
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
6
6
|
*/
|
|
7
|
-
import { Logger } from '
|
|
7
|
+
import { Logger, createLogger } from '../utils/logger';
|
|
8
8
|
import { CachedDataProvider, DataResponse, ICacheProvider, IDataProvider, MemoryCacheProvider, Model, MustacheTemplateProvider, SelectOptions } from '@qwickapps/schema';
|
|
9
9
|
import {
|
|
10
10
|
DataProxy,
|
|
@@ -51,11 +51,7 @@ export class TemplateResolver implements ITemplateResolver {
|
|
|
51
51
|
private log: Logger;
|
|
52
52
|
|
|
53
53
|
constructor(config: TemplateResolverConfig) {
|
|
54
|
-
this.log =
|
|
55
|
-
namespace: 'ContentResolver',
|
|
56
|
-
enabled: config.enableLogging || false
|
|
57
|
-
});
|
|
58
|
-
|
|
54
|
+
this.log = createLogger('ContentResolver');
|
|
59
55
|
this.enableLogging = config.enableLogging || false;
|
|
60
56
|
this.templateResolver = config.templateResolver || new MustacheTemplateProvider();
|
|
61
57
|
if (config.cacheProvider === true) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test for DOM nesting fix in HTML transformation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { transformHtmlToReact, defaultMarkdownRules } from '../htmlTransform';
|
|
6
|
+
|
|
7
|
+
describe('DOM nesting fix', () => {
|
|
8
|
+
it('should fix invalid paragraph nesting with block elements', () => {
|
|
9
|
+
// This is the problematic HTML that marked.js creates from mixed content
|
|
10
|
+
const invalidHtml = `
|
|
11
|
+
<p>You can mix HTML with Markdown when needed:</p>
|
|
12
|
+
<p><div style="padding: 16px; border: 1px solid #ccc;">
|
|
13
|
+
This is <strong>HTML with Markdown</strong> inside it!
|
|
14
|
+
</div></p>
|
|
15
|
+
<p>Back to regular content.</p>
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
// Should not throw React error
|
|
19
|
+
expect(() => {
|
|
20
|
+
const result = transformHtmlToReact(invalidHtml, { rules: defaultMarkdownRules });
|
|
21
|
+
expect(result).toBeDefined();
|
|
22
|
+
expect(Array.isArray(result)).toBe(true);
|
|
23
|
+
}).not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should preserve valid nesting', () => {
|
|
27
|
+
const validHtml = `
|
|
28
|
+
<p>This is a valid paragraph with <strong>inline elements</strong>.</p>
|
|
29
|
+
<div>This is a valid div with block content.</div>
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const result = transformHtmlToReact(validHtml, { rules: defaultMarkdownRules });
|
|
33
|
+
expect(result).toBeDefined();
|
|
34
|
+
expect(result.length).toBeGreaterThan(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle deeply nested invalid structures', () => {
|
|
38
|
+
const deeplyNested = `
|
|
39
|
+
<p>
|
|
40
|
+
Outer paragraph
|
|
41
|
+
<div>
|
|
42
|
+
Block in paragraph
|
|
43
|
+
<p>Paragraph in div in paragraph</p>
|
|
44
|
+
</div>
|
|
45
|
+
</p>
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
expect(() => {
|
|
49
|
+
const result = transformHtmlToReact(deeplyNested, { rules: defaultMarkdownRules });
|
|
50
|
+
expect(result).toBeDefined();
|
|
51
|
+
}).not.toThrow();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test for optional logging dependency fallback
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createLogger, loggers } from '../logger';
|
|
6
|
+
|
|
7
|
+
// Mock the require function to simulate missing @qwickapps/logging package
|
|
8
|
+
const originalRequire = require;
|
|
9
|
+
|
|
10
|
+
describe('Optional logging dependency', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Reset console spy before each test
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Restore original require
|
|
18
|
+
(global as any).require = originalRequire;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should use console fallback when @qwickapps/logging is not available', () => {
|
|
22
|
+
// Mock require to throw an error (simulating missing package)
|
|
23
|
+
(global as any).require = jest.fn().mockImplementation((module: string) => {
|
|
24
|
+
if (module === '@qwickapps/logging') {
|
|
25
|
+
throw new Error('Cannot find module');
|
|
26
|
+
}
|
|
27
|
+
return originalRequire(module);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Spy on console methods
|
|
31
|
+
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
32
|
+
|
|
33
|
+
// Create a new logger (should use fallback)
|
|
34
|
+
const logger = createLogger('TestLogger');
|
|
35
|
+
logger.info('Test message');
|
|
36
|
+
|
|
37
|
+
expect(consoleSpy).toHaveBeenCalledWith('[TestLogger] Test message');
|
|
38
|
+
consoleSpy.mockRestore();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should have all framework loggers available', () => {
|
|
42
|
+
expect(loggers).toHaveProperty('scaffold');
|
|
43
|
+
expect(loggers).toHaveProperty('navigation');
|
|
44
|
+
expect(loggers).toHaveProperty('auth');
|
|
45
|
+
expect(loggers).toHaveProperty('theme');
|
|
46
|
+
expect(loggers).toHaveProperty('palette');
|
|
47
|
+
expect(loggers).toHaveProperty('form');
|
|
48
|
+
expect(loggers).toHaveProperty('layout');
|
|
49
|
+
expect(loggers).toHaveProperty('menu');
|
|
50
|
+
expect(loggers).toHaveProperty('router');
|
|
51
|
+
|
|
52
|
+
// Each logger should have the required methods
|
|
53
|
+
Object.values(loggers).forEach(logger => {
|
|
54
|
+
expect(typeof logger.debug).toBe('function');
|
|
55
|
+
expect(typeof logger.info).toBe('function');
|
|
56
|
+
expect(typeof logger.warn).toBe('function');
|
|
57
|
+
expect(typeof logger.error).toBe('function');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should suppress debug logs in production', () => {
|
|
62
|
+
const originalEnv = process.env.NODE_ENV;
|
|
63
|
+
process.env.NODE_ENV = 'production';
|
|
64
|
+
|
|
65
|
+
// Mock require to throw an error (use fallback)
|
|
66
|
+
(global as any).require = jest.fn().mockImplementation((module: string) => {
|
|
67
|
+
if (module === '@qwickapps/logging') {
|
|
68
|
+
throw new Error('Cannot find module');
|
|
69
|
+
}
|
|
70
|
+
return originalRequire(module);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const debugSpy = jest.spyOn(console, 'debug').mockImplementation();
|
|
74
|
+
|
|
75
|
+
const logger = createLogger('TestLogger');
|
|
76
|
+
logger.debug('Debug message');
|
|
77
|
+
|
|
78
|
+
expect(debugSpy).not.toHaveBeenCalled();
|
|
79
|
+
|
|
80
|
+
debugSpy.mockRestore();
|
|
81
|
+
process.env.NODE_ENV = originalEnv;
|
|
82
|
+
});
|
|
83
|
+
});
|