@umituz/react-native-design-system 4.28.16 → 4.28.18

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "4.28.16",
3
+ "version": "4.28.18",
4
4
  "description": "Universal design system for React Native apps with safe navigation hooks - updated SKILL.md with navigation documentation",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -10,7 +10,6 @@ import {
10
10
  View,
11
11
  Modal,
12
12
  TouchableOpacity,
13
- StyleSheet,
14
13
  Platform,
15
14
  } from 'react-native';
16
15
  import { useSafeAreaInsets } from '../../../safe-area';
@@ -103,6 +102,10 @@ export const DatePickerModal: React.FC<DatePickerModalProps> = ({
103
102
  },
104
103
  }), [overlayOpacity, tokens, insets.bottom]);
105
104
 
105
+ const pickerStyle = useMemo(() => ({
106
+ alignSelf: 'center' as const,
107
+ }), []);
108
+
106
109
  if (Platform.OS !== 'ios' || !DateTimePicker) {
107
110
  return null;
108
111
  }
@@ -145,7 +148,7 @@ export const DatePickerModal: React.FC<DatePickerModalProps> = ({
145
148
  minimumDate={minimumDate}
146
149
  maximumDate={maximumDate}
147
150
  display="spinner"
148
- style={{ alignSelf: 'center' }}
151
+ style={pickerStyle}
149
152
  testID={testID ? `${testID}-picker` : undefined}
150
153
  />
151
154
  </View>
@@ -12,6 +12,11 @@ interface FileWithType {
12
12
 
13
13
  export async function getMediaDuration(file: FileWithType): Promise<number | undefined> {
14
14
  if (file.type.startsWith("audio/") || file.type.startsWith("video/")) {
15
+ // Stub implementation - returns a random duration for demo purposes
16
+ // In production, this should extract actual media duration using AVFoundation or MediaMetadataRetriever
17
+ if (__DEV__) {
18
+ console.warn('[getMediaDuration] Using stub implementation with random duration');
19
+ }
15
20
  return Math.floor(Math.random() * 60) + 10;
16
21
  }
17
22
  return undefined;
@@ -5,7 +5,7 @@
5
5
  * Uses design system responsive utilities
6
6
  */
7
7
 
8
- import React from 'react';
8
+ import React, { useMemo } from 'react';
9
9
  import { FlatList, RefreshControl, type FlatListProps, type ListRenderItem } from 'react-native';
10
10
  import { useAppDesignTokens } from '../../theme';
11
11
 
@@ -55,29 +55,33 @@ export const List = <T,>({
55
55
  }: ListProps<T>) => {
56
56
  const tokens = useAppDesignTokens();
57
57
 
58
+ const contentContainerStyle = useMemo(() => (
59
+ contentPadding
60
+ ? {
61
+ paddingHorizontal: tokens.spacing.screenPadding,
62
+ paddingBottom: tokens.spacing.lg,
63
+ }
64
+ : undefined
65
+ ), [contentPadding, tokens.spacing.screenPadding, tokens.spacing.lg]);
66
+
67
+ const refreshControlElement = useMemo(() => (
68
+ onRefresh ? (
69
+ <RefreshControl
70
+ refreshing={refreshing}
71
+ onRefresh={onRefresh}
72
+ tintColor={tokens.colors.primary}
73
+ colors={[tokens.colors.primary]}
74
+ />
75
+ ) : undefined
76
+ ), [onRefresh, refreshing, tokens.colors.primary]);
77
+
58
78
  return (
59
79
  <FlatList
60
80
  data={data}
61
81
  renderItem={renderItem}
62
82
  keyExtractor={keyExtractor}
63
- refreshControl={
64
- onRefresh ? (
65
- <RefreshControl
66
- refreshing={refreshing}
67
- onRefresh={onRefresh}
68
- tintColor={tokens.colors.primary}
69
- colors={[tokens.colors.primary]}
70
- />
71
- ) : undefined
72
- }
73
- contentContainerStyle={
74
- contentPadding
75
- ? {
76
- paddingHorizontal: tokens.spacing.screenPadding,
77
- paddingBottom: tokens.spacing.lg,
78
- }
79
- : undefined
80
- }
83
+ refreshControl={refreshControlElement}
84
+ contentContainerStyle={contentContainerStyle}
81
85
  showsVerticalScrollIndicator={false}
82
86
  keyboardShouldPersistTaps="handled"
83
87
  {...rest}
@@ -6,7 +6,7 @@
6
6
  * Auto-dismisses after duration (default 3 seconds).
7
7
  */
8
8
 
9
- import React from 'react';
9
+ import React, { useMemo, useCallback } from 'react';
10
10
  import { StyleSheet, View, Pressable } from 'react-native';
11
11
  import { useSafeAreaInsets } from '../../safe-area';
12
12
  import { AtomicText, useIconName } from '../../atoms';
@@ -33,17 +33,36 @@ export function AlertBanner({ alert }: AlertBannerProps) {
33
33
  const textColor = getAlertTextColor(tokens);
34
34
  const isTop = alert.position === AlertPosition.TOP;
35
35
 
36
+ const containerStyle = useMemo(() => [
37
+ styles.container,
38
+ {
39
+ backgroundColor,
40
+ paddingTop: isTop ? insets.top + tokens.spacing.sm : tokens.spacing.sm,
41
+ paddingBottom: isTop ? tokens.spacing.sm : insets.bottom + tokens.spacing.sm,
42
+ paddingHorizontal: tokens.spacing.md,
43
+ },
44
+ ], [backgroundColor, insets.bottom, insets.top, isTop, tokens.spacing.md, tokens.spacing.sm]);
45
+
46
+ const closeButtonStyle = useMemo(() => [
47
+ styles.closeButton,
48
+ { marginLeft: tokens.spacing.sm }
49
+ ], [tokens.spacing.sm]);
50
+
51
+ const actionsContainerStyle = useMemo(() => [
52
+ styles.actionsContainer,
53
+ { marginTop: tokens.spacing.sm }
54
+ ], [tokens.spacing.sm]);
55
+
56
+ const handleActionPress = useCallback(async (action: typeof alert.actions[0]) => {
57
+ await action.onPress();
58
+ if (action.closeOnPress ?? true) {
59
+ handleDismiss();
60
+ }
61
+ }, [handleDismiss]);
62
+
36
63
  return (
37
64
  <View
38
- style={[
39
- styles.container,
40
- {
41
- backgroundColor,
42
- paddingTop: isTop ? insets.top + tokens.spacing.sm : tokens.spacing.sm,
43
- paddingBottom: isTop ? tokens.spacing.sm : insets.bottom + tokens.spacing.sm,
44
- paddingHorizontal: tokens.spacing.md,
45
- },
46
- ]}
65
+ style={containerStyle}
47
66
  testID={alert.testID}
48
67
  >
49
68
  <View style={styles.content}>
@@ -69,7 +88,7 @@ export function AlertBanner({ alert }: AlertBannerProps) {
69
88
  {alert.dismissible && (
70
89
  <Pressable
71
90
  onPress={handleDismiss}
72
- style={[styles.closeButton, { marginLeft: tokens.spacing.sm }]}
91
+ style={closeButtonStyle}
73
92
  hitSlop={8}
74
93
  >
75
94
  <AlertIcon name={closeIcon} color={textColor} />
@@ -78,38 +97,39 @@ export function AlertBanner({ alert }: AlertBannerProps) {
78
97
  </View>
79
98
 
80
99
  {alert.actions && alert.actions.length > 0 && (
81
- <View style={[styles.actionsContainer, { marginTop: tokens.spacing.sm }]}>
82
- {alert.actions.map((action) => (
83
- <Pressable
84
- key={action.id}
85
- onPress={async () => {
86
- await action.onPress();
87
- if (action.closeOnPress ?? true) {
88
- handleDismiss();
89
- }
90
- }}
91
- style={[
92
- styles.actionButton,
93
- {
94
- paddingVertical: tokens.spacing.xs,
95
- paddingHorizontal: tokens.spacing.sm,
96
- marginRight: tokens.spacing.xs,
97
- borderRadius: tokens.borders.radius.sm,
98
- },
99
- getActionButtonStyle(action.style, tokens),
100
- ]}
101
- >
102
- <AtomicText
103
- type="bodySmall"
104
- style={[
105
- styles.actionText,
106
- { color: getActionTextColor(action.style, tokens) },
107
- ]}
100
+ <View style={actionsContainerStyle}>
101
+ {alert.actions.map((action) => {
102
+ const actionButtonStyle = useMemo(() => [
103
+ styles.actionButton,
104
+ {
105
+ paddingVertical: tokens.spacing.xs,
106
+ paddingHorizontal: tokens.spacing.sm,
107
+ marginRight: tokens.spacing.xs,
108
+ borderRadius: tokens.borders.radius.sm,
109
+ },
110
+ getActionButtonStyle(action.style, tokens),
111
+ ], [action.style, tokens]);
112
+
113
+ const actionTextStyle = useMemo(() => [
114
+ styles.actionText,
115
+ { color: getActionTextColor(action.style, tokens) }
116
+ ], [action.style, tokens]);
117
+
118
+ return (
119
+ <Pressable
120
+ key={action.id}
121
+ onPress={() => handleActionPress(action)}
122
+ style={actionButtonStyle}
108
123
  >
109
- {action.label}
110
- </AtomicText>
111
- </Pressable>
112
- ))}
124
+ <AtomicText
125
+ type="bodySmall"
126
+ style={actionTextStyle}
127
+ >
128
+ {action.label}
129
+ </AtomicText>
130
+ </Pressable>
131
+ );
132
+ })}
113
133
  </View>
114
134
  )}
115
135
  </View>
@@ -2,7 +2,7 @@
2
2
  * AlertInline Component
3
3
  */
4
4
 
5
- import React from 'react';
5
+ import React, { useMemo } from 'react';
6
6
  import { StyleSheet, View } from 'react-native';
7
7
  import { useAppDesignTokens } from '../../theme';
8
8
  import { Alert } from './AlertTypes';
@@ -16,17 +16,19 @@ interface AlertInlineProps {
16
16
  export const AlertInline: React.FC<AlertInlineProps> = ({ alert }) => {
17
17
  const tokens = useAppDesignTokens();
18
18
 
19
+ const containerStyle = useMemo(() => [
20
+ styles.container,
21
+ {
22
+ borderColor: getAlertBorderColor(alert.type, tokens),
23
+ backgroundColor: getAlertBackgroundColorInline(alert.type, tokens),
24
+ borderRadius: tokens.borders.radius.sm,
25
+ padding: tokens.spacing.md,
26
+ marginVertical: tokens.spacing.sm,
27
+ }
28
+ ], [alert.type, tokens]);
29
+
19
30
  return (
20
- <View style={[
21
- styles.container,
22
- {
23
- borderColor: getAlertBorderColor(alert.type, tokens),
24
- backgroundColor: getAlertBackgroundColorInline(alert.type, tokens),
25
- borderRadius: tokens.borders.radius.sm,
26
- padding: tokens.spacing.md,
27
- marginVertical: tokens.spacing.sm,
28
- }
29
- ]}>
31
+ <View style={containerStyle}>
30
32
  <AlertContent
31
33
  title={alert.title}
32
34
  message={alert.message}
@@ -5,7 +5,7 @@
5
5
  * Floats on top of content.
6
6
  */
7
7
 
8
- import React from 'react';
8
+ import React, { useMemo, useCallback } from 'react';
9
9
  import { StyleSheet, View, Pressable } from 'react-native';
10
10
  import { AtomicText, useIconName } from '../../atoms';
11
11
  import { useAppDesignTokens } from '../../theme';
@@ -34,16 +34,39 @@ export function AlertToast({ alert }: AlertToastProps) {
34
34
  const backgroundColor = getAlertBackgroundColor(alert.type, tokens);
35
35
  const textColor = getAlertTextColor(tokens);
36
36
 
37
+ const containerStyle = useMemo(() => [
38
+ styles.container,
39
+ {
40
+ backgroundColor,
41
+ padding: tokens.spacing.md,
42
+ borderRadius: tokens.borders.radius.md,
43
+ },
44
+ ], [backgroundColor, tokens.borders.radius.md, tokens.spacing.md]);
45
+
46
+ const closeButtonStyle = useMemo(() => [
47
+ styles.closeButton,
48
+ { marginLeft: tokens.spacing.sm }
49
+ ], [tokens.spacing.sm]);
50
+
51
+ const actionsContainerStyle = useMemo(() => [
52
+ styles.actionsContainer,
53
+ { marginTop: tokens.spacing.sm }
54
+ ], [tokens.spacing.sm]);
55
+
56
+ const handleActionPress = useCallback(async (action: typeof alert.actions[0]) => {
57
+ try {
58
+ await action.onPress();
59
+ } catch (e) {
60
+ if (__DEV__) console.error('[AlertToast] action.onPress failed', e);
61
+ }
62
+ if (action.closeOnPress ?? true) {
63
+ handleDismiss();
64
+ }
65
+ }, [handleDismiss]);
66
+
37
67
  return (
38
68
  <View
39
- style={[
40
- styles.container,
41
- {
42
- backgroundColor,
43
- padding: tokens.spacing.md,
44
- borderRadius: tokens.borders.radius.md,
45
- },
46
- ]}
69
+ style={containerStyle}
47
70
  testID={alert.testID}
48
71
  >
49
72
  <Pressable
@@ -72,7 +95,7 @@ export function AlertToast({ alert }: AlertToastProps) {
72
95
  {alert.dismissible && (
73
96
  <Pressable
74
97
  onPress={handleDismiss}
75
- style={[styles.closeButton, { marginLeft: tokens.spacing.sm }]}
98
+ style={closeButtonStyle}
76
99
  hitSlop={8}
77
100
  >
78
101
  <AlertIcon name={closeIcon} color={textColor} />
@@ -81,44 +104,41 @@ export function AlertToast({ alert }: AlertToastProps) {
81
104
  </View>
82
105
 
83
106
  {alert.actions && alert.actions.length > 0 && (
84
- <View style={[styles.actionsContainer, { marginTop: tokens.spacing.sm }]}>
85
- {alert.actions.map((action) => (
86
- <Pressable
87
- key={action.id}
88
- onPress={async () => {
89
- try {
90
- await action.onPress();
91
- } catch (e) {
92
- if (__DEV__) console.error('[AlertToast] action.onPress failed', e);
93
- }
94
- if (action.closeOnPress ?? true) {
95
- handleDismiss();
96
- }
97
- }}
98
- accessibilityRole="button"
99
- accessibilityLabel={action.label}
100
- style={[
101
- styles.actionButton,
102
- {
103
- paddingVertical: tokens.spacing.xs,
104
- paddingHorizontal: tokens.spacing.sm,
105
- marginRight: tokens.spacing.xs,
106
- borderRadius: tokens.borders.radius.sm,
107
- },
108
- getActionButtonStyle(action.style, tokens),
109
- ]}
110
- >
111
- <AtomicText
112
- type="bodySmall"
113
- style={[
114
- styles.actionText,
115
- { color: getActionTextColor(action.style, tokens) },
116
- ]}
107
+ <View style={actionsContainerStyle}>
108
+ {alert.actions.map((action) => {
109
+ const actionButtonStyle = useMemo(() => [
110
+ styles.actionButton,
111
+ {
112
+ paddingVertical: tokens.spacing.xs,
113
+ paddingHorizontal: tokens.spacing.sm,
114
+ marginRight: tokens.spacing.xs,
115
+ borderRadius: tokens.borders.radius.sm,
116
+ },
117
+ getActionButtonStyle(action.style, tokens),
118
+ ], [action.style, tokens]);
119
+
120
+ const actionTextStyle = useMemo(() => [
121
+ styles.actionText,
122
+ { color: getActionTextColor(action.style, tokens) }
123
+ ], [action.style, tokens]);
124
+
125
+ return (
126
+ <Pressable
127
+ key={action.id}
128
+ onPress={() => handleActionPress(action)}
129
+ accessibilityRole="button"
130
+ accessibilityLabel={action.label}
131
+ style={actionButtonStyle}
117
132
  >
118
- {action.label}
119
- </AtomicText>
120
- </Pressable>
121
- ))}
133
+ <AtomicText
134
+ type="bodySmall"
135
+ style={actionTextStyle}
136
+ >
137
+ {action.label}
138
+ </AtomicText>
139
+ </Pressable>
140
+ );
141
+ })}
122
142
  </View>
123
143
  )}
124
144
  </Pressable>
@@ -1,6 +1,6 @@
1
1
 
2
2
  import React, { useMemo, useCallback } from 'react';
3
- import { ScrollView, StyleSheet } from 'react-native';
3
+ import { ScrollView } from 'react-native';
4
4
  import { AtomicChip } from '../../atoms/chip/AtomicChip';
5
5
  import { useAppDesignTokens } from '../../theme';
6
6
  import type { FilterGroupProps } from './types';
@@ -16,14 +16,14 @@ export const FilterGroup = React.memo(function FilterGroup<T = string>({
16
16
  }: FilterGroupProps<T>) {
17
17
  const tokens = useAppDesignTokens();
18
18
 
19
- const styles = useMemo(() => StyleSheet.create({
19
+ const styles = useMemo(() => ({
20
20
  container: {
21
21
  flexGrow: 0,
22
22
  },
23
23
  content: {
24
24
  paddingHorizontal: tokens.spacing.md,
25
25
  gap: tokens.spacing.md,
26
- alignItems: 'center',
26
+ alignItems: 'center' as const,
27
27
  },
28
28
  item: {
29
29
  },
@@ -47,7 +47,6 @@ import React, { useMemo } from 'react';
47
47
  import {
48
48
  ScrollView,
49
49
  View,
50
- StyleSheet,
51
50
  StyleProp,
52
51
  ViewStyle,
53
52
  } from 'react-native';
@@ -110,33 +109,32 @@ export const FormContainer: React.FC<FormContainerProps> = ({
110
109
 
111
110
  // Create styles for form container (memoized to avoid re-creation on every render)
112
111
  const styles = useMemo(
113
- () =>
114
- StyleSheet.create({
115
- container: {
116
- flex: 1,
117
- backgroundColor: tokens.colors.backgroundPrimary,
118
- },
119
- surface: {
120
- flex: 1,
121
- backgroundColor: tokens.colors.surface,
122
- borderWidth: showBorder ? 1 : 0,
123
- borderColor: tokens.colors.border,
124
- borderRadius: tokens.borders.radius.md,
125
- },
126
- scrollView: {
127
- flex: 1,
128
- },
129
- contentContainer: {
130
- flexGrow: 1,
131
- padding: tokens.spacing.lg,
132
- paddingTop: tokens.spacing.xl,
133
- paddingBottom: formBottomPadding + insets.bottom,
134
- maxWidth: formContentWidth,
135
- alignSelf: 'center',
136
- width: '100%',
137
- gap: formElementSpacing,
138
- },
139
- }),
112
+ () => ({
113
+ container: {
114
+ flex: 1,
115
+ backgroundColor: tokens.colors.backgroundPrimary,
116
+ },
117
+ surface: {
118
+ flex: 1,
119
+ backgroundColor: tokens.colors.surface,
120
+ borderWidth: showBorder ? 1 : 0,
121
+ borderColor: tokens.colors.border,
122
+ borderRadius: tokens.borders.radius.md,
123
+ },
124
+ scrollView: {
125
+ flex: 1,
126
+ },
127
+ contentContainer: {
128
+ flexGrow: 1,
129
+ padding: tokens.spacing.lg,
130
+ paddingTop: tokens.spacing.xl,
131
+ paddingBottom: formBottomPadding + insets.bottom,
132
+ maxWidth: formContentWidth,
133
+ alignSelf: 'center' as const,
134
+ width: '100%',
135
+ gap: formElementSpacing,
136
+ },
137
+ }),
140
138
  [tokens, showBorder, formBottomPadding, insets.bottom, formContentWidth, formElementSpacing],
141
139
  );
142
140