@umituz/react-native-design-system 1.5.36 → 1.5.38

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 (62) hide show
  1. package/README.md +2 -2
  2. package/package.json +7 -5
  3. package/src/index.ts +29 -221
  4. package/src/presentation/organisms/AppHeader.tsx +3 -5
  5. package/src/presentation/tokens/commonStyles.ts +1 -1
  6. package/src/presentation/atoms/AtomicAvatar.tsx +0 -157
  7. package/src/presentation/atoms/AtomicAvatarGroup.tsx +0 -169
  8. package/src/presentation/atoms/AtomicBadge.tsx +0 -232
  9. package/src/presentation/atoms/AtomicButton.tsx +0 -236
  10. package/src/presentation/atoms/AtomicCard.tsx +0 -107
  11. package/src/presentation/atoms/AtomicChip.tsx +0 -223
  12. package/src/presentation/atoms/AtomicDatePicker.tsx +0 -347
  13. package/src/presentation/atoms/AtomicDivider.tsx +0 -114
  14. package/src/presentation/atoms/AtomicFab.tsx +0 -98
  15. package/src/presentation/atoms/AtomicFilter.tsx +0 -154
  16. package/src/presentation/atoms/AtomicFormError.tsx +0 -105
  17. package/src/presentation/atoms/AtomicIcon.tsx +0 -40
  18. package/src/presentation/atoms/AtomicImage.tsx +0 -149
  19. package/src/presentation/atoms/AtomicInput.tsx +0 -363
  20. package/src/presentation/atoms/AtomicNumberInput.tsx +0 -182
  21. package/src/presentation/atoms/AtomicPicker.tsx +0 -458
  22. package/src/presentation/atoms/AtomicProgress.tsx +0 -139
  23. package/src/presentation/atoms/AtomicSearchBar.tsx +0 -114
  24. package/src/presentation/atoms/AtomicSort.tsx +0 -145
  25. package/src/presentation/atoms/AtomicSwitch.tsx +0 -166
  26. package/src/presentation/atoms/AtomicText.tsx +0 -55
  27. package/src/presentation/atoms/AtomicTextArea.tsx +0 -313
  28. package/src/presentation/atoms/AtomicTouchable.tsx +0 -209
  29. package/src/presentation/atoms/fab/styles/fabStyles.ts +0 -69
  30. package/src/presentation/atoms/fab/types/index.ts +0 -82
  31. package/src/presentation/atoms/filter/styles/filterStyles.ts +0 -32
  32. package/src/presentation/atoms/filter/types/index.ts +0 -89
  33. package/src/presentation/atoms/index.ts +0 -366
  34. package/src/presentation/atoms/input/hooks/useInputState.ts +0 -15
  35. package/src/presentation/atoms/input/styles/inputStyles.ts +0 -66
  36. package/src/presentation/atoms/input/types/index.ts +0 -25
  37. package/src/presentation/atoms/picker/styles/pickerStyles.ts +0 -207
  38. package/src/presentation/atoms/picker/types/index.ts +0 -40
  39. package/src/presentation/atoms/touchable/styles/touchableStyles.ts +0 -62
  40. package/src/presentation/atoms/touchable/types/index.ts +0 -155
  41. package/src/presentation/hooks/useResponsive.ts +0 -180
  42. package/src/presentation/molecules/AtomicConfirmationModal.tsx +0 -243
  43. package/src/presentation/molecules/EmptyState.tsx +0 -130
  44. package/src/presentation/molecules/FormField.tsx +0 -128
  45. package/src/presentation/molecules/GridContainer.tsx +0 -124
  46. package/src/presentation/molecules/IconContainer.tsx +0 -94
  47. package/src/presentation/molecules/ListItem.tsx +0 -36
  48. package/src/presentation/molecules/ScreenHeader.tsx +0 -140
  49. package/src/presentation/molecules/SearchBar.tsx +0 -85
  50. package/src/presentation/molecules/SectionCard.tsx +0 -74
  51. package/src/presentation/molecules/SectionContainer.tsx +0 -106
  52. package/src/presentation/molecules/SectionHeader.tsx +0 -125
  53. package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +0 -133
  54. package/src/presentation/molecules/confirmation-modal/types/index.ts +0 -105
  55. package/src/presentation/molecules/index.ts +0 -41
  56. package/src/presentation/molecules/listitem/styles/listItemStyles.ts +0 -19
  57. package/src/presentation/molecules/listitem/types/index.ts +0 -17
  58. package/src/presentation/organisms/FormContainer.tsx +0 -180
  59. package/src/presentation/organisms/ScreenLayout.tsx +0 -171
  60. package/src/presentation/organisms/index.ts +0 -25
  61. package/src/presentation/utils/platformConstants.ts +0 -124
  62. package/src/presentation/utils/responsive.ts +0 -516
@@ -1,107 +0,0 @@
1
- import React from 'react';
2
- import { View, StyleProp, ViewStyle, Pressable } from 'react-native';
3
- import { useAppDesignTokens } from '@umituz/react-native-theme';
4
-
5
- export type AtomicCardVariant = 'flat' | 'elevated' | 'outlined';
6
- export type AtomicCardPadding = 'none' | 'sm' | 'md' | 'lg' | 'xl';
7
-
8
- export interface AtomicCardProps {
9
- variant?: AtomicCardVariant;
10
- padding?: AtomicCardPadding;
11
- onPress?: () => void;
12
- disabled?: boolean;
13
- style?: StyleProp<ViewStyle>;
14
- children?: React.ReactNode;
15
- testID?: string;
16
- }
17
-
18
-
19
- export const AtomicCard: React.FC<AtomicCardProps> = ({
20
- variant = 'elevated',
21
- padding = 'md',
22
- onPress,
23
- disabled = false,
24
- style,
25
- children,
26
- testID,
27
- }) => {
28
- const tokens = useAppDesignTokens();
29
-
30
- const handlePress = () => {
31
- if (onPress && !disabled) {
32
- onPress();
33
- }
34
- };
35
-
36
- // Map padding to token values
37
- const getPaddingValue = (): number => {
38
- const paddingMap = {
39
- none: 0,
40
- sm: tokens.spacing.sm,
41
- md: tokens.spacing.md,
42
- lg: tokens.spacing.lg,
43
- xl: tokens.spacing.xl,
44
- };
45
- return paddingMap[padding];
46
- };
47
-
48
- // Get variant styles
49
- const getVariantStyle = (): ViewStyle => {
50
- const baseStyle: ViewStyle = {
51
- backgroundColor: tokens.colors.surface,
52
- borderRadius: tokens.borders.radius.md,
53
- };
54
-
55
- switch (variant) {
56
- case 'elevated':
57
- return {
58
- ...baseStyle,
59
- borderWidth: 1,
60
- borderColor: tokens.colors.border,
61
- };
62
-
63
- case 'outlined':
64
- return {
65
- ...baseStyle,
66
- borderWidth: 1,
67
- borderColor: tokens.colors.border,
68
- };
69
-
70
- case 'flat':
71
- return {
72
- ...baseStyle,
73
- borderWidth: 0,
74
- };
75
-
76
- default:
77
- return baseStyle;
78
- }
79
- };
80
-
81
- const cardStyle: StyleProp<ViewStyle> = [
82
- getVariantStyle(),
83
- {
84
- padding: getPaddingValue(),
85
- opacity: disabled ? 0.5 : 1,
86
- },
87
- style,
88
- ];
89
-
90
- const cardContent = (
91
- <View style={cardStyle} testID={testID}>
92
- {children}
93
- </View>
94
- );
95
-
96
- // If onPress provided, wrap with pressable
97
- if (onPress && !disabled) {
98
- return (
99
- <Pressable onPress={handlePress}>
100
- {cardContent}
101
- </Pressable>
102
- );
103
- }
104
-
105
- // Otherwise just return static card
106
- return cardContent;
107
- };
@@ -1,223 +0,0 @@
1
- /**
2
- * AtomicChip - Universal Chip/Tag Component
3
- *
4
- * Displays small tags, labels, or status indicators
5
- * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
- *
7
- * Atomic Design Level: ATOM
8
- * Purpose: Tag and label display
9
- *
10
- * Usage:
11
- * - Category tags
12
- * - Status indicators
13
- * - Filter chips
14
- * - Skill labels
15
- * - Badge displays
16
- */
17
-
18
- import React from 'react';
19
- import { View, StyleSheet, ViewStyle, TouchableOpacity } from 'react-native';
20
- import { AtomicText } from './AtomicText';
21
- import { AtomicIcon } from './AtomicIcon';
22
- import { useAppDesignTokens } from '@umituz/react-native-theme';
23
-
24
- // =============================================================================
25
- // TYPE DEFINITIONS
26
- // =============================================================================
27
-
28
- export interface AtomicChipProps {
29
- /** Text content of the chip */
30
- children: React.ReactNode;
31
- /** Chip variant */
32
- variant?: 'filled' | 'outlined' | 'soft';
33
- /** Chip size */
34
- size?: 'sm' | 'md' | 'lg';
35
- /** Chip color theme */
36
- color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
37
- /** Custom background color */
38
- backgroundColor?: string;
39
- /** Custom text color */
40
- textColor?: string;
41
- /** Custom border color */
42
- borderColor?: string;
43
- /** Leading icon */
44
- leadingIcon?: string;
45
- /** Trailing icon */
46
- trailingIcon?: string;
47
- /** Whether the chip is clickable */
48
- clickable?: boolean;
49
- /** Click handler */
50
- onPress?: () => void;
51
- /** Whether the chip is selected */
52
- selected?: boolean;
53
- /** Whether the chip is disabled */
54
- disabled?: boolean;
55
- /** Style overrides */
56
- style?: ViewStyle;
57
- /** Test ID for testing */
58
- testID?: string;
59
- }
60
-
61
- // =============================================================================
62
- // COMPONENT IMPLEMENTATION
63
- // =============================================================================
64
-
65
- export const AtomicChip: React.FC<AtomicChipProps> = ({
66
- children,
67
- variant = 'filled',
68
- size = 'md',
69
- color = 'primary',
70
- backgroundColor,
71
- textColor,
72
- borderColor,
73
- leadingIcon,
74
- trailingIcon,
75
- clickable = false,
76
- onPress,
77
- selected = false,
78
- disabled = false,
79
- style,
80
- testID,
81
- }) => {
82
- const tokens = useAppDesignTokens();
83
-
84
- // Size mapping
85
- const sizeMap = {
86
- sm: {
87
- paddingHorizontal: tokens.spacing.sm,
88
- paddingVertical: tokens.spacing.xs,
89
- fontSize: tokens.typography.bodySmall.fontSize,
90
- iconSize: 'xs' as const
91
- },
92
- md: {
93
- paddingHorizontal: tokens.spacing.md,
94
- paddingVertical: tokens.spacing.sm,
95
- fontSize: tokens.typography.bodyMedium.fontSize,
96
- iconSize: 'sm' as const
97
- },
98
- lg: {
99
- paddingHorizontal: tokens.spacing.md,
100
- paddingVertical: tokens.spacing.sm,
101
- fontSize: tokens.typography.bodyLarge.fontSize,
102
- iconSize: 'sm' as const
103
- },
104
- };
105
-
106
- const sizeConfig = sizeMap[size];
107
-
108
- // Color mapping
109
- const colorMap = {
110
- primary: {
111
- filled: { bg: tokens.colors.primary, text: tokens.colors.onPrimary, border: tokens.colors.primary },
112
- outlined: { bg: 'transparent', text: tokens.colors.primary, border: tokens.colors.primary },
113
- soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border: 'transparent' },
114
- },
115
- secondary: {
116
- filled: { bg: tokens.colors.secondary, text: tokens.colors.onSecondary, border: tokens.colors.secondary },
117
- outlined: { bg: 'transparent', text: tokens.colors.secondary, border: tokens.colors.secondary },
118
- soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border: 'transparent' },
119
- },
120
- success: {
121
- filled: { bg: tokens.colors.success, text: tokens.colors.onSuccess, border: tokens.colors.success },
122
- outlined: { bg: 'transparent', text: tokens.colors.success, border: tokens.colors.success },
123
- soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border: 'transparent' },
124
- },
125
- warning: {
126
- filled: { bg: tokens.colors.warning, text: tokens.colors.onWarning, border: tokens.colors.warning },
127
- outlined: { bg: 'transparent', text: tokens.colors.warning, border: tokens.colors.warning },
128
- soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border: 'transparent' },
129
- },
130
- error: {
131
- filled: { bg: tokens.colors.error, text: tokens.colors.onError, border: tokens.colors.error },
132
- outlined: { bg: 'transparent', text: tokens.colors.error, border: tokens.colors.error },
133
- soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border: 'transparent' },
134
- },
135
- info: {
136
- filled: { bg: tokens.colors.info, text: tokens.colors.onInfo, border: tokens.colors.info },
137
- outlined: { bg: 'transparent', text: tokens.colors.info, border: tokens.colors.info },
138
- soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border: 'transparent' },
139
- },
140
- };
141
-
142
- const colorConfig = colorMap[color][variant];
143
-
144
- // Apply custom colors if provided
145
- const finalBackgroundColor = backgroundColor || colorConfig.bg;
146
- const finalTextColor = textColor || colorConfig.text;
147
- const finalBorderColor = borderColor || colorConfig.border;
148
-
149
- // Handle disabled state
150
- const isDisabled = disabled || (!clickable && !onPress);
151
- const opacity = isDisabled ? 0.5 : 1;
152
-
153
- // Handle selected state
154
- const selectedStyle = selected ? {
155
- borderWidth: tokens.borders.width.medium,
156
- borderColor: tokens.colors.primary,
157
- } : {};
158
-
159
- const chipStyle: ViewStyle = {
160
- flexDirection: 'row',
161
- alignItems: 'center',
162
- justifyContent: 'center',
163
- paddingHorizontal: sizeConfig.paddingHorizontal,
164
- paddingVertical: sizeConfig.paddingVertical,
165
- backgroundColor: finalBackgroundColor,
166
- borderRadius: tokens.borders.radius.xl,
167
- borderWidth: variant === 'outlined' ? 1 : 0,
168
- borderColor: finalBorderColor,
169
- opacity,
170
- ...selectedStyle,
171
- };
172
-
173
- const textStyle = {
174
- fontSize: sizeConfig.fontSize,
175
- fontWeight: tokens.typography.medium,
176
- };
177
-
178
- const iconColor = finalTextColor;
179
-
180
- const content = (
181
- <View style={[chipStyle, style]} testID={testID}>
182
- {leadingIcon && (
183
- <AtomicIcon
184
- name={leadingIcon}
185
- size={sizeConfig.iconSize}
186
- customColor={iconColor}
187
- style={{ marginRight: tokens.spacing.xs }}
188
- />
189
- )}
190
- <AtomicText
191
- type="labelMedium"
192
- color={finalTextColor}
193
- style={textStyle}
194
- >
195
- {children}
196
- </AtomicText>
197
- {trailingIcon && (
198
- <AtomicIcon
199
- name={trailingIcon}
200
- size={sizeConfig.iconSize}
201
- customColor={iconColor}
202
- style={{ marginLeft: tokens.spacing.xs }}
203
- />
204
- )}
205
- </View>
206
- );
207
-
208
- if (clickable && onPress && !disabled) {
209
- return (
210
- <TouchableOpacity onPress={onPress} activeOpacity={0.7}>
211
- {content}
212
- </TouchableOpacity>
213
- );
214
- }
215
-
216
- return content;
217
- };
218
-
219
- // =============================================================================
220
- // EXPORTS
221
- // =============================================================================
222
-
223
- export default AtomicChip;
@@ -1,347 +0,0 @@
1
- /**
2
- * AtomicDatePicker Component
3
- *
4
- * A reusable date picker component that wraps the native date picker
5
- * with consistent styling and behavior across platforms.
6
- *
7
- * Features:
8
- * - Platform-specific native pickers (iOS wheel, Android dialog)
9
- * - Consistent styling with design tokens
10
- * - Locale-aware date/time formatting (native Date methods)
11
- * - Timezone-aware (respects device timezone)
12
- * - Automatic language integration (native locale support)
13
- * - Optional label and error states
14
- * - Minimum and maximum date constraints
15
- * - Disabled state support
16
- * - Theme-aware styling
17
- * - Proper keyboard avoidance on iOS
18
- *
19
- * Usage:
20
- * ```tsx
21
- * const [selectedDate, setSelectedDate] = useState(new Date());
22
- *
23
- * <AtomicDatePicker
24
- * value={selectedDate}
25
- * onChange={setSelectedDate}
26
- * label="Birth Date"
27
- * minimumDate={new Date(1900, 0, 1)}
28
- * maximumDate={new Date()}
29
- * />
30
- * ```
31
- *
32
- * Platform Behavior:
33
- * - Opens bottom sheet from bottom with spinner wheel
34
- * - Requires "Done" button to confirm selection
35
- * - Can be dismissed by swiping down or tapping backdrop
36
- *
37
- * @module AtomicDatePicker
38
- */
39
-
40
- import React, { useState, useEffect } from 'react';
41
- import {
42
- View,
43
- Text,
44
- TouchableOpacity,
45
- StyleSheet,
46
- useWindowDimensions,
47
- type StyleProp,
48
- type ViewStyle,
49
- } from 'react-native';
50
- import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
51
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
52
- import { useAppDesignTokens } from '@umituz/react-native-theme';
53
- import { AtomicIcon, type AtomicIconColor } from './AtomicIcon';
54
- // @ts-ignore - Peer dependency, types may not be available during typecheck
55
- import { BottomSheetModal, useBottomSheetModal } from '@umituz/react-native-bottom-sheet';
56
- import { AtomicButton } from './AtomicButton';
57
-
58
- /**
59
- * Props for AtomicDatePicker component
60
- */
61
- export interface AtomicDatePickerProps {
62
- /** Selected date value */
63
- value: Date | null;
64
- /** Callback when date changes */
65
- onChange: (date: Date) => void;
66
- /** Optional label displayed above picker */
67
- label?: string;
68
- /** Optional error message displayed below picker */
69
- error?: string;
70
- /** Disable picker interaction */
71
- disabled?: boolean;
72
- /** Minimum selectable date */
73
- minimumDate?: Date;
74
- /** Maximum selectable date */
75
- maximumDate?: Date;
76
- /** Picker mode - date, time, or datetime (iOS only) */
77
- mode?: 'date' | 'time' | 'datetime';
78
- /** Placeholder text when no value selected */
79
- placeholder?: string;
80
- /** Optional test ID for E2E testing */
81
- testID?: string;
82
- /** Optional container style */
83
- style?: StyleProp<ViewStyle>;
84
- }
85
-
86
- /**
87
- * AtomicDatePicker - Universal date/time picker component
88
- *
89
- * Wraps @react-native-community/datetimepicker with:
90
- * - Theme integration
91
- * - Platform-specific modal handling
92
- * - Error states
93
- * - Disabled states
94
- * - Responsive sizing
95
- */
96
- export const AtomicDatePicker: React.FC<AtomicDatePickerProps> = ({
97
- value,
98
- onChange,
99
- label,
100
- error,
101
- disabled = false,
102
- minimumDate,
103
- maximumDate,
104
- mode = 'date',
105
- placeholder = 'Select date',
106
- testID,
107
- style,
108
- }) => {
109
- const tokens = useAppDesignTokens();
110
- const { height } = useWindowDimensions();
111
- const insets = useSafeAreaInsets();
112
- const { modalRef, present, dismiss } = useBottomSheetModal();
113
- const [tempDate, setTempDate] = useState<Date>(value ?? new Date());
114
-
115
- // Update tempDate when value prop changes
116
- useEffect(() => {
117
- if (value) {
118
- setTempDate(value);
119
- }
120
- }, [value]);
121
-
122
- /**
123
- * Handle date/time change in picker
124
- * Updates temporary date state (not final until Done is pressed)
125
- */
126
- const handleChange = (event: DateTimePickerEvent, selectedDate?: Date) => {
127
- if (event.type === 'set' && selectedDate) {
128
- setTempDate(selectedDate);
129
- }
130
- };
131
-
132
- /**
133
- * Handle Done button - apply selected date and close sheet
134
- */
135
- const handleDone = () => {
136
- onChange(tempDate);
137
- dismiss();
138
- };
139
-
140
- /**
141
- * Handle open - reset temp date to current value
142
- */
143
- const handleOpen = () => {
144
- setTempDate(value ?? new Date());
145
- present();
146
- };
147
-
148
- /**
149
- * Format date based on mode
150
- * Uses native Date formatting (locale-aware)
151
- */
152
- const formatDate = (date: Date): string => {
153
- if (mode === 'time') {
154
- // Format time only
155
- return date.toLocaleTimeString([], {
156
- hour: '2-digit',
157
- minute: '2-digit'
158
- });
159
- }
160
- if (mode === 'datetime') {
161
- // Format date + time
162
- const dateStr = date.toLocaleDateString([], {
163
- year: 'numeric',
164
- month: 'short',
165
- day: 'numeric',
166
- });
167
- const timeStr = date.toLocaleTimeString([], {
168
- hour: '2-digit',
169
- minute: '2-digit'
170
- });
171
- return `${dateStr} ${timeStr}`;
172
- }
173
- // Format date only
174
- return date.toLocaleDateString([], {
175
- year: 'numeric',
176
- month: 'long',
177
- day: 'numeric',
178
- });
179
- };
180
-
181
- /**
182
- * Determine icon color based on state
183
- */
184
- const getIconColor = (): AtomicIconColor => {
185
- if (disabled) return 'secondary';
186
- if (error) return 'error';
187
- return 'primary';
188
- };
189
-
190
- const styles = getStyles(tokens, height, insets);
191
-
192
- return (
193
- <View style={[styles.container, style]} testID={testID}>
194
- {label && (
195
- <Text style={styles.label} testID={testID ? `${testID}-label` : undefined}>
196
- {label}
197
- </Text>
198
- )}
199
-
200
- <TouchableOpacity
201
- style={[
202
- styles.button,
203
- error ? styles.buttonError : undefined,
204
- disabled ? styles.buttonDisabled : undefined,
205
- ]}
206
- onPress={handleOpen}
207
- disabled={disabled}
208
- testID={testID ? `${testID}-button` : undefined}
209
- accessibilityLabel={label || placeholder}
210
- accessibilityRole="button"
211
- accessibilityState={{ disabled }}
212
- >
213
- <AtomicIcon
214
- name="Calendar"
215
- color={getIconColor()}
216
- size="md"
217
- />
218
- <Text
219
- style={[
220
- styles.text,
221
- disabled ? styles.textDisabled : undefined,
222
- error ? styles.textError : undefined,
223
- ]}
224
- >
225
- {value ? formatDate(value) : placeholder}
226
- </Text>
227
- </TouchableOpacity>
228
-
229
- {error && (
230
- <Text style={styles.errorText} testID={testID ? `${testID}-error` : undefined}>
231
- {error}
232
- </Text>
233
- )}
234
-
235
- {/* Bottom Sheet DatePicker */}
236
- <BottomSheetModal
237
- ref={modalRef}
238
- preset="medium"
239
- enableBackdrop
240
- enablePanDownToClose
241
- enableHandleIndicator
242
- onDismiss={() => {
243
- // Reset temp date when closed without saving
244
- setTempDate(value ?? new Date());
245
- }}
246
- >
247
- <View style={styles.bottomSheetContent}>
248
- <DateTimePicker
249
- value={tempDate}
250
- mode={mode}
251
- display="spinner"
252
- onChange={handleChange}
253
- minimumDate={minimumDate}
254
- maximumDate={maximumDate}
255
- testID={testID ? `${testID}-picker` : undefined}
256
- />
257
- <View style={styles.buttonContainer}>
258
- <AtomicButton
259
- title="Done"
260
- onPress={handleDone}
261
- variant="primary"
262
- style={styles.doneButton}
263
- testID={testID ? `${testID}-done` : undefined}
264
- />
265
- </View>
266
- </View>
267
- </BottomSheetModal>
268
- </View>
269
- );
270
- };
271
-
272
- /**
273
- * Get component styles based on design tokens
274
- */
275
- const getStyles = (
276
- tokens: ReturnType<typeof useAppDesignTokens>,
277
- height: number,
278
- insets: { top: number; bottom: number; left: number; right: number },
279
- ) => {
280
- // Responsive button sizing based on device height
281
- const buttonMinWidth = height <= 667 ? Math.min(height * 0.25, 150) : 200;
282
-
283
- return StyleSheet.create({
284
- container: {
285
- marginBottom: tokens.spacing.md,
286
- },
287
- label: {
288
- fontSize: tokens.typography.bodyMedium.fontSize,
289
- fontWeight: tokens.typography.semibold,
290
- color: tokens.colors.textPrimary,
291
- marginBottom: tokens.spacing.sm,
292
- },
293
- button: {
294
- flexDirection: 'row',
295
- alignItems: 'center',
296
- backgroundColor: tokens.colors.surface,
297
- borderWidth: 1,
298
- borderColor: tokens.colors.border,
299
- borderRadius: tokens.borders.radius.lg,
300
- paddingHorizontal: tokens.spacing.md,
301
- paddingVertical: tokens.spacing.md,
302
- gap: tokens.spacing.sm,
303
- minHeight: 48, // Apple HIG minimum touch target
304
- },
305
- buttonError: {
306
- borderColor: tokens.colors.error,
307
- borderWidth: tokens.borders.width.medium,
308
- },
309
- buttonDisabled: {
310
- backgroundColor: tokens.colors.surfaceDisabled,
311
- opacity: tokens.opacity.disabled,
312
- },
313
- text: {
314
- flex: 1,
315
- fontSize: tokens.typography.bodyLarge.fontSize,
316
- color: tokens.colors.textPrimary,
317
- },
318
- textDisabled: {
319
- color: tokens.colors.textDisabled,
320
- },
321
- textError: {
322
- color: tokens.colors.error,
323
- },
324
- errorText: {
325
- fontSize: tokens.typography.bodySmall.fontSize,
326
- color: tokens.colors.error,
327
- marginTop: tokens.spacing.xs,
328
- marginLeft: tokens.spacing.xs,
329
- },
330
- bottomSheetContent: {
331
- paddingHorizontal: tokens.spacing.lg,
332
- paddingTop: tokens.spacing.md,
333
- paddingBottom: Math.max(
334
- insets.bottom + tokens.spacing.md,
335
- tokens.spacing.xl,
336
- ),
337
- },
338
- buttonContainer: {
339
- alignItems: 'center',
340
- marginTop: tokens.spacing.lg,
341
- paddingHorizontal: tokens.spacing.md,
342
- },
343
- doneButton: {
344
- minWidth: buttonMinWidth,
345
- },
346
- });
347
- };