@umituz/react-native-design-system 4.28.10 → 4.28.12
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/package.json +36 -8
- package/src/atoms/AtomicAvatar.tsx +69 -40
- package/src/atoms/AtomicSpinner.tsx +24 -22
- package/src/atoms/AtomicText.tsx +32 -27
- package/src/atoms/AtomicTextArea.tsx +17 -15
- package/src/atoms/EmptyState.tsx +45 -42
- package/src/atoms/button/AtomicButton.tsx +8 -9
- package/src/atoms/card/AtomicCard.tsx +26 -8
- package/src/atoms/datepicker/components/DatePickerButton.tsx +8 -8
- package/src/atoms/datepicker/components/DatePickerModal.tsx +7 -7
- package/src/atoms/fab/styles/fabStyles.ts +1 -22
- package/src/atoms/icon/index.ts +6 -20
- package/src/atoms/picker/components/PickerModal.tsx +24 -4
- package/src/atoms/skeleton/AtomicSkeleton.tsx +9 -11
- package/src/carousel/Carousel.tsx +43 -20
- package/src/carousel/carouselCalculations.ts +12 -9
- package/src/carousel/index.ts +0 -1
- package/src/device/detection/iPadDetection.ts +5 -14
- package/src/device/infrastructure/services/DeviceFeatureService.ts +89 -9
- package/src/device/infrastructure/services/DeviceInfoService.ts +33 -0
- package/src/device/infrastructure/services/UserFriendlyIdService.ts +8 -6
- package/src/device/infrastructure/utils/__tests__/stringUtils.test.ts +56 -20
- package/src/device/infrastructure/utils/nativeModuleUtils.ts +16 -2
- package/src/device/infrastructure/utils/stringUtils.ts +51 -5
- package/src/filesystem/domain/utils/FileUtils.ts +5 -1
- package/src/image/domain/utils/ImageUtils.ts +6 -0
- package/src/layouts/AppHeader/AppHeader.tsx +13 -3
- package/src/layouts/Container/Container.tsx +19 -1
- package/src/layouts/FormLayout/FormLayout.tsx +20 -1
- package/src/layouts/Grid/Grid.tsx +34 -4
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +4 -0
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +42 -3
- package/src/molecules/Divider/types.ts +1 -1
- package/src/molecules/SearchBar/SearchBar.tsx +28 -24
- package/src/molecules/StepHeader/StepHeader.tsx +1 -1
- package/src/molecules/StepProgress/StepProgress.tsx +1 -1
- package/src/molecules/action-footer/ActionFooter.tsx +33 -32
- package/src/molecules/alerts/AlertModal.tsx +36 -20
- package/src/molecules/alerts/AlertService.ts +60 -15
- package/src/molecules/avatar/Avatar.tsx +48 -40
- package/src/molecules/avatar/AvatarGroup.tsx +8 -8
- package/src/molecules/bottom-sheet/components/BottomSheet.tsx +1 -1
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +1 -1
- package/src/molecules/bottom-sheet/components/filter/FilterSheet.tsx +1 -1
- package/src/molecules/calendar/infrastructure/utils/DateUtilities.ts +12 -1
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +48 -32
- package/src/molecules/circular-menu/CircularMenuItem.tsx +1 -1
- package/src/molecules/countdown/components/CountdownHeader.tsx +1 -1
- package/src/molecules/countdown/components/TimeUnit.tsx +1 -1
- package/src/molecules/hero-section/HeroSection.tsx +1 -1
- package/src/molecules/icon-grid/IconGrid.tsx +1 -1
- package/src/molecules/info-grid/InfoGrid.tsx +6 -4
- package/src/molecules/navigation/TabsNavigator.tsx +1 -1
- package/src/molecules/navigation/components/NavigationHeader.tsx +1 -1
- package/src/organisms/FormContainer.tsx +11 -1
- package/src/tanstack/domain/utils/ErrorHelpers.ts +2 -2
- package/src/tanstack/domain/utils/MetricsCalculator.ts +6 -1
- package/src/theme/core/colors/ColorUtils.ts +7 -4
- package/src/utils/formatters/stringFormatter.ts +18 -3
- package/src/utils/index.ts +140 -0
- package/src/utils/math/CalculationUtils.ts +10 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Calendar Day Cell Component
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React from 'react';
|
|
5
|
+
import React, { useMemo } from 'react';
|
|
6
6
|
import { TouchableOpacity, View, StyleProp, ViewStyle } from 'react-native';
|
|
7
7
|
import { AtomicText } from '../../../../atoms';
|
|
8
8
|
import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
|
|
@@ -38,22 +38,34 @@ export const CalendarDayCell: React.FC<CalendarDayCellProps> = React.memo(({
|
|
|
38
38
|
const visibleEvents = day.events.slice(0, maxEventIndicators);
|
|
39
39
|
const hiddenEventCount = Math.max(0, eventCount - maxEventIndicators);
|
|
40
40
|
|
|
41
|
+
const cellStyle = useMemo(() => [
|
|
42
|
+
calendarStyles.dayCell,
|
|
43
|
+
{
|
|
44
|
+
backgroundColor: isSelected ? tokens.colors.primary : 'transparent',
|
|
45
|
+
borderColor: isSelected
|
|
46
|
+
? tokens.colors.primary
|
|
47
|
+
: day.isToday
|
|
48
|
+
? tokens.colors.primary
|
|
49
|
+
: tokens.colors.border,
|
|
50
|
+
borderWidth: isSelected ? 2 : day.isToday ? 2 : 1,
|
|
51
|
+
opacity: day.isDisabled ? 0.4 : 1,
|
|
52
|
+
},
|
|
53
|
+
dayStyle,
|
|
54
|
+
], [isSelected, day.isToday, day.isDisabled, tokens.colors.primary, tokens.colors.border, dayStyle]);
|
|
55
|
+
|
|
56
|
+
const dayTextStyle = useMemo(() => [
|
|
57
|
+
calendarStyles.dayText,
|
|
58
|
+
day.isToday && !isSelected && { fontWeight: 'bold' as const },
|
|
59
|
+
], [day.isToday, isSelected]);
|
|
60
|
+
|
|
61
|
+
const todayDotStyle = useMemo(() => [
|
|
62
|
+
calendarStyles.eventDot,
|
|
63
|
+
{ backgroundColor: tokens.colors.success },
|
|
64
|
+
], [calendarStyles.eventDot, tokens.colors.success]);
|
|
65
|
+
|
|
41
66
|
return (
|
|
42
67
|
<TouchableOpacity
|
|
43
|
-
style={
|
|
44
|
-
calendarStyles.dayCell,
|
|
45
|
-
{
|
|
46
|
-
backgroundColor: isSelected ? tokens.colors.primary : 'transparent',
|
|
47
|
-
borderColor: isSelected
|
|
48
|
-
? tokens.colors.primary
|
|
49
|
-
: day.isToday
|
|
50
|
-
? tokens.colors.primary
|
|
51
|
-
: tokens.colors.border,
|
|
52
|
-
borderWidth: isSelected ? 2 : day.isToday ? 2 : 1,
|
|
53
|
-
opacity: day.isDisabled ? 0.4 : 1,
|
|
54
|
-
},
|
|
55
|
-
dayStyle,
|
|
56
|
-
]}
|
|
68
|
+
style={cellStyle}
|
|
57
69
|
onPress={() => !day.isDisabled && onDateSelect(day.date)}
|
|
58
70
|
disabled={day.isDisabled}
|
|
59
71
|
testID={testID ? `${testID}-day-${index}` : undefined}
|
|
@@ -64,31 +76,35 @@ export const CalendarDayCell: React.FC<CalendarDayCellProps> = React.memo(({
|
|
|
64
76
|
<AtomicText
|
|
65
77
|
type="bodyMedium"
|
|
66
78
|
color={isSelected ? 'inverse' : day.isCurrentMonth ? 'primary' : 'secondary'}
|
|
67
|
-
style={
|
|
79
|
+
style={dayTextStyle}
|
|
68
80
|
>
|
|
69
81
|
{day.date.getDate()}
|
|
70
82
|
</AtomicText>
|
|
71
83
|
|
|
72
84
|
<View style={calendarStyles.eventIndicators}>
|
|
73
85
|
{day.isToday && eventCount === 0 && (
|
|
74
|
-
<View style={
|
|
86
|
+
<View style={todayDotStyle} />
|
|
75
87
|
)}
|
|
76
88
|
|
|
77
|
-
{visibleEvents.map((event) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
?
|
|
85
|
-
:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
{visibleEvents.map((event) => {
|
|
90
|
+
const eventDotStyle = useMemo(() => [
|
|
91
|
+
calendarStyles.eventDot,
|
|
92
|
+
{
|
|
93
|
+
backgroundColor: event.color
|
|
94
|
+
? event.color
|
|
95
|
+
: event.isCompleted
|
|
96
|
+
? tokens.colors.success
|
|
97
|
+
: tokens.colors.primary,
|
|
98
|
+
},
|
|
99
|
+
], [event.color, event.isCompleted, tokens.colors.success, tokens.colors.primary]);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<View
|
|
103
|
+
key={event.id}
|
|
104
|
+
style={eventDotStyle}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
92
108
|
|
|
93
109
|
{showEventCount && hiddenEventCount > 0 && (
|
|
94
110
|
<AtomicText type="bodySmall" color="secondary" style={calendarStyles.moreEventsText}>
|
|
@@ -4,7 +4,7 @@ import { AtomicIcon } from "../../atoms";
|
|
|
4
4
|
import { AtomicText } from "../../atoms";
|
|
5
5
|
import { useAppDesignTokens } from "../../theme";
|
|
6
6
|
import { LAYOUT } from "./constants";
|
|
7
|
-
import { calculateResponsiveSize } from "../../
|
|
7
|
+
import { calculateResponsiveSize } from "../../responsive";
|
|
8
8
|
|
|
9
9
|
export interface CircularMenuItemProps {
|
|
10
10
|
icon: string;
|
|
@@ -3,7 +3,7 @@ import { View, StyleSheet, TouchableOpacity } from 'react-native';
|
|
|
3
3
|
import { AtomicText, AtomicIcon, useIconName } from '../../../atoms';
|
|
4
4
|
import { useAppDesignTokens } from '../../../theme';
|
|
5
5
|
import type { IconName } from '../../../atoms';
|
|
6
|
-
import { calculateResponsiveSize } from '../../../
|
|
6
|
+
import { calculateResponsiveSize } from '../../../responsive';
|
|
7
7
|
import { COUNTDOWN_TOGGLE } from '../../../constants';
|
|
8
8
|
|
|
9
9
|
export interface CountdownHeaderProps {
|
|
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import { View, StyleSheet } from 'react-native';
|
|
3
3
|
import { AtomicText } from '../../../atoms';
|
|
4
4
|
import { useAppDesignTokens } from '../../../theme';
|
|
5
|
-
import { calculateResponsiveSize } from '../../../
|
|
5
|
+
import { calculateResponsiveSize } from '../../../responsive';
|
|
6
6
|
import { COUNTDOWN_SIZES } from '../../../constants';
|
|
7
7
|
|
|
8
8
|
export interface TimeUnitProps {
|
|
@@ -3,7 +3,7 @@ import React, { useMemo } from 'react';
|
|
|
3
3
|
import { View, StyleSheet, Text, Image } from 'react-native';
|
|
4
4
|
import { useAppDesignTokens } from '../../theme';
|
|
5
5
|
import type { HeroSectionProps } from './types';
|
|
6
|
-
import { calculateResponsiveSize } from '../../
|
|
6
|
+
import { calculateResponsiveSize } from '../../responsive';
|
|
7
7
|
import { HERO_ICON } from '../../constants';
|
|
8
8
|
|
|
9
9
|
export const HeroSection: React.FC<HeroSectionProps> = ({
|
|
@@ -21,7 +21,7 @@ import { AtomicIcon } from '../../atoms';
|
|
|
21
21
|
import { AtomicText } from '../../atoms';
|
|
22
22
|
import type { IconName } from '../../atoms';
|
|
23
23
|
import { calculateGridItemWidth } from '../../utils/math';
|
|
24
|
-
import { calculateResponsiveSize } from '../../
|
|
24
|
+
import { calculateResponsiveSize } from '../../responsive';
|
|
25
25
|
import { ICON_GRID } from '../../constants';
|
|
26
26
|
|
|
27
27
|
export interface IconGridItem {
|
|
@@ -5,10 +5,10 @@ import { AtomicText } from '../../atoms/AtomicText';
|
|
|
5
5
|
import { AtomicIcon } from '../../atoms';
|
|
6
6
|
import { useAppDesignTokens } from '../../theme';
|
|
7
7
|
import type { InfoGridProps } from './types';
|
|
8
|
-
import { calculateResponsiveSize } from '../../
|
|
8
|
+
import { calculateResponsiveSize } from '../../responsive';
|
|
9
9
|
import { INFO_GRID_ICONS } from '../../constants';
|
|
10
10
|
|
|
11
|
-
export const InfoGrid: React.FC<InfoGridProps> = ({
|
|
11
|
+
export const InfoGrid: React.FC<InfoGridProps> = React.memo(({
|
|
12
12
|
title,
|
|
13
13
|
headerIcon,
|
|
14
14
|
items,
|
|
@@ -73,6 +73,8 @@ export const InfoGrid: React.FC<InfoGridProps> = ({
|
|
|
73
73
|
},
|
|
74
74
|
}), [tokens, columns, spacingMultiplier]);
|
|
75
75
|
|
|
76
|
+
const memoizedItemStyle = useMemo(() => itemStyle, [itemStyle]);
|
|
77
|
+
|
|
76
78
|
return (
|
|
77
79
|
<View style={[styles.container, style]}>
|
|
78
80
|
{(title || headerIcon) && (
|
|
@@ -88,7 +90,7 @@ export const InfoGrid: React.FC<InfoGridProps> = ({
|
|
|
88
90
|
|
|
89
91
|
<View style={styles.grid}>
|
|
90
92
|
{items.map((item) => (
|
|
91
|
-
<View key={item.text} style={[styles.item,
|
|
93
|
+
<View key={item.text} style={[styles.item, memoizedItemStyle]}>
|
|
92
94
|
{item.icon && (
|
|
93
95
|
<View style={styles.iconContainer}>
|
|
94
96
|
<AtomicIcon name={item.icon} size="xs" color="primary" />
|
|
@@ -102,4 +104,4 @@ export const InfoGrid: React.FC<InfoGridProps> = ({
|
|
|
102
104
|
</View>
|
|
103
105
|
</View>
|
|
104
106
|
);
|
|
105
|
-
};
|
|
107
|
+
});
|
|
@@ -6,7 +6,7 @@ import type { TabNavigatorConfig, TabScreen } from "./types";
|
|
|
6
6
|
import { NavigationValidator } from "./utils/NavigationValidator";
|
|
7
7
|
import { createTabScreen } from "./utils/ScreenFactory";
|
|
8
8
|
import { useAppDesignTokens } from "../../theme";
|
|
9
|
-
import { calculateResponsiveSize } from "../../
|
|
9
|
+
import { calculateResponsiveSize } from "../../responsive";
|
|
10
10
|
import { NAVIGATION } from "../../constants";
|
|
11
11
|
|
|
12
12
|
// Create the navigator instance ONCE outside the component
|
|
@@ -4,7 +4,7 @@ import { AtomicText } from '../../../atoms';
|
|
|
4
4
|
import { AtomicIcon, useIconName } from '../../../atoms';
|
|
5
5
|
import { useAppDesignTokens } from '../../../theme';
|
|
6
6
|
import { useSafeAreaInsets } from '../../../safe-area';
|
|
7
|
-
import { calculateResponsiveSize } from '../../../
|
|
7
|
+
import { calculateResponsiveSize } from '../../../responsive';
|
|
8
8
|
import { NAVIGATION } from '../../../constants';
|
|
9
9
|
|
|
10
10
|
export interface NavigationHeaderProps {
|
|
@@ -97,6 +97,9 @@ export const FormContainer: React.FC<FormContainerProps> = ({
|
|
|
97
97
|
showsVerticalScrollIndicator = false,
|
|
98
98
|
testID,
|
|
99
99
|
showBorder = true,
|
|
100
|
+
accessibilityLabel,
|
|
101
|
+
accessibilityHint,
|
|
102
|
+
accessible,
|
|
100
103
|
}) => {
|
|
101
104
|
const tokens = useAppDesignTokens();
|
|
102
105
|
const insets = useSafeAreaInsets();
|
|
@@ -138,7 +141,14 @@ export const FormContainer: React.FC<FormContainerProps> = ({
|
|
|
138
141
|
);
|
|
139
142
|
|
|
140
143
|
return (
|
|
141
|
-
<View
|
|
144
|
+
<View
|
|
145
|
+
style={[styles.container, containerStyle]}
|
|
146
|
+
testID={testID}
|
|
147
|
+
accessibilityLabel={accessibilityLabel}
|
|
148
|
+
accessibilityHint={accessibilityHint}
|
|
149
|
+
accessible={accessible !== false}
|
|
150
|
+
accessibilityRole="form"
|
|
151
|
+
>
|
|
142
152
|
<View style={styles.surface}>
|
|
143
153
|
<ScrollView
|
|
144
154
|
style={styles.scrollView}
|
|
@@ -146,8 +146,8 @@ export function getErrorCode(error: unknown): string | null {
|
|
|
146
146
|
/**
|
|
147
147
|
* Log error in development
|
|
148
148
|
*/
|
|
149
|
-
export function logError(
|
|
149
|
+
export function logError(context: string, error: unknown): void {
|
|
150
150
|
if (__DEV__) {
|
|
151
|
-
|
|
151
|
+
console.error(`[${context}]`, error);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -22,7 +22,12 @@ export class MetricsCalculator {
|
|
|
22
22
|
* Calculates fetch time for a query
|
|
23
23
|
*/
|
|
24
24
|
static calculateFetchTime(query: Query): number {
|
|
25
|
-
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const updatedAt = query.state.dataUpdatedAt ?? now;
|
|
27
|
+
const fetchTime = now - updatedAt;
|
|
28
|
+
|
|
29
|
+
// Ensure non-negative (handles cases where dataUpdatedAt is in the future)
|
|
30
|
+
return Math.max(0, fetchTime);
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -34,10 +34,13 @@ export const withAlpha = (hexColor: string, alpha: number): string => {
|
|
|
34
34
|
return hexColor;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
let hex = hexColor;
|
|
38
|
+
|
|
39
|
+
// Convert 3-digit hex to 6-digit (e.g., #RGB → #RRGGBB)
|
|
40
|
+
if (hex.length === 4) {
|
|
41
|
+
// Remove # and double each character, then add # back
|
|
42
|
+
hex = '#' + hex.slice(1).split('').map(c => c + c).join('');
|
|
43
|
+
}
|
|
41
44
|
|
|
42
45
|
const alphaHex = Math.round(alpha * 255)
|
|
43
46
|
.toString(16)
|
|
@@ -22,10 +22,19 @@ export function formatFileSize(bytes: number, options: FileSizeFormatOptions = {
|
|
|
22
22
|
const { decimals = 1, locale = 'en-US' } = options;
|
|
23
23
|
|
|
24
24
|
if (bytes === 0) return '0 Bytes';
|
|
25
|
+
if (bytes < 0) {
|
|
26
|
+
if (__DEV__) {
|
|
27
|
+
console.warn(`[formatFileSize] File size cannot be negative (received: ${bytes}), treating as 0`);
|
|
28
|
+
}
|
|
29
|
+
return '0 Bytes';
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
const k = 1024;
|
|
27
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
28
|
-
const i = Math.
|
|
33
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; // Added EB for safety
|
|
34
|
+
const i = Math.min(
|
|
35
|
+
Math.floor(Math.log(bytes) / Math.log(k)),
|
|
36
|
+
sizes.length - 1 // Prevent array out of bounds
|
|
37
|
+
);
|
|
29
38
|
|
|
30
39
|
return `${formatNumber(bytes / Math.pow(k, i), { decimals, locale })} ${sizes[i]}`;
|
|
31
40
|
}
|
|
@@ -130,10 +139,16 @@ export function formatPhone(phone: string, options: PhoneFormatOptions = {}): st
|
|
|
130
139
|
* @returns Truncated text
|
|
131
140
|
*/
|
|
132
141
|
export function truncateText(text: string, maxLength: number, suffix: string = '...'): string {
|
|
133
|
-
if (!text
|
|
142
|
+
if (!text) return '';
|
|
143
|
+
if (text.length <= maxLength) {
|
|
134
144
|
return text;
|
|
135
145
|
}
|
|
136
146
|
|
|
147
|
+
// Ensure we don't end up with negative slice length
|
|
148
|
+
if (maxLength <= suffix.length) {
|
|
149
|
+
return suffix.slice(0, maxLength);
|
|
150
|
+
}
|
|
151
|
+
|
|
137
152
|
return text.slice(0, maxLength - suffix.length) + suffix;
|
|
138
153
|
}
|
|
139
154
|
|
package/src/utils/index.ts
CHANGED
|
@@ -2,11 +2,151 @@
|
|
|
2
2
|
* Utils Module
|
|
3
3
|
*
|
|
4
4
|
* Centralized utility functions for the design system.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { createMappedArray, retryWithBackoff, formatFileSize, clamp, Logger, DesignSystemError } from '@umituz/react-native-design-system/utils';
|
|
5
8
|
*/
|
|
6
9
|
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// ARRAY UTILITIES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
7
14
|
export {
|
|
8
15
|
createMappedArray,
|
|
9
16
|
safeSlice,
|
|
10
17
|
filterById,
|
|
11
18
|
findById,
|
|
12
19
|
} from './arrayUtils';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// TIME CONSTANTS
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
MILLISECONDS_PER_SECOND,
|
|
27
|
+
MILLISECONDS_PER_MINUTE,
|
|
28
|
+
MILLISECONDS_PER_HOUR,
|
|
29
|
+
MILLISECONDS_PER_DAY,
|
|
30
|
+
SECONDS_PER_MINUTE,
|
|
31
|
+
SECONDS_PER_HOUR,
|
|
32
|
+
SECONDS_PER_DAY,
|
|
33
|
+
MINUTES_PER_HOUR,
|
|
34
|
+
MINUTES_PER_DAY,
|
|
35
|
+
ONE_SECOND_MS,
|
|
36
|
+
FIVE_SECONDS_MS,
|
|
37
|
+
TEN_SECONDS_MS,
|
|
38
|
+
THIRTY_SECONDS_MS,
|
|
39
|
+
ONE_MINUTE_MS,
|
|
40
|
+
FIVE_MINUTES_MS,
|
|
41
|
+
TEN_MINUTES_MS,
|
|
42
|
+
THIRTY_MINUTES_MS,
|
|
43
|
+
ONE_HOUR_MS,
|
|
44
|
+
ONE_DAY_MS,
|
|
45
|
+
DEFAULT_TIMEOUT_MS,
|
|
46
|
+
DEFAULT_LONG_TIMEOUT_MS,
|
|
47
|
+
DEFAULT_CACHE_TTL_MS,
|
|
48
|
+
} from './constants/TimeConstants';
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// ASYNC UTILITIES
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
retryWithBackoff,
|
|
56
|
+
retryWithTimeout,
|
|
57
|
+
isNetworkError,
|
|
58
|
+
isRetryableHttpStatus,
|
|
59
|
+
type RetryOptions,
|
|
60
|
+
} from './async';
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// FORMATTER UTILITIES
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
formatFileSize,
|
|
68
|
+
formatDuration,
|
|
69
|
+
formatPhone,
|
|
70
|
+
truncateText,
|
|
71
|
+
capitalize,
|
|
72
|
+
toTitleCase,
|
|
73
|
+
toSlug,
|
|
74
|
+
type FileSizeFormatOptions,
|
|
75
|
+
type DurationFormatOptions,
|
|
76
|
+
type PhoneFormatOptions,
|
|
77
|
+
} from './formatters/stringFormatter';
|
|
78
|
+
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// MATH UTILITIES
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
clamp,
|
|
85
|
+
calculatePercentage,
|
|
86
|
+
roundTo,
|
|
87
|
+
calculateGridItemWidth,
|
|
88
|
+
isInRange,
|
|
89
|
+
lerp,
|
|
90
|
+
mapRange,
|
|
91
|
+
normalizeProgress,
|
|
92
|
+
formatPercentage,
|
|
93
|
+
calculateStepProgress,
|
|
94
|
+
isComplete,
|
|
95
|
+
hasStarted,
|
|
96
|
+
intensityToOpacity,
|
|
97
|
+
createRgbaColor,
|
|
98
|
+
ratioToOpacity,
|
|
99
|
+
} from './math';
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// LOGGER
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
Logger,
|
|
107
|
+
logger,
|
|
108
|
+
type LoggerConfig,
|
|
109
|
+
} from './logger';
|
|
110
|
+
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// HOOKS
|
|
113
|
+
// =============================================================================
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
useAsyncOperation,
|
|
117
|
+
type AsyncOperationOptions,
|
|
118
|
+
type AsyncOperationState,
|
|
119
|
+
type AsyncOperationActions,
|
|
120
|
+
type AsyncOperationReturn,
|
|
121
|
+
type ErrorHandler as AsyncErrorHandler,
|
|
122
|
+
} from './hooks';
|
|
123
|
+
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// ERROR HANDLING
|
|
126
|
+
// =============================================================================
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
DesignSystemError,
|
|
130
|
+
ErrorCodes,
|
|
131
|
+
ErrorCategory,
|
|
132
|
+
} from './errors';
|
|
133
|
+
|
|
134
|
+
export type {
|
|
135
|
+
ErrorCode,
|
|
136
|
+
ErrorMetadata,
|
|
137
|
+
} from './errors';
|
|
138
|
+
|
|
139
|
+
export { ErrorHandler } from './errors/ErrorHandler';
|
|
140
|
+
|
|
141
|
+
export type {
|
|
142
|
+
Result,
|
|
143
|
+
} from './errors/types/Result';
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
ok,
|
|
147
|
+
err,
|
|
148
|
+
unwrap,
|
|
149
|
+
unwrapOr,
|
|
150
|
+
map,
|
|
151
|
+
mapError,
|
|
152
|
+
} from './errors';
|
|
@@ -109,5 +109,14 @@ export function mapRange(
|
|
|
109
109
|
outMin: number,
|
|
110
110
|
outMax: number
|
|
111
111
|
): number {
|
|
112
|
-
|
|
112
|
+
const range = inMax - inMin;
|
|
113
|
+
if (range === 0) {
|
|
114
|
+
// When input range is zero, return output minimum
|
|
115
|
+
// This prevents division by zero (Infinity/NaN)
|
|
116
|
+
if (__DEV__) {
|
|
117
|
+
console.warn(`[mapRange] Input range is zero (inMin=${inMin}, inMax=${inMax}), returning outMin=${outMin}`);
|
|
118
|
+
}
|
|
119
|
+
return outMin;
|
|
120
|
+
}
|
|
121
|
+
return ((value - inMin) * (outMax - outMin)) / range + outMin;
|
|
113
122
|
}
|