@abstraks-dev/ui-library 1.0.1
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/LICENSE +21 -0
- package/README.md +708 -0
- package/dist/__tests__/Anchor.test.js +145 -0
- package/dist/__tests__/ArrowRight.test.js +91 -0
- package/dist/__tests__/Avatar.test.js +123 -0
- package/dist/__tests__/Button.test.js +82 -0
- package/dist/__tests__/Card.test.js +198 -0
- package/dist/__tests__/CheckCircle.test.js +98 -0
- package/dist/__tests__/Checkbox.test.js +161 -0
- package/dist/__tests__/ChevronDown.test.js +73 -0
- package/dist/__tests__/Close.test.js +98 -0
- package/dist/__tests__/EditSquare.test.js +99 -0
- package/dist/__tests__/Error.test.js +74 -0
- package/dist/__tests__/Footer.test.js +66 -0
- package/dist/__tests__/Heading.test.js +227 -0
- package/dist/__tests__/Hero.test.js +74 -0
- package/dist/__tests__/Label.test.js +123 -0
- package/dist/__tests__/Loader.test.js +115 -0
- package/dist/__tests__/MenuHover.test.js +137 -0
- package/dist/__tests__/Paragraph.test.js +93 -0
- package/dist/__tests__/PlusCircle.test.js +99 -0
- package/dist/__tests__/Radio.test.js +153 -0
- package/dist/__tests__/Select.test.js +187 -0
- package/dist/__tests__/Tabs.test.js +162 -0
- package/dist/__tests__/TextArea.test.js +127 -0
- package/dist/__tests__/TextInput.test.js +181 -0
- package/dist/__tests__/Toggle.test.js +120 -0
- package/dist/__tests__/TrashX.test.js +99 -0
- package/dist/__tests__/useHeadingAccessibility.test.js +144 -0
- package/dist/components/Anchor.js +131 -0
- package/dist/components/Animation.js +129 -0
- package/dist/components/AnimationGroup.js +207 -0
- package/dist/components/AnimationToggle.js +216 -0
- package/dist/components/Avatar.js +153 -0
- package/dist/components/Button.js +218 -0
- package/dist/components/Card.js +222 -0
- package/dist/components/Checkbox.js +305 -0
- package/dist/components/Crud.js +564 -0
- package/dist/components/DragAndDrop.js +337 -0
- package/dist/components/Error.js +206 -0
- package/dist/components/Footer.js +99 -0
- package/dist/components/Form.js +412 -0
- package/dist/components/Header.js +372 -0
- package/dist/components/Heading.js +134 -0
- package/dist/components/Hero.js +181 -0
- package/dist/components/Label.js +256 -0
- package/dist/components/Loader.js +302 -0
- package/dist/components/MenuHover.js +114 -0
- package/dist/components/Paragraph.js +128 -0
- package/dist/components/Prompt.js +61 -0
- package/dist/components/Radio.js +254 -0
- package/dist/components/Select.js +422 -0
- package/dist/components/SideMenu.js +313 -0
- package/dist/components/Tabs.js +297 -0
- package/dist/components/TextArea.js +370 -0
- package/dist/components/TextInput.js +286 -0
- package/dist/components/Toggle.js +186 -0
- package/dist/components/crudFiles/CrudEditBase.js +150 -0
- package/dist/components/crudFiles/CrudViewBase.js +39 -0
- package/dist/components/crudFiles/crudDevelopment.js +118 -0
- package/dist/components/crudFiles/crudEditHandlers.js +50 -0
- package/dist/constants/animation.js +30 -0
- package/dist/icons/ArrowIcon.js +32 -0
- package/dist/icons/ArrowRight.js +33 -0
- package/dist/icons/CheckCircle.js +33 -0
- package/dist/icons/ChevronDown.js +28 -0
- package/dist/icons/Close.js +33 -0
- package/dist/icons/EditSquare.js +33 -0
- package/dist/icons/Ellipses.js +34 -0
- package/dist/icons/Hamburger.js +39 -0
- package/dist/icons/LoadingSpinner.js +42 -0
- package/dist/icons/PlusCircle.js +33 -0
- package/dist/icons/SaveIcon.js +32 -0
- package/dist/icons/TrashX.js +33 -0
- package/dist/icons/__tests__/CheckCircle.test.js +9 -0
- package/dist/icons/__tests__/ChevronDown.test.js +9 -0
- package/dist/icons/__tests__/Close.test.js +9 -0
- package/dist/icons/__tests__/EditSquare.test.js +9 -0
- package/dist/icons/__tests__/PlusCircle.test.js +9 -0
- package/dist/icons/__tests__/TrashX.test.js +9 -0
- package/dist/icons/index.js +89 -0
- package/dist/index.js +332 -0
- package/dist/setupTests.js +3 -0
- package/dist/styles/_variables.scss +286 -0
- package/dist/styles/anchor.scss +40 -0
- package/dist/styles/animation-accessibility.scss +96 -0
- package/dist/styles/animation-toggle.scss +233 -0
- package/dist/styles/animation.scss +3781 -0
- package/dist/styles/avatar.scss +285 -0
- package/dist/styles/button.scss +430 -0
- package/dist/styles/card.scss +210 -0
- package/dist/styles/checkbox.scss +160 -0
- package/dist/styles/crud.scss +474 -0
- package/dist/styles/dragAndDrop.scss +312 -0
- package/dist/styles/error.scss +232 -0
- package/dist/styles/footer.scss +58 -0
- package/dist/styles/form.scss +420 -0
- package/dist/styles/grid.scss +29 -0
- package/dist/styles/header.scss +276 -0
- package/dist/styles/heading.scss +118 -0
- package/dist/styles/hero.scss +185 -0
- package/dist/styles/htmlElements.scss +20 -0
- package/dist/styles/image.scss +9 -0
- package/dist/styles/label.scss +340 -0
- package/dist/styles/list-item.scss +5 -0
- package/dist/styles/loader.scss +354 -0
- package/dist/styles/logo.scss +19 -0
- package/dist/styles/main.css +9056 -0
- package/dist/styles/main.css.map +1 -0
- package/dist/styles/main.scss +0 -0
- package/dist/styles/menu-hover.scss +30 -0
- package/dist/styles/paragraph.scss +88 -0
- package/dist/styles/prompt.scss +51 -0
- package/dist/styles/radio.scss +202 -0
- package/dist/styles/select.scss +363 -0
- package/dist/styles/side-menu.scss +334 -0
- package/dist/styles/tabs.scss +540 -0
- package/dist/styles/text-area.scss +388 -0
- package/dist/styles/text-input.scss +171 -0
- package/dist/styles/toggle.scss +0 -0
- package/dist/styles/unordered-list.scss +8 -0
- package/dist/utils/ScrollHandler.js +30 -0
- package/dist/utils/accessibility.js +128 -0
- package/dist/utils/heroUtils.js +316 -0
- package/dist/utils/index.js +104 -0
- package/dist/utils/inputValidation.js +29 -0
- package/dist/utils/keyboardNavigation.js +536 -0
- package/dist/utils/labelUtils.js +708 -0
- package/dist/utils/loaderUtils.js +387 -0
- package/dist/utils/menuUtils.js +575 -0
- package/dist/utils/useHeadingAccessibility.js +298 -0
- package/dist/utils/useRadioGroup.js +260 -0
- package/dist/utils/useSelectAccessibility.js +426 -0
- package/dist/utils/useTabsAccessibility.js +278 -0
- package/dist/utils/useTextAreaAccessibility.js +255 -0
- package/dist/utils/useTextInputAccessibility.js +295 -0
- package/dist/utils/useTypographyAccessibility.js +168 -0
- package/dist/utils/useWindowSize.js +32 -0
- package/dist/utils/utils/ScrollHandler.js +26 -0
- package/dist/utils/utils/accessibility.js +133 -0
- package/dist/utils/utils/heroUtils.js +348 -0
- package/dist/utils/utils/index.js +9 -0
- package/dist/utils/utils/inputValidation.js +22 -0
- package/dist/utils/utils/keyboardNavigation.js +664 -0
- package/dist/utils/utils/labelUtils.js +772 -0
- package/dist/utils/utils/loaderUtils.js +436 -0
- package/dist/utils/utils/menuUtils.js +651 -0
- package/dist/utils/utils/useHeadingAccessibility.js +334 -0
- package/dist/utils/utils/useRadioGroup.js +311 -0
- package/dist/utils/utils/useSelectAccessibility.js +498 -0
- package/dist/utils/utils/useTabsAccessibility.js +316 -0
- package/dist/utils/utils/useTextAreaAccessibility.js +303 -0
- package/dist/utils/utils/useTextInputAccessibility.js +338 -0
- package/dist/utils/utils/useTypographyAccessibility.js +180 -0
- package/dist/utils/utils/useWindowSize.js +26 -0
- package/dist/utils/utils/validation.js +131 -0
- package/dist/utils/validation.js +139 -0
- package/package.json +90 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader Component Utilities
|
|
3
|
+
* Provides helper functions and hooks for Loader component functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook for managing loading states with automatic timeout
|
|
10
|
+
* @param {Object} options - Configuration options
|
|
11
|
+
* @param {boolean} options.initialLoading - Initial loading state
|
|
12
|
+
* @param {number} options.minLoadTime - Minimum loading time in ms
|
|
13
|
+
* @param {number} options.maxLoadTime - Maximum loading time before timeout
|
|
14
|
+
* @param {Function} options.onTimeout - Callback when loading times out
|
|
15
|
+
* @returns {Object} Loading state and handlers
|
|
16
|
+
*/
|
|
17
|
+
export const useLoadingState = ({
|
|
18
|
+
initialLoading = false,
|
|
19
|
+
minLoadTime = 300,
|
|
20
|
+
maxLoadTime = 30000,
|
|
21
|
+
onTimeout = null,
|
|
22
|
+
} = {}) => {
|
|
23
|
+
const [isLoading, setIsLoading] = useState(initialLoading);
|
|
24
|
+
const [loadingProgress, setLoadingProgress] = useState(0);
|
|
25
|
+
const [hasTimedOut, setHasTimedOut] = useState(false);
|
|
26
|
+
const [loadingMessage, setLoadingMessage] = useState('Loading...');
|
|
27
|
+
|
|
28
|
+
const startTimeRef = useRef(null);
|
|
29
|
+
const timeoutRef = useRef(null);
|
|
30
|
+
const progressIntervalRef = useRef(null);
|
|
31
|
+
|
|
32
|
+
const startLoading = useCallback(
|
|
33
|
+
(message = 'Loading...') => {
|
|
34
|
+
setIsLoading(true);
|
|
35
|
+
setHasTimedOut(false);
|
|
36
|
+
setLoadingProgress(0);
|
|
37
|
+
setLoadingMessage(message);
|
|
38
|
+
startTimeRef.current = Date.now();
|
|
39
|
+
|
|
40
|
+
// Set up timeout
|
|
41
|
+
if (maxLoadTime > 0) {
|
|
42
|
+
timeoutRef.current = setTimeout(() => {
|
|
43
|
+
setHasTimedOut(true);
|
|
44
|
+
setLoadingMessage('Loading is taking longer than expected...');
|
|
45
|
+
if (onTimeout) onTimeout();
|
|
46
|
+
}, maxLoadTime);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Set up progress simulation
|
|
50
|
+
progressIntervalRef.current = setInterval(() => {
|
|
51
|
+
setLoadingProgress((prev) => {
|
|
52
|
+
const newProgress = Math.min(prev + Math.random() * 10, 90);
|
|
53
|
+
return newProgress;
|
|
54
|
+
});
|
|
55
|
+
}, 200);
|
|
56
|
+
},
|
|
57
|
+
[maxLoadTime, onTimeout]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const stopLoading = useCallback(() => {
|
|
61
|
+
const loadTime = startTimeRef.current
|
|
62
|
+
? Date.now() - startTimeRef.current
|
|
63
|
+
: 0;
|
|
64
|
+
|
|
65
|
+
// Ensure minimum load time for UX
|
|
66
|
+
const remainingTime = Math.max(0, minLoadTime - loadTime);
|
|
67
|
+
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
setLoadingProgress(100);
|
|
71
|
+
setHasTimedOut(false);
|
|
72
|
+
|
|
73
|
+
// Clean up timers
|
|
74
|
+
if (timeoutRef.current) {
|
|
75
|
+
clearTimeout(timeoutRef.current);
|
|
76
|
+
timeoutRef.current = null;
|
|
77
|
+
}
|
|
78
|
+
if (progressIntervalRef.current) {
|
|
79
|
+
clearInterval(progressIntervalRef.current);
|
|
80
|
+
progressIntervalRef.current = null;
|
|
81
|
+
}
|
|
82
|
+
}, remainingTime);
|
|
83
|
+
}, [minLoadTime]);
|
|
84
|
+
|
|
85
|
+
const updateMessage = useCallback((message) => {
|
|
86
|
+
setLoadingMessage(message);
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
// Cleanup on unmount
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
return () => {
|
|
92
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
93
|
+
if (progressIntervalRef.current)
|
|
94
|
+
clearInterval(progressIntervalRef.current);
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
isLoading,
|
|
100
|
+
loadingProgress,
|
|
101
|
+
hasTimedOut,
|
|
102
|
+
loadingMessage,
|
|
103
|
+
startLoading,
|
|
104
|
+
stopLoading,
|
|
105
|
+
updateMessage,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hook for managing loader accessibility announcements
|
|
111
|
+
* @param {Object} options - Configuration options
|
|
112
|
+
* @returns {Object} Accessibility utilities
|
|
113
|
+
*/
|
|
114
|
+
export const useLoaderAnnouncements = ({
|
|
115
|
+
politeAnnouncements = true,
|
|
116
|
+
announceProgress = false,
|
|
117
|
+
} = {}) => {
|
|
118
|
+
const [announcement, setAnnouncement] = useState('');
|
|
119
|
+
const liveRegionRef = useRef(null);
|
|
120
|
+
|
|
121
|
+
const announce = useCallback((message, priority = 'polite') => {
|
|
122
|
+
setAnnouncement(message);
|
|
123
|
+
|
|
124
|
+
// Create live region if needed
|
|
125
|
+
if (!liveRegionRef.current) {
|
|
126
|
+
const region = document.createElement('div');
|
|
127
|
+
region.setAttribute('aria-live', priority);
|
|
128
|
+
region.setAttribute('aria-atomic', 'true');
|
|
129
|
+
region.className = 'sr-only';
|
|
130
|
+
region.style.cssText = `
|
|
131
|
+
position: absolute !important;
|
|
132
|
+
width: 1px !important;
|
|
133
|
+
height: 1px !important;
|
|
134
|
+
padding: 0 !important;
|
|
135
|
+
margin: -1px !important;
|
|
136
|
+
overflow: hidden !important;
|
|
137
|
+
clip: rect(0, 0, 0, 0) !important;
|
|
138
|
+
white-space: nowrap !important;
|
|
139
|
+
border: 0 !important;
|
|
140
|
+
`;
|
|
141
|
+
document.body.appendChild(region);
|
|
142
|
+
liveRegionRef.current = region;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Announce message
|
|
146
|
+
liveRegionRef.current.textContent = message;
|
|
147
|
+
|
|
148
|
+
// Clear after announcement
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
if (liveRegionRef.current) {
|
|
151
|
+
liveRegionRef.current.textContent = '';
|
|
152
|
+
}
|
|
153
|
+
}, 1000);
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
const announceLoadingStart = useCallback(
|
|
157
|
+
(message = 'Loading started') => {
|
|
158
|
+
if (politeAnnouncements) {
|
|
159
|
+
announce(message, 'polite');
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[announce, politeAnnouncements]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const announceLoadingComplete = useCallback(
|
|
166
|
+
(message = 'Loading complete') => {
|
|
167
|
+
if (politeAnnouncements) {
|
|
168
|
+
announce(message, 'polite');
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
[announce, politeAnnouncements]
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const announceProgressUpdate = useCallback(
|
|
175
|
+
(progress, message) => {
|
|
176
|
+
if (announceProgress && progress % 25 === 0) {
|
|
177
|
+
announce(`${message} ${progress}% complete`, 'polite');
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
[announce, announceProgress]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Cleanup
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
return () => {
|
|
186
|
+
if (
|
|
187
|
+
liveRegionRef.current &&
|
|
188
|
+
document.body.contains(liveRegionRef.current)
|
|
189
|
+
) {
|
|
190
|
+
document.body.removeChild(liveRegionRef.current);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}, []);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
announcement,
|
|
197
|
+
announce,
|
|
198
|
+
announceLoadingStart,
|
|
199
|
+
announceLoadingComplete,
|
|
200
|
+
announceProgressUpdate,
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Common loading messages for different contexts
|
|
206
|
+
*/
|
|
207
|
+
export const loadingMessages = {
|
|
208
|
+
general: 'Loading...',
|
|
209
|
+
saving: 'Saving your changes...',
|
|
210
|
+
loading: 'Loading content...',
|
|
211
|
+
processing: 'Processing your request...',
|
|
212
|
+
uploading: 'Uploading file...',
|
|
213
|
+
downloading: 'Downloading...',
|
|
214
|
+
authenticating: 'Signing you in...',
|
|
215
|
+
searching: 'Searching...',
|
|
216
|
+
deleting: 'Deleting item...',
|
|
217
|
+
creating: 'Creating new item...',
|
|
218
|
+
updating: 'Updating information...',
|
|
219
|
+
connecting: 'Connecting...',
|
|
220
|
+
syncing: 'Syncing data...',
|
|
221
|
+
importing: 'Importing data...',
|
|
222
|
+
exporting: 'Exporting data...',
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Size recommendations based on container/context
|
|
227
|
+
*/
|
|
228
|
+
export const sizeGuide = {
|
|
229
|
+
button: 'sm',
|
|
230
|
+
card: 'md',
|
|
231
|
+
modal: 'md',
|
|
232
|
+
page: 'lg',
|
|
233
|
+
fullscreen: 'xl',
|
|
234
|
+
inline: 'sm',
|
|
235
|
+
sidebar: 'md',
|
|
236
|
+
dashboard: 'lg',
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Variant recommendations based on context
|
|
241
|
+
*/
|
|
242
|
+
export const variantGuide = {
|
|
243
|
+
data: 'svg', // Data loading
|
|
244
|
+
file: 'pulse', // File operations
|
|
245
|
+
search: 'dots', // Search operations
|
|
246
|
+
save: 'spinner', // Save operations
|
|
247
|
+
general: 'svg', // General loading
|
|
248
|
+
processing: 'pulse', // Heavy processing
|
|
249
|
+
quick: 'dots', // Quick operations
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Accessibility testing utilities for loaders
|
|
254
|
+
*/
|
|
255
|
+
export const loaderTestingUtils = {
|
|
256
|
+
/**
|
|
257
|
+
* Validates loader accessibility attributes
|
|
258
|
+
*/
|
|
259
|
+
validateLoaderAccessibility() {
|
|
260
|
+
const loaders = document.querySelectorAll('[role="status"]');
|
|
261
|
+
const results = [];
|
|
262
|
+
|
|
263
|
+
loaders.forEach((loader) => {
|
|
264
|
+
const hasAriaLabel = !!loader.getAttribute('aria-label');
|
|
265
|
+
const hasAriaLive = !!loader.getAttribute('aria-live');
|
|
266
|
+
const hasAriaBusy = loader.getAttribute('aria-busy') === 'true';
|
|
267
|
+
const hasScreenReaderText = !!loader.querySelector('.sr-only');
|
|
268
|
+
|
|
269
|
+
results.push({
|
|
270
|
+
element: loader.className,
|
|
271
|
+
hasRole: loader.getAttribute('role') === 'status',
|
|
272
|
+
hasAriaLabel,
|
|
273
|
+
hasAriaLive,
|
|
274
|
+
hasAriaBusy,
|
|
275
|
+
hasScreenReaderText,
|
|
276
|
+
ariaLabel: loader.getAttribute('aria-label'),
|
|
277
|
+
isAccessible: hasAriaLabel && hasAriaLive && hasScreenReaderText,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return results;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Tests animation respect for reduced motion
|
|
286
|
+
*/
|
|
287
|
+
validateMotionPreferences() {
|
|
288
|
+
const prefersReducedMotion = window.matchMedia(
|
|
289
|
+
'(prefers-reduced-motion: reduce)'
|
|
290
|
+
).matches;
|
|
291
|
+
const animatedElements = document.querySelectorAll(
|
|
292
|
+
'.loader__svg, .loader__spinner, .loader__dots, .loader__pulse'
|
|
293
|
+
);
|
|
294
|
+
const results = [];
|
|
295
|
+
|
|
296
|
+
animatedElements.forEach((element) => {
|
|
297
|
+
const computedStyle = window.getComputedStyle(element);
|
|
298
|
+
const hasAnimation = computedStyle.animationName !== 'none';
|
|
299
|
+
const animationDuration = computedStyle.animationDuration;
|
|
300
|
+
|
|
301
|
+
results.push({
|
|
302
|
+
element: element.className,
|
|
303
|
+
prefersReducedMotion,
|
|
304
|
+
hasAnimation,
|
|
305
|
+
animationDuration,
|
|
306
|
+
respectsPreference: prefersReducedMotion
|
|
307
|
+
? animationDuration === '0s'
|
|
308
|
+
: true,
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return results;
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Tests loader focus management
|
|
317
|
+
*/
|
|
318
|
+
validateFocusManagement() {
|
|
319
|
+
const loaders = document.querySelectorAll('[role="status"]');
|
|
320
|
+
const results = [];
|
|
321
|
+
|
|
322
|
+
loaders.forEach((loader) => {
|
|
323
|
+
const isFocusable = loader.tabIndex >= 0;
|
|
324
|
+
const hasProperFocusStyle = !!window.getComputedStyle(loader, ':focus')
|
|
325
|
+
.outline;
|
|
326
|
+
|
|
327
|
+
results.push({
|
|
328
|
+
element: loader.className,
|
|
329
|
+
isFocusable,
|
|
330
|
+
hasProperFocusStyle,
|
|
331
|
+
tabIndex: loader.tabIndex,
|
|
332
|
+
isAccessible: !isFocusable, // Loaders shouldn't be focusable
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return results;
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Tests high contrast compatibility
|
|
341
|
+
*/
|
|
342
|
+
validateHighContrast() {
|
|
343
|
+
const loaders = document.querySelectorAll('.loader');
|
|
344
|
+
const results = [];
|
|
345
|
+
|
|
346
|
+
loaders.forEach((loader) => {
|
|
347
|
+
const style = window.getComputedStyle(loader);
|
|
348
|
+
const spinnerElements = loader.querySelectorAll(
|
|
349
|
+
'.loader__svg, .loader__spinner, .loader__dots, .loader__pulse'
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
let hasContrastStyles = false;
|
|
353
|
+
spinnerElements.forEach((spinner) => {
|
|
354
|
+
const spinnerStyle = window.getComputedStyle(spinner);
|
|
355
|
+
// Check if element has contrast-friendly styling
|
|
356
|
+
hasContrastStyles =
|
|
357
|
+
spinnerStyle.border !== 'none' || spinnerStyle.outline !== 'none';
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
results.push({
|
|
361
|
+
element: loader.className,
|
|
362
|
+
hasContrastStyles,
|
|
363
|
+
backgroundColor: style.backgroundColor,
|
|
364
|
+
color: style.color,
|
|
365
|
+
isHighContrastReady: hasContrastStyles,
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return results;
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Comprehensive loader audit
|
|
374
|
+
*/
|
|
375
|
+
auditLoader() {
|
|
376
|
+
return {
|
|
377
|
+
accessibility: this.validateLoaderAccessibility(),
|
|
378
|
+
motionPreferences: this.validateMotionPreferences(),
|
|
379
|
+
focusManagement: this.validateFocusManagement(),
|
|
380
|
+
highContrast: this.validateHighContrast(),
|
|
381
|
+
timestamp: new Date().toISOString(),
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Performance utilities for loaders
|
|
388
|
+
*/
|
|
389
|
+
export const loaderPerformanceUtils = {
|
|
390
|
+
/**
|
|
391
|
+
* Measures loader render performance
|
|
392
|
+
*/
|
|
393
|
+
measureRenderTime: (callback) => {
|
|
394
|
+
const start = performance.now();
|
|
395
|
+
callback();
|
|
396
|
+
const end = performance.now();
|
|
397
|
+
return end - start;
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Monitors loader animation performance
|
|
402
|
+
*/
|
|
403
|
+
monitorAnimationPerformance: (element) => {
|
|
404
|
+
let frameCount = 0;
|
|
405
|
+
let lastTime = performance.now();
|
|
406
|
+
|
|
407
|
+
const monitor = () => {
|
|
408
|
+
frameCount++;
|
|
409
|
+
const currentTime = performance.now();
|
|
410
|
+
const deltaTime = currentTime - lastTime;
|
|
411
|
+
|
|
412
|
+
if (deltaTime >= 1000) {
|
|
413
|
+
const fps = Math.round((frameCount * 1000) / deltaTime);
|
|
414
|
+
console.log(`Loader animation FPS: ${fps}`);
|
|
415
|
+
frameCount = 0;
|
|
416
|
+
lastTime = currentTime;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (element && element.offsetParent) {
|
|
420
|
+
requestAnimationFrame(monitor);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
requestAnimationFrame(monitor);
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
export default {
|
|
429
|
+
useLoadingState,
|
|
430
|
+
useLoaderAnnouncements,
|
|
431
|
+
loadingMessages,
|
|
432
|
+
sizeGuide,
|
|
433
|
+
variantGuide,
|
|
434
|
+
loaderTestingUtils,
|
|
435
|
+
loaderPerformanceUtils,
|
|
436
|
+
};
|