@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.
- package/README.md +2 -2
- package/package.json +7 -5
- package/src/index.ts +29 -221
- package/src/presentation/organisms/AppHeader.tsx +3 -5
- package/src/presentation/tokens/commonStyles.ts +1 -1
- package/src/presentation/atoms/AtomicAvatar.tsx +0 -157
- package/src/presentation/atoms/AtomicAvatarGroup.tsx +0 -169
- package/src/presentation/atoms/AtomicBadge.tsx +0 -232
- package/src/presentation/atoms/AtomicButton.tsx +0 -236
- package/src/presentation/atoms/AtomicCard.tsx +0 -107
- package/src/presentation/atoms/AtomicChip.tsx +0 -223
- package/src/presentation/atoms/AtomicDatePicker.tsx +0 -347
- package/src/presentation/atoms/AtomicDivider.tsx +0 -114
- package/src/presentation/atoms/AtomicFab.tsx +0 -98
- package/src/presentation/atoms/AtomicFilter.tsx +0 -154
- package/src/presentation/atoms/AtomicFormError.tsx +0 -105
- package/src/presentation/atoms/AtomicIcon.tsx +0 -40
- package/src/presentation/atoms/AtomicImage.tsx +0 -149
- package/src/presentation/atoms/AtomicInput.tsx +0 -363
- package/src/presentation/atoms/AtomicNumberInput.tsx +0 -182
- package/src/presentation/atoms/AtomicPicker.tsx +0 -458
- package/src/presentation/atoms/AtomicProgress.tsx +0 -139
- package/src/presentation/atoms/AtomicSearchBar.tsx +0 -114
- package/src/presentation/atoms/AtomicSort.tsx +0 -145
- package/src/presentation/atoms/AtomicSwitch.tsx +0 -166
- package/src/presentation/atoms/AtomicText.tsx +0 -55
- package/src/presentation/atoms/AtomicTextArea.tsx +0 -313
- package/src/presentation/atoms/AtomicTouchable.tsx +0 -209
- package/src/presentation/atoms/fab/styles/fabStyles.ts +0 -69
- package/src/presentation/atoms/fab/types/index.ts +0 -82
- package/src/presentation/atoms/filter/styles/filterStyles.ts +0 -32
- package/src/presentation/atoms/filter/types/index.ts +0 -89
- package/src/presentation/atoms/index.ts +0 -366
- package/src/presentation/atoms/input/hooks/useInputState.ts +0 -15
- package/src/presentation/atoms/input/styles/inputStyles.ts +0 -66
- package/src/presentation/atoms/input/types/index.ts +0 -25
- package/src/presentation/atoms/picker/styles/pickerStyles.ts +0 -207
- package/src/presentation/atoms/picker/types/index.ts +0 -40
- package/src/presentation/atoms/touchable/styles/touchableStyles.ts +0 -62
- package/src/presentation/atoms/touchable/types/index.ts +0 -155
- package/src/presentation/hooks/useResponsive.ts +0 -180
- package/src/presentation/molecules/AtomicConfirmationModal.tsx +0 -243
- package/src/presentation/molecules/EmptyState.tsx +0 -130
- package/src/presentation/molecules/FormField.tsx +0 -128
- package/src/presentation/molecules/GridContainer.tsx +0 -124
- package/src/presentation/molecules/IconContainer.tsx +0 -94
- package/src/presentation/molecules/ListItem.tsx +0 -36
- package/src/presentation/molecules/ScreenHeader.tsx +0 -140
- package/src/presentation/molecules/SearchBar.tsx +0 -85
- package/src/presentation/molecules/SectionCard.tsx +0 -74
- package/src/presentation/molecules/SectionContainer.tsx +0 -106
- package/src/presentation/molecules/SectionHeader.tsx +0 -125
- package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +0 -133
- package/src/presentation/molecules/confirmation-modal/types/index.ts +0 -105
- package/src/presentation/molecules/index.ts +0 -41
- package/src/presentation/molecules/listitem/styles/listItemStyles.ts +0 -19
- package/src/presentation/molecules/listitem/types/index.ts +0 -17
- package/src/presentation/organisms/FormContainer.tsx +0 -180
- package/src/presentation/organisms/ScreenLayout.tsx +0 -171
- package/src/presentation/organisms/index.ts +0 -25
- package/src/presentation/utils/platformConstants.ts +0 -124
- package/src/presentation/utils/responsive.ts +0 -516
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { ScrollView, View } from 'react-native';
|
|
3
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
4
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
5
|
-
import { AtomicChip } from './AtomicChip';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Sort option interface
|
|
9
|
-
*/
|
|
10
|
-
export interface SortOption {
|
|
11
|
-
id: string;
|
|
12
|
-
label: string;
|
|
13
|
-
icon?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Sort direction type
|
|
18
|
-
*/
|
|
19
|
-
export type SortDirection = 'asc' | 'desc';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* AtomicSort component props
|
|
23
|
-
*/
|
|
24
|
-
export interface AtomicSortProps {
|
|
25
|
-
options: SortOption[];
|
|
26
|
-
selectedId: string | null;
|
|
27
|
-
sortDirection: SortDirection;
|
|
28
|
-
onSortChange: (optionId: string, direction: SortDirection) => void;
|
|
29
|
-
showDirectionToggle?: boolean;
|
|
30
|
-
variant?: 'outlined' | 'filled' | 'soft';
|
|
31
|
-
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
|
|
32
|
-
size?: 'sm' | 'md' | 'lg';
|
|
33
|
-
style?: StyleProp<ViewStyle>;
|
|
34
|
-
testID?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* AtomicSort - Horizontal Sort Chip Component
|
|
39
|
-
*
|
|
40
|
-
* A Material Design 3 compliant sort component using chip selection.
|
|
41
|
-
* Supports single selection with ascending/descending direction toggle.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```tsx
|
|
45
|
-
* const [sortBy, setSortBy] = useState<string | null>('name');
|
|
46
|
-
* const [sortDir, setSortDir] = useState<SortDirection>('asc');
|
|
47
|
-
*
|
|
48
|
-
* <AtomicSort
|
|
49
|
-
* options={[
|
|
50
|
-
* { id: 'name', label: 'Name', icon: 'sort-alpha' },
|
|
51
|
-
* { id: 'date', label: 'Date', icon: 'schedule' },
|
|
52
|
-
* { id: 'priority', label: 'Priority', icon: 'flag' },
|
|
53
|
-
* ]}
|
|
54
|
-
* selectedId={sortBy}
|
|
55
|
-
* sortDirection={sortDir}
|
|
56
|
-
* onSortChange={(id, dir) => {
|
|
57
|
-
* setSortBy(id);
|
|
58
|
-
* setSortDir(dir);
|
|
59
|
-
* }}
|
|
60
|
-
* showDirectionToggle={true}
|
|
61
|
-
* />
|
|
62
|
-
* ```
|
|
63
|
-
*
|
|
64
|
-
* Features:
|
|
65
|
-
* - Horizontal scrollable sort chips
|
|
66
|
-
* - Single selection (one active sort at a time)
|
|
67
|
-
* - Direction toggle (click active chip to switch asc/desc)
|
|
68
|
-
* - Visual arrow indicators (↑ asc, ↓ desc)
|
|
69
|
-
* - Theme-aware colors from design tokens
|
|
70
|
-
* - Icon support per sort option
|
|
71
|
-
* - Fully controlled component
|
|
72
|
-
*
|
|
73
|
-
* Behavior:
|
|
74
|
-
* - Click inactive chip → Selects it with ascending direction
|
|
75
|
-
* - Click active chip → Toggles direction (asc ↔ desc)
|
|
76
|
-
* - Visual feedback via filled variant for active sort
|
|
77
|
-
*/
|
|
78
|
-
export const AtomicSort: React.FC<AtomicSortProps> = ({
|
|
79
|
-
options,
|
|
80
|
-
selectedId,
|
|
81
|
-
sortDirection,
|
|
82
|
-
onSortChange,
|
|
83
|
-
showDirectionToggle = true,
|
|
84
|
-
variant = 'outlined',
|
|
85
|
-
color = 'primary',
|
|
86
|
-
size = 'md',
|
|
87
|
-
style,
|
|
88
|
-
testID,
|
|
89
|
-
}) => {
|
|
90
|
-
const tokens = useAppDesignTokens();
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Handle sort chip press
|
|
94
|
-
* - If clicking active chip: Toggle direction
|
|
95
|
-
* - If clicking inactive chip: Select it with 'asc' direction
|
|
96
|
-
*/
|
|
97
|
-
const handleSortPress = (optionId: string) => {
|
|
98
|
-
if (selectedId === optionId) {
|
|
99
|
-
const newDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
|
100
|
-
onSortChange(optionId, newDirection);
|
|
101
|
-
} else {
|
|
102
|
-
onSortChange(optionId, 'asc');
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const directionIcon = sortDirection === 'asc' ? 'arrow-upward' : 'arrow-downward';
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<ScrollView
|
|
110
|
-
horizontal
|
|
111
|
-
showsHorizontalScrollIndicator={false}
|
|
112
|
-
contentContainerStyle={{
|
|
113
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
114
|
-
gap: tokens.spacing.sm,
|
|
115
|
-
}}
|
|
116
|
-
style={[style]}
|
|
117
|
-
testID={testID}
|
|
118
|
-
>
|
|
119
|
-
<View style={{ flexDirection: 'row', gap: tokens.spacing.sm }}>
|
|
120
|
-
{options.map((option) => {
|
|
121
|
-
const isSelected = selectedId === option.id;
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<AtomicChip
|
|
125
|
-
key={option.id}
|
|
126
|
-
variant={isSelected ? 'filled' : variant}
|
|
127
|
-
color={color}
|
|
128
|
-
size={size}
|
|
129
|
-
leadingIcon={option.icon}
|
|
130
|
-
trailingIcon={
|
|
131
|
-
isSelected && showDirectionToggle ? directionIcon : undefined
|
|
132
|
-
}
|
|
133
|
-
selected={isSelected}
|
|
134
|
-
clickable={true}
|
|
135
|
-
onPress={() => handleSortPress(option.id)}
|
|
136
|
-
testID={`sort-chip-${option.id}`}
|
|
137
|
-
>
|
|
138
|
-
{option.label}
|
|
139
|
-
</AtomicChip>
|
|
140
|
-
);
|
|
141
|
-
})}
|
|
142
|
-
</View>
|
|
143
|
-
</ScrollView>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicSwitch - Universal Switch Component
|
|
3
|
-
*
|
|
4
|
-
* Provides consistent switch/toggle functionality with theme integration
|
|
5
|
-
* Theme: {{THEME_NAME}} ({{CATEGORY}} category)
|
|
6
|
-
*
|
|
7
|
-
* Atomic Design Level: ATOM
|
|
8
|
-
* Purpose: Basic switch/toggle input
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* - Settings toggles
|
|
12
|
-
* - Feature enable/disable
|
|
13
|
-
* - Boolean preferences
|
|
14
|
-
* - Form inputs
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React from 'react';
|
|
18
|
-
import { Switch, SwitchProps, StyleSheet, ViewStyle } from 'react-native';
|
|
19
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// TYPE DEFINITIONS
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
export interface AtomicSwitchProps extends Omit<SwitchProps, 'style'> {
|
|
26
|
-
/** Switch value */
|
|
27
|
-
value: boolean;
|
|
28
|
-
/** Value change handler */
|
|
29
|
-
onValueChange: (value: boolean) => void;
|
|
30
|
-
/** Size variant */
|
|
31
|
-
size?: 'sm' | 'md' | 'lg';
|
|
32
|
-
/** Color variant */
|
|
33
|
-
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error';
|
|
34
|
-
/** Disabled state */
|
|
35
|
-
disabled?: boolean;
|
|
36
|
-
/** Container style override */
|
|
37
|
-
style?: ViewStyle;
|
|
38
|
-
/** Track color override */
|
|
39
|
-
trackColor?: { false: string; true: string };
|
|
40
|
-
/** Thumb color override */
|
|
41
|
-
thumbColor?: string;
|
|
42
|
-
/** iOS specific props */
|
|
43
|
-
ios_backgroundColor?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// =============================================================================
|
|
47
|
-
// SIZE CONFIGURATION
|
|
48
|
-
// =============================================================================
|
|
49
|
-
|
|
50
|
-
const SIZE_CONFIG = {
|
|
51
|
-
sm: { scaleX: 0.8, scaleY: 0.8 },
|
|
52
|
-
md: { scaleX: 1, scaleY: 1 },
|
|
53
|
-
lg: { scaleX: 1.2, scaleY: 1.2 },
|
|
54
|
-
} as const;
|
|
55
|
-
|
|
56
|
-
// =============================================================================
|
|
57
|
-
// COMPONENT IMPLEMENTATION
|
|
58
|
-
// =============================================================================
|
|
59
|
-
|
|
60
|
-
export const AtomicSwitch: React.FC<AtomicSwitchProps> = ({
|
|
61
|
-
value,
|
|
62
|
-
onValueChange,
|
|
63
|
-
size = 'md',
|
|
64
|
-
variant = 'primary',
|
|
65
|
-
disabled = false,
|
|
66
|
-
style,
|
|
67
|
-
trackColor,
|
|
68
|
-
thumbColor,
|
|
69
|
-
ios_backgroundColor,
|
|
70
|
-
...props
|
|
71
|
-
}) => {
|
|
72
|
-
const tokens = useAppDesignTokens();
|
|
73
|
-
const styles = getStyles(tokens);
|
|
74
|
-
|
|
75
|
-
const sizeConfig = SIZE_CONFIG[size as 'sm' | 'md' | 'lg'];
|
|
76
|
-
const colors = getVariantColors(tokens, variant as 'primary' | 'secondary' | 'success' | 'warning' | 'error');
|
|
77
|
-
|
|
78
|
-
const defaultTrackColor = trackColor || {
|
|
79
|
-
false: colors.trackFalse,
|
|
80
|
-
true: colors.trackTrue,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const defaultThumbColor = thumbColor || colors.thumb;
|
|
84
|
-
const defaultIosBackgroundColor = ios_backgroundColor || colors.trackFalse;
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<Switch
|
|
88
|
-
value={value}
|
|
89
|
-
onValueChange={onValueChange}
|
|
90
|
-
disabled={disabled}
|
|
91
|
-
trackColor={defaultTrackColor}
|
|
92
|
-
thumbColor={defaultThumbColor}
|
|
93
|
-
ios_backgroundColor={defaultIosBackgroundColor}
|
|
94
|
-
style={[
|
|
95
|
-
styles.switch,
|
|
96
|
-
{
|
|
97
|
-
transform: [{ scaleX: sizeConfig.scaleX }, { scaleY: sizeConfig.scaleY }],
|
|
98
|
-
},
|
|
99
|
-
style,
|
|
100
|
-
]}
|
|
101
|
-
{...props}
|
|
102
|
-
/>
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// =============================================================================
|
|
107
|
-
// HELPER FUNCTIONS
|
|
108
|
-
// =============================================================================
|
|
109
|
-
|
|
110
|
-
const getVariantColors = (tokens: ReturnType<typeof useAppDesignTokens>, variant: AtomicSwitchProps['variant']) => {
|
|
111
|
-
switch (variant) {
|
|
112
|
-
case 'primary':
|
|
113
|
-
return {
|
|
114
|
-
trackFalse: tokens.colors.surfaceSecondary,
|
|
115
|
-
trackTrue: tokens.colors.primary,
|
|
116
|
-
thumb: tokens.colors.surface,
|
|
117
|
-
};
|
|
118
|
-
case 'secondary':
|
|
119
|
-
return {
|
|
120
|
-
trackFalse: tokens.colors.surfaceSecondary,
|
|
121
|
-
trackTrue: tokens.colors.secondary,
|
|
122
|
-
thumb: tokens.colors.surface,
|
|
123
|
-
};
|
|
124
|
-
case 'success':
|
|
125
|
-
return {
|
|
126
|
-
trackFalse: tokens.colors.surfaceSecondary,
|
|
127
|
-
trackTrue: tokens.colors.success,
|
|
128
|
-
thumb: tokens.colors.surface,
|
|
129
|
-
};
|
|
130
|
-
case 'warning':
|
|
131
|
-
return {
|
|
132
|
-
trackFalse: tokens.colors.surfaceSecondary,
|
|
133
|
-
trackTrue: tokens.colors.warning,
|
|
134
|
-
thumb: tokens.colors.surface,
|
|
135
|
-
};
|
|
136
|
-
case 'error':
|
|
137
|
-
return {
|
|
138
|
-
trackFalse: tokens.colors.surfaceSecondary,
|
|
139
|
-
trackTrue: tokens.colors.error,
|
|
140
|
-
thumb: tokens.colors.surface,
|
|
141
|
-
};
|
|
142
|
-
default:
|
|
143
|
-
return {
|
|
144
|
-
trackFalse: tokens.colors.surfaceSecondary,
|
|
145
|
-
trackTrue: tokens.colors.primary,
|
|
146
|
-
thumb: tokens.colors.surface,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// =============================================================================
|
|
152
|
-
// STYLES
|
|
153
|
-
// =============================================================================
|
|
154
|
-
|
|
155
|
-
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
156
|
-
StyleSheet.create({
|
|
157
|
-
switch: {
|
|
158
|
-
// Default switch styling is handled by platform
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// =============================================================================
|
|
163
|
-
// EXPORTS
|
|
164
|
-
// =============================================================================
|
|
165
|
-
|
|
166
|
-
export default AtomicSwitch;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Text, StyleProp, TextStyle } from 'react-native';
|
|
3
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
4
|
-
import type { TextStyleVariant, ColorVariant } from '@umituz/react-native-typography';
|
|
5
|
-
import { getTextColor } from '@umituz/react-native-typography';
|
|
6
|
-
|
|
7
|
-
export interface AtomicTextProps {
|
|
8
|
-
children: React.ReactNode;
|
|
9
|
-
type?: TextStyleVariant;
|
|
10
|
-
color?: ColorVariant | string;
|
|
11
|
-
numberOfLines?: number;
|
|
12
|
-
ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
|
|
13
|
-
textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify';
|
|
14
|
-
style?: StyleProp<TextStyle>;
|
|
15
|
-
testID?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const AtomicText: React.FC<AtomicTextProps> = ({
|
|
19
|
-
children,
|
|
20
|
-
type = 'bodyMedium',
|
|
21
|
-
color,
|
|
22
|
-
numberOfLines,
|
|
23
|
-
ellipsizeMode,
|
|
24
|
-
textAlign,
|
|
25
|
-
style,
|
|
26
|
-
testID,
|
|
27
|
-
}) => {
|
|
28
|
-
const tokens = useAppDesignTokens();
|
|
29
|
-
|
|
30
|
-
// Get typography style from tokens
|
|
31
|
-
const typographyStyle = tokens.typography[type];
|
|
32
|
-
|
|
33
|
-
// Get color from tokens or use custom color using utility function
|
|
34
|
-
const resolvedColor = getTextColor(color, tokens);
|
|
35
|
-
|
|
36
|
-
const textStyle: StyleProp<TextStyle> = [
|
|
37
|
-
typographyStyle,
|
|
38
|
-
{
|
|
39
|
-
color: resolvedColor,
|
|
40
|
-
...(textAlign && { textAlign }),
|
|
41
|
-
},
|
|
42
|
-
style,
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Text
|
|
47
|
-
numberOfLines={numberOfLines}
|
|
48
|
-
ellipsizeMode={ellipsizeMode}
|
|
49
|
-
style={textStyle}
|
|
50
|
-
testID={testID}
|
|
51
|
-
>
|
|
52
|
-
{children}
|
|
53
|
-
</Text>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AtomicTextArea Component
|
|
3
|
-
*
|
|
4
|
-
* A multiline text input component with pure React Native implementation
|
|
5
|
-
* for longer text entry with consistent styling.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Pure React Native TextInput with multiline
|
|
9
|
-
* - Outlined/filled/flat variants
|
|
10
|
-
* - Error, success, disabled states
|
|
11
|
-
* - Character counter with max length
|
|
12
|
-
* - Helper text for guidance or errors
|
|
13
|
-
* - Configurable rows for height
|
|
14
|
-
* - Theme-aware styling
|
|
15
|
-
* - Full accessibility support
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* ```tsx
|
|
19
|
-
* const [description, setDescription] = useState('');
|
|
20
|
-
*
|
|
21
|
-
* <AtomicTextArea
|
|
22
|
-
* value={description}
|
|
23
|
-
* onChangeText={setDescription}
|
|
24
|
-
* label="Description"
|
|
25
|
-
* placeholder="Enter description..."
|
|
26
|
-
* maxLength={500}
|
|
27
|
-
* showCharacterCount
|
|
28
|
-
* rows={6}
|
|
29
|
-
* helperText="Provide a detailed description"
|
|
30
|
-
* />
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import React, { useState } from 'react';
|
|
35
|
-
import { View, TextInput, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
36
|
-
import { useAppDesignTokens } from '@umituz/react-native-theme';
|
|
37
|
-
import { AtomicText } from './AtomicText';
|
|
38
|
-
|
|
39
|
-
export type AtomicTextAreaVariant = 'outlined' | 'filled' | 'flat';
|
|
40
|
-
export type AtomicTextAreaState = 'default' | 'error' | 'success' | 'disabled';
|
|
41
|
-
export type AtomicTextAreaSize = 'sm' | 'md' | 'lg';
|
|
42
|
-
|
|
43
|
-
export interface AtomicTextAreaProps {
|
|
44
|
-
/** Textarea label */
|
|
45
|
-
label?: string;
|
|
46
|
-
/** Current textarea value */
|
|
47
|
-
value?: string;
|
|
48
|
-
/** Value change callback */
|
|
49
|
-
onChangeText?: (text: string) => void;
|
|
50
|
-
/** Textarea variant (outlined, filled, flat) */
|
|
51
|
-
variant?: AtomicTextAreaVariant;
|
|
52
|
-
/** Textarea state (default, error, success, disabled) */
|
|
53
|
-
state?: AtomicTextAreaState;
|
|
54
|
-
/** Textarea size (sm, md, lg) */
|
|
55
|
-
size?: AtomicTextAreaSize;
|
|
56
|
-
/** Placeholder text */
|
|
57
|
-
placeholder?: string;
|
|
58
|
-
/** Helper text below textarea */
|
|
59
|
-
helperText?: string;
|
|
60
|
-
/** Maximum character length */
|
|
61
|
-
maxLength?: number;
|
|
62
|
-
/** Show character counter */
|
|
63
|
-
showCharacterCount?: boolean;
|
|
64
|
-
/** Number of visible text rows */
|
|
65
|
-
rows?: number;
|
|
66
|
-
/** Minimum height in pixels */
|
|
67
|
-
minHeight?: number;
|
|
68
|
-
/** Auto-capitalize */
|
|
69
|
-
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
|
|
70
|
-
/** Auto-correct */
|
|
71
|
-
autoCorrect?: boolean;
|
|
72
|
-
/** Disabled state */
|
|
73
|
-
disabled?: boolean;
|
|
74
|
-
/** Container style */
|
|
75
|
-
style?: StyleProp<ViewStyle>;
|
|
76
|
-
/** Input text style */
|
|
77
|
-
inputStyle?: StyleProp<TextStyle>;
|
|
78
|
-
/** Test ID for E2E testing */
|
|
79
|
-
testID?: string;
|
|
80
|
-
/** Blur callback */
|
|
81
|
-
onBlur?: () => void;
|
|
82
|
-
/** Focus callback */
|
|
83
|
-
onFocus?: () => void;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* AtomicTextArea - Pure React Native Multiline Text Input
|
|
88
|
-
*/
|
|
89
|
-
export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
90
|
-
variant = 'outlined',
|
|
91
|
-
state = 'default',
|
|
92
|
-
size = 'md',
|
|
93
|
-
label,
|
|
94
|
-
value = '',
|
|
95
|
-
onChangeText,
|
|
96
|
-
placeholder,
|
|
97
|
-
helperText,
|
|
98
|
-
maxLength,
|
|
99
|
-
showCharacterCount = false,
|
|
100
|
-
rows = 4,
|
|
101
|
-
minHeight,
|
|
102
|
-
autoCapitalize = 'sentences',
|
|
103
|
-
autoCorrect = true,
|
|
104
|
-
disabled = false,
|
|
105
|
-
style,
|
|
106
|
-
inputStyle,
|
|
107
|
-
testID,
|
|
108
|
-
onBlur,
|
|
109
|
-
onFocus,
|
|
110
|
-
}) => {
|
|
111
|
-
const tokens = useAppDesignTokens();
|
|
112
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
113
|
-
const isDisabled = state === 'disabled' || disabled;
|
|
114
|
-
const characterCount = value?.toString().length || 0;
|
|
115
|
-
const hasError = state === 'error';
|
|
116
|
-
const hasSuccess = state === 'success';
|
|
117
|
-
|
|
118
|
-
// Size configuration
|
|
119
|
-
const sizeConfig = {
|
|
120
|
-
sm: {
|
|
121
|
-
paddingVertical: tokens.spacing.xs,
|
|
122
|
-
paddingHorizontal: tokens.spacing.sm,
|
|
123
|
-
fontSize: tokens.typography.bodySmall.fontSize,
|
|
124
|
-
lineHeight: 20,
|
|
125
|
-
},
|
|
126
|
-
md: {
|
|
127
|
-
paddingVertical: tokens.spacing.sm,
|
|
128
|
-
paddingHorizontal: tokens.spacing.md,
|
|
129
|
-
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
130
|
-
lineHeight: 24,
|
|
131
|
-
},
|
|
132
|
-
lg: {
|
|
133
|
-
paddingVertical: tokens.spacing.md,
|
|
134
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
135
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
136
|
-
lineHeight: 28,
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const config = sizeConfig[size];
|
|
141
|
-
|
|
142
|
-
// Calculate height based on rows
|
|
143
|
-
const getTextAreaHeight = () => {
|
|
144
|
-
if (minHeight) return minHeight;
|
|
145
|
-
const paddingVertical = config.paddingVertical * 2;
|
|
146
|
-
return (rows * config.lineHeight) + paddingVertical;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Get variant styles
|
|
150
|
-
const getVariantStyle = (): ViewStyle => {
|
|
151
|
-
const baseStyle: ViewStyle = {
|
|
152
|
-
backgroundColor: tokens.colors.surface,
|
|
153
|
-
borderRadius: tokens.borders.radius.md,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
let borderColor = tokens.colors.border;
|
|
157
|
-
if (isFocused) borderColor = tokens.colors.primary;
|
|
158
|
-
if (hasError) borderColor = tokens.colors.error;
|
|
159
|
-
if (hasSuccess) borderColor = tokens.colors.success;
|
|
160
|
-
if (isDisabled) borderColor = tokens.colors.borderDisabled;
|
|
161
|
-
|
|
162
|
-
switch (variant) {
|
|
163
|
-
case 'outlined':
|
|
164
|
-
return {
|
|
165
|
-
...baseStyle,
|
|
166
|
-
borderWidth: isFocused ? 2 : 1,
|
|
167
|
-
borderColor,
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
case 'filled':
|
|
171
|
-
return {
|
|
172
|
-
...baseStyle,
|
|
173
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
174
|
-
borderWidth: 0,
|
|
175
|
-
borderBottomWidth: isFocused ? 2 : 1,
|
|
176
|
-
borderBottomColor: borderColor,
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
case 'flat':
|
|
180
|
-
return {
|
|
181
|
-
...baseStyle,
|
|
182
|
-
backgroundColor: 'transparent',
|
|
183
|
-
borderWidth: 0,
|
|
184
|
-
borderBottomWidth: 1,
|
|
185
|
-
borderBottomColor: borderColor,
|
|
186
|
-
borderRadius: 0,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
default:
|
|
190
|
-
return baseStyle;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// Get text color based on state
|
|
195
|
-
const getTextColor = () => {
|
|
196
|
-
if (isDisabled) return tokens.colors.textDisabled;
|
|
197
|
-
if (hasError) return tokens.colors.error;
|
|
198
|
-
if (hasSuccess) return tokens.colors.success;
|
|
199
|
-
return tokens.colors.textPrimary;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const containerStyle: StyleProp<ViewStyle> = [
|
|
203
|
-
styles.container,
|
|
204
|
-
getVariantStyle(),
|
|
205
|
-
{
|
|
206
|
-
paddingVertical: config.paddingVertical,
|
|
207
|
-
paddingHorizontal: config.paddingHorizontal,
|
|
208
|
-
height: getTextAreaHeight(),
|
|
209
|
-
opacity: isDisabled ? 0.5 : 1,
|
|
210
|
-
},
|
|
211
|
-
style,
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
const textInputStyle: StyleProp<TextStyle> = [
|
|
215
|
-
styles.input,
|
|
216
|
-
{
|
|
217
|
-
fontSize: config.fontSize,
|
|
218
|
-
lineHeight: config.lineHeight,
|
|
219
|
-
color: getTextColor(),
|
|
220
|
-
},
|
|
221
|
-
inputStyle,
|
|
222
|
-
];
|
|
223
|
-
|
|
224
|
-
return (
|
|
225
|
-
<View testID={testID}>
|
|
226
|
-
{label && (
|
|
227
|
-
<AtomicText
|
|
228
|
-
type="labelMedium"
|
|
229
|
-
color={hasError ? 'error' : hasSuccess ? 'success' : 'secondary'}
|
|
230
|
-
style={styles.label}
|
|
231
|
-
>
|
|
232
|
-
{label}
|
|
233
|
-
</AtomicText>
|
|
234
|
-
)}
|
|
235
|
-
|
|
236
|
-
<View style={containerStyle}>
|
|
237
|
-
<TextInput
|
|
238
|
-
value={value}
|
|
239
|
-
onChangeText={onChangeText}
|
|
240
|
-
placeholder={placeholder}
|
|
241
|
-
placeholderTextColor={tokens.colors.textSecondary}
|
|
242
|
-
maxLength={maxLength}
|
|
243
|
-
autoCapitalize={autoCapitalize}
|
|
244
|
-
autoCorrect={autoCorrect}
|
|
245
|
-
editable={!isDisabled}
|
|
246
|
-
multiline={true}
|
|
247
|
-
numberOfLines={rows}
|
|
248
|
-
textAlignVertical="top"
|
|
249
|
-
style={textInputStyle}
|
|
250
|
-
onBlur={() => {
|
|
251
|
-
setIsFocused(false);
|
|
252
|
-
onBlur?.();
|
|
253
|
-
}}
|
|
254
|
-
onFocus={() => {
|
|
255
|
-
setIsFocused(true);
|
|
256
|
-
onFocus?.();
|
|
257
|
-
}}
|
|
258
|
-
testID={testID ? `${testID}-input` : undefined}
|
|
259
|
-
/>
|
|
260
|
-
</View>
|
|
261
|
-
|
|
262
|
-
{(helperText || showCharacterCount) && (
|
|
263
|
-
<View style={styles.helperRow}>
|
|
264
|
-
{helperText && (
|
|
265
|
-
<AtomicText
|
|
266
|
-
type="bodySmall"
|
|
267
|
-
color={hasError ? 'error' : 'secondary'}
|
|
268
|
-
style={styles.helperText}
|
|
269
|
-
testID={testID ? `${testID}-helper` : undefined}
|
|
270
|
-
>
|
|
271
|
-
{helperText}
|
|
272
|
-
</AtomicText>
|
|
273
|
-
)}
|
|
274
|
-
{showCharacterCount && maxLength && (
|
|
275
|
-
<AtomicText
|
|
276
|
-
type="bodySmall"
|
|
277
|
-
color="secondary"
|
|
278
|
-
style={styles.characterCount}
|
|
279
|
-
testID={testID ? `${testID}-count` : undefined}
|
|
280
|
-
>
|
|
281
|
-
{characterCount}/{maxLength}
|
|
282
|
-
</AtomicText>
|
|
283
|
-
)}
|
|
284
|
-
</View>
|
|
285
|
-
)}
|
|
286
|
-
</View>
|
|
287
|
-
);
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const styles = StyleSheet.create({
|
|
291
|
-
container: {
|
|
292
|
-
justifyContent: 'flex-start',
|
|
293
|
-
},
|
|
294
|
-
input: {
|
|
295
|
-
flex: 1,
|
|
296
|
-
},
|
|
297
|
-
label: {
|
|
298
|
-
marginBottom: 4,
|
|
299
|
-
},
|
|
300
|
-
helperRow: {
|
|
301
|
-
flexDirection: 'row',
|
|
302
|
-
justifyContent: 'space-between',
|
|
303
|
-
marginTop: 4,
|
|
304
|
-
},
|
|
305
|
-
helperText: {
|
|
306
|
-
flex: 1,
|
|
307
|
-
},
|
|
308
|
-
characterCount: {
|
|
309
|
-
marginLeft: 8,
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
export type { AtomicTextAreaProps as TextAreaProps };
|