@umituz/react-native-design-system 4.27.2 → 4.27.4
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/.agents/workflows/setup-design-system.md +46 -0
- package/package.json +2 -1
- package/src/atoms/card/AtomicCard.tsx +2 -2
- package/src/molecules/SearchBar/SearchBar.tsx +5 -2
- package/src/molecules/calendar/infrastructure/services/CalendarService.ts +15 -2
- package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +10 -0
- package/src/utils/hooks/useAsyncOperation.ts +8 -6
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Sets up or updates the @umituz/react-native-design-system package in a React Native app with its components and tokens.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Design System Setup Skill
|
|
6
|
+
|
|
7
|
+
When this workflow/skill is invoked, follow these explicit instructions to configure `@umituz/react-native-design-system`.
|
|
8
|
+
|
|
9
|
+
## Step 1: Check and Update `package.json`
|
|
10
|
+
- Analyze the project's `package.json`.
|
|
11
|
+
- Check if `@umituz/react-native-design-system` exists.
|
|
12
|
+
- If missing: Install with `npm install @umituz/react-native-design-system`.
|
|
13
|
+
- If outdated: Update it to the latest version.
|
|
14
|
+
|
|
15
|
+
## Step 2: Install Required Peer Dependencies
|
|
16
|
+
The design system heavily relies on Expo modules, Gesture Handler, and core UI dependencies. Check & install missing peer dependencies (use `npx expo install` for Expo packages):
|
|
17
|
+
- `expo-font`, `expo-asset`, `expo-haptics`, `expo-clipboard`, `expo-device`, `expo-network`, `expo-secure-store`
|
|
18
|
+
- `react-native-gesture-handler`
|
|
19
|
+
- `react-native-safe-area-context`
|
|
20
|
+
- `react-native-svg`
|
|
21
|
+
- `@gorhom/portal` (if used in app)
|
|
22
|
+
- `@tanstack/react-query` & `zustand`
|
|
23
|
+
|
|
24
|
+
// turbo
|
|
25
|
+
## Step 3: Native Setup (If bare React Native)
|
|
26
|
+
If the project structure indicates an iOS build folder is present, run:
|
|
27
|
+
```bash
|
|
28
|
+
cd ios && pod install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Step 4: Setup Initialization & Boilerplate
|
|
32
|
+
- Locate the main entry point (e.g., `App.tsx`, `app/_layout.tsx`, or a primary wrapper).
|
|
33
|
+
- Wrap the app with `DesignSystemProvider`.
|
|
34
|
+
- **CRITICAL NOTE:** DO NOT use barrel imports (direct `@umituz/react-native-design-system`). Use sub-path imports instead.
|
|
35
|
+
```typescript
|
|
36
|
+
import { DesignSystemProvider } from '@umituz/react-native-design-system/theme';
|
|
37
|
+
|
|
38
|
+
// Inside App Root
|
|
39
|
+
<DesignSystemProvider>
|
|
40
|
+
{children}
|
|
41
|
+
</DesignSystemProvider>
|
|
42
|
+
```
|
|
43
|
+
- If fonts need to be loaded, ensure the app's loading phase properly waits for `expo-font` before rendering the main UI.
|
|
44
|
+
|
|
45
|
+
## Step 5: Summary
|
|
46
|
+
Output what was done: the packages updated, peer dependencies installed, and files modified to inject the `DesignSystemProvider`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.27.
|
|
3
|
+
"version": "4.27.4",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities - TanStack persistence and expo-image-manipulator now lazy loaded",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -341,6 +341,7 @@
|
|
|
341
341
|
"files": [
|
|
342
342
|
"dist",
|
|
343
343
|
"src",
|
|
344
|
+
".agents",
|
|
344
345
|
"README.md",
|
|
345
346
|
"LICENSE"
|
|
346
347
|
]
|
|
@@ -182,13 +182,13 @@ const AtomicCardComponent: React.FC<AtomicCardProps> = ({
|
|
|
182
182
|
|
|
183
183
|
const paddingValue = getCardPadding(padding, tokens);
|
|
184
184
|
|
|
185
|
-
const containerStyle = [
|
|
185
|
+
const containerStyle = useMemo(() => [
|
|
186
186
|
cardStyles.container,
|
|
187
187
|
{ borderRadius: tokens.borders.radius.lg },
|
|
188
188
|
variantStyles.container,
|
|
189
189
|
selected && { borderColor: tokens.colors.primary, borderWidth: 2 },
|
|
190
190
|
style,
|
|
191
|
-
];
|
|
191
|
+
], [tokens.borders.radius.lg, variantStyles.container, selected, tokens.colors.primary, style]);
|
|
192
192
|
|
|
193
193
|
const handlePress = (event: GestureResponderEvent) => {
|
|
194
194
|
if (!disabled && onPress) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
TextInput,
|
|
@@ -28,6 +28,9 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|
|
28
28
|
const searchIcon = useIconName('search');
|
|
29
29
|
const closeCircleIcon = useIconName('closeCircle');
|
|
30
30
|
|
|
31
|
+
// Memoize hitSlop to prevent object creation on every render
|
|
32
|
+
const hitSlop = useMemo(() => ({ top: 10, bottom: 10, left: 10, right: 10 }), []);
|
|
33
|
+
|
|
31
34
|
const handleClear = useCallback(() => {
|
|
32
35
|
onChangeText('');
|
|
33
36
|
onClear?.();
|
|
@@ -94,7 +97,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|
|
94
97
|
<TouchableOpacity
|
|
95
98
|
onPress={handleClear}
|
|
96
99
|
style={styles.clearButton}
|
|
97
|
-
hitSlop={
|
|
100
|
+
hitSlop={hitSlop}
|
|
98
101
|
accessibilityRole="button"
|
|
99
102
|
accessibilityLabel="Clear search"
|
|
100
103
|
>
|
|
@@ -21,6 +21,9 @@ import { DateUtilities } from '../utils/DateUtilities';
|
|
|
21
21
|
* Follows SOLID principles with composition over inheritance.
|
|
22
22
|
*/
|
|
23
23
|
export class CalendarService {
|
|
24
|
+
// Cache for weekday names to prevent recalculation
|
|
25
|
+
private static weekdayNamesCache = new Map<string, string[]>();
|
|
26
|
+
|
|
24
27
|
/**
|
|
25
28
|
* Generate calendar days for a specific month
|
|
26
29
|
*/
|
|
@@ -96,13 +99,23 @@ export class CalendarService {
|
|
|
96
99
|
/**
|
|
97
100
|
* Get weekday names
|
|
98
101
|
*/
|
|
99
|
-
static getWeekdayNames(locale
|
|
102
|
+
static getWeekdayNames(locale: string = 'en-US'): string[] {
|
|
103
|
+
const cacheKey = locale;
|
|
104
|
+
|
|
105
|
+
// Return cached result if available
|
|
106
|
+
if (this.weekdayNamesCache.has(cacheKey)) {
|
|
107
|
+
return this.weekdayNamesCache.get(cacheKey)!;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Calculate and cache
|
|
100
111
|
const weekdays: string[] = [];
|
|
101
112
|
for (let i = 0; i < 7; i++) {
|
|
102
113
|
const date = new Date();
|
|
103
114
|
date.setDate(date.getDate() - date.getDay() + i);
|
|
104
|
-
weekdays.push(date.toLocaleDateString(locale
|
|
115
|
+
weekdays.push(date.toLocaleDateString(locale, { weekday: 'short' }));
|
|
105
116
|
}
|
|
117
|
+
|
|
118
|
+
this.weekdayNamesCache.set(cacheKey, weekdays);
|
|
106
119
|
return weekdays;
|
|
107
120
|
}
|
|
108
121
|
|
|
@@ -18,6 +18,7 @@ class DevMonitorClass {
|
|
|
18
18
|
private statsInterval: ReturnType<typeof setInterval> | null = null;
|
|
19
19
|
private cacheSubscription: (() => void) | null = null;
|
|
20
20
|
private isEnabled: boolean;
|
|
21
|
+
private maxMetrics: number = 1000; // Prevent unbounded growth
|
|
21
22
|
|
|
22
23
|
constructor(options: DevMonitorOptions = {}) {
|
|
23
24
|
this.isEnabled = __DEV__ ?? false;
|
|
@@ -53,6 +54,14 @@ class DevMonitorClass {
|
|
|
53
54
|
|
|
54
55
|
this.metrics.set(queryKeyString, updatedMetrics);
|
|
55
56
|
|
|
57
|
+
// Prevent unbounded growth - remove oldest metric if limit exceeded
|
|
58
|
+
if (this.metrics.size > this.maxMetrics) {
|
|
59
|
+
const oldestKey = this.metrics.keys().next().value;
|
|
60
|
+
if (oldestKey) {
|
|
61
|
+
this.metrics.delete(oldestKey);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
if (this.options.enableLogging && updatedMetrics.slowFetchCount > 0) {
|
|
57
66
|
const fetchTime = MetricsCalculator.calculateFetchTime(query);
|
|
58
67
|
if (MetricsCalculator.isSlowQuery(fetchTime, this.options.slowQueryThreshold)) {
|
|
@@ -177,6 +186,7 @@ class DevMonitorClass {
|
|
|
177
186
|
clear(): void {
|
|
178
187
|
if (!this.isEnabled) return;
|
|
179
188
|
this.metrics.clear();
|
|
189
|
+
this.stopStatsLogging();
|
|
180
190
|
if (this.options.enableLogging) {
|
|
181
191
|
DevMonitorLogger.logMethodsCleared();
|
|
182
192
|
}
|
|
@@ -79,12 +79,14 @@ export function useAsyncOperation<T, E = Error>(
|
|
|
79
79
|
const operationRef = useRef(operation);
|
|
80
80
|
const errorHandlerRef = useRef(errorHandler);
|
|
81
81
|
|
|
82
|
-
// Keep all callback refs in sync with latest values
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
// Keep all callback refs in sync with latest values - use useEffect to prevent updates on every render
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
onSuccessRef.current = onSuccess;
|
|
85
|
+
onErrorRef.current = onError;
|
|
86
|
+
onFinallyRef.current = onFinally;
|
|
87
|
+
operationRef.current = operation;
|
|
88
|
+
errorHandlerRef.current = errorHandler;
|
|
89
|
+
}, [onSuccess, onError, onFinally, operation, errorHandler]);
|
|
88
90
|
|
|
89
91
|
// Cleanup on unmount
|
|
90
92
|
useEffect(() => {
|