@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.
Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +708 -0
  3. package/dist/__tests__/Anchor.test.js +145 -0
  4. package/dist/__tests__/ArrowRight.test.js +91 -0
  5. package/dist/__tests__/Avatar.test.js +123 -0
  6. package/dist/__tests__/Button.test.js +82 -0
  7. package/dist/__tests__/Card.test.js +198 -0
  8. package/dist/__tests__/CheckCircle.test.js +98 -0
  9. package/dist/__tests__/Checkbox.test.js +161 -0
  10. package/dist/__tests__/ChevronDown.test.js +73 -0
  11. package/dist/__tests__/Close.test.js +98 -0
  12. package/dist/__tests__/EditSquare.test.js +99 -0
  13. package/dist/__tests__/Error.test.js +74 -0
  14. package/dist/__tests__/Footer.test.js +66 -0
  15. package/dist/__tests__/Heading.test.js +227 -0
  16. package/dist/__tests__/Hero.test.js +74 -0
  17. package/dist/__tests__/Label.test.js +123 -0
  18. package/dist/__tests__/Loader.test.js +115 -0
  19. package/dist/__tests__/MenuHover.test.js +137 -0
  20. package/dist/__tests__/Paragraph.test.js +93 -0
  21. package/dist/__tests__/PlusCircle.test.js +99 -0
  22. package/dist/__tests__/Radio.test.js +153 -0
  23. package/dist/__tests__/Select.test.js +187 -0
  24. package/dist/__tests__/Tabs.test.js +162 -0
  25. package/dist/__tests__/TextArea.test.js +127 -0
  26. package/dist/__tests__/TextInput.test.js +181 -0
  27. package/dist/__tests__/Toggle.test.js +120 -0
  28. package/dist/__tests__/TrashX.test.js +99 -0
  29. package/dist/__tests__/useHeadingAccessibility.test.js +144 -0
  30. package/dist/components/Anchor.js +131 -0
  31. package/dist/components/Animation.js +129 -0
  32. package/dist/components/AnimationGroup.js +207 -0
  33. package/dist/components/AnimationToggle.js +216 -0
  34. package/dist/components/Avatar.js +153 -0
  35. package/dist/components/Button.js +218 -0
  36. package/dist/components/Card.js +222 -0
  37. package/dist/components/Checkbox.js +305 -0
  38. package/dist/components/Crud.js +564 -0
  39. package/dist/components/DragAndDrop.js +337 -0
  40. package/dist/components/Error.js +206 -0
  41. package/dist/components/Footer.js +99 -0
  42. package/dist/components/Form.js +412 -0
  43. package/dist/components/Header.js +372 -0
  44. package/dist/components/Heading.js +134 -0
  45. package/dist/components/Hero.js +181 -0
  46. package/dist/components/Label.js +256 -0
  47. package/dist/components/Loader.js +302 -0
  48. package/dist/components/MenuHover.js +114 -0
  49. package/dist/components/Paragraph.js +128 -0
  50. package/dist/components/Prompt.js +61 -0
  51. package/dist/components/Radio.js +254 -0
  52. package/dist/components/Select.js +422 -0
  53. package/dist/components/SideMenu.js +313 -0
  54. package/dist/components/Tabs.js +297 -0
  55. package/dist/components/TextArea.js +370 -0
  56. package/dist/components/TextInput.js +286 -0
  57. package/dist/components/Toggle.js +186 -0
  58. package/dist/components/crudFiles/CrudEditBase.js +150 -0
  59. package/dist/components/crudFiles/CrudViewBase.js +39 -0
  60. package/dist/components/crudFiles/crudDevelopment.js +118 -0
  61. package/dist/components/crudFiles/crudEditHandlers.js +50 -0
  62. package/dist/constants/animation.js +30 -0
  63. package/dist/icons/ArrowIcon.js +32 -0
  64. package/dist/icons/ArrowRight.js +33 -0
  65. package/dist/icons/CheckCircle.js +33 -0
  66. package/dist/icons/ChevronDown.js +28 -0
  67. package/dist/icons/Close.js +33 -0
  68. package/dist/icons/EditSquare.js +33 -0
  69. package/dist/icons/Ellipses.js +34 -0
  70. package/dist/icons/Hamburger.js +39 -0
  71. package/dist/icons/LoadingSpinner.js +42 -0
  72. package/dist/icons/PlusCircle.js +33 -0
  73. package/dist/icons/SaveIcon.js +32 -0
  74. package/dist/icons/TrashX.js +33 -0
  75. package/dist/icons/__tests__/CheckCircle.test.js +9 -0
  76. package/dist/icons/__tests__/ChevronDown.test.js +9 -0
  77. package/dist/icons/__tests__/Close.test.js +9 -0
  78. package/dist/icons/__tests__/EditSquare.test.js +9 -0
  79. package/dist/icons/__tests__/PlusCircle.test.js +9 -0
  80. package/dist/icons/__tests__/TrashX.test.js +9 -0
  81. package/dist/icons/index.js +89 -0
  82. package/dist/index.js +332 -0
  83. package/dist/setupTests.js +3 -0
  84. package/dist/styles/_variables.scss +286 -0
  85. package/dist/styles/anchor.scss +40 -0
  86. package/dist/styles/animation-accessibility.scss +96 -0
  87. package/dist/styles/animation-toggle.scss +233 -0
  88. package/dist/styles/animation.scss +3781 -0
  89. package/dist/styles/avatar.scss +285 -0
  90. package/dist/styles/button.scss +430 -0
  91. package/dist/styles/card.scss +210 -0
  92. package/dist/styles/checkbox.scss +160 -0
  93. package/dist/styles/crud.scss +474 -0
  94. package/dist/styles/dragAndDrop.scss +312 -0
  95. package/dist/styles/error.scss +232 -0
  96. package/dist/styles/footer.scss +58 -0
  97. package/dist/styles/form.scss +420 -0
  98. package/dist/styles/grid.scss +29 -0
  99. package/dist/styles/header.scss +276 -0
  100. package/dist/styles/heading.scss +118 -0
  101. package/dist/styles/hero.scss +185 -0
  102. package/dist/styles/htmlElements.scss +20 -0
  103. package/dist/styles/image.scss +9 -0
  104. package/dist/styles/label.scss +340 -0
  105. package/dist/styles/list-item.scss +5 -0
  106. package/dist/styles/loader.scss +354 -0
  107. package/dist/styles/logo.scss +19 -0
  108. package/dist/styles/main.css +9056 -0
  109. package/dist/styles/main.css.map +1 -0
  110. package/dist/styles/main.scss +0 -0
  111. package/dist/styles/menu-hover.scss +30 -0
  112. package/dist/styles/paragraph.scss +88 -0
  113. package/dist/styles/prompt.scss +51 -0
  114. package/dist/styles/radio.scss +202 -0
  115. package/dist/styles/select.scss +363 -0
  116. package/dist/styles/side-menu.scss +334 -0
  117. package/dist/styles/tabs.scss +540 -0
  118. package/dist/styles/text-area.scss +388 -0
  119. package/dist/styles/text-input.scss +171 -0
  120. package/dist/styles/toggle.scss +0 -0
  121. package/dist/styles/unordered-list.scss +8 -0
  122. package/dist/utils/ScrollHandler.js +30 -0
  123. package/dist/utils/accessibility.js +128 -0
  124. package/dist/utils/heroUtils.js +316 -0
  125. package/dist/utils/index.js +104 -0
  126. package/dist/utils/inputValidation.js +29 -0
  127. package/dist/utils/keyboardNavigation.js +536 -0
  128. package/dist/utils/labelUtils.js +708 -0
  129. package/dist/utils/loaderUtils.js +387 -0
  130. package/dist/utils/menuUtils.js +575 -0
  131. package/dist/utils/useHeadingAccessibility.js +298 -0
  132. package/dist/utils/useRadioGroup.js +260 -0
  133. package/dist/utils/useSelectAccessibility.js +426 -0
  134. package/dist/utils/useTabsAccessibility.js +278 -0
  135. package/dist/utils/useTextAreaAccessibility.js +255 -0
  136. package/dist/utils/useTextInputAccessibility.js +295 -0
  137. package/dist/utils/useTypographyAccessibility.js +168 -0
  138. package/dist/utils/useWindowSize.js +32 -0
  139. package/dist/utils/utils/ScrollHandler.js +26 -0
  140. package/dist/utils/utils/accessibility.js +133 -0
  141. package/dist/utils/utils/heroUtils.js +348 -0
  142. package/dist/utils/utils/index.js +9 -0
  143. package/dist/utils/utils/inputValidation.js +22 -0
  144. package/dist/utils/utils/keyboardNavigation.js +664 -0
  145. package/dist/utils/utils/labelUtils.js +772 -0
  146. package/dist/utils/utils/loaderUtils.js +436 -0
  147. package/dist/utils/utils/menuUtils.js +651 -0
  148. package/dist/utils/utils/useHeadingAccessibility.js +334 -0
  149. package/dist/utils/utils/useRadioGroup.js +311 -0
  150. package/dist/utils/utils/useSelectAccessibility.js +498 -0
  151. package/dist/utils/utils/useTabsAccessibility.js +316 -0
  152. package/dist/utils/utils/useTextAreaAccessibility.js +303 -0
  153. package/dist/utils/utils/useTextInputAccessibility.js +338 -0
  154. package/dist/utils/utils/useTypographyAccessibility.js +180 -0
  155. package/dist/utils/utils/useWindowSize.js +26 -0
  156. package/dist/utils/utils/validation.js +131 -0
  157. package/dist/utils/validation.js +139 -0
  158. 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
+ };