react-native-sop-docs/03-development-standards/components.md

520 lines
11 KiB
Markdown

# Required Components
## TextInput Component
```typescript
// src/components/common/TextInput/TextInput.tsx
import React, {useState} from 'react';
import {
TextInput as RNTextInput,
View,
Text,
StyleSheet,
TextInputProps,
} from 'react-native';
interface CustomTextInputProps extends TextInputProps {
label?: string;
error?: string;
required?: boolean;
}
export const TextInput: React.FC<CustomTextInputProps> = ({
label,
error,
required,
style,
...props
}) => {
const [isFocused, setIsFocused] = useState(false);
return (
<View style={styles.container}>
{label && (
<Text style={styles.label}>
{label}
{required && <Text style={styles.required}> *</Text>}
</Text>
)}
<RNTextInput
style={[
styles.input,
isFocused && styles.focused,
error && styles.error,
style,
]}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
{...props}
/>
{error && <Text style={styles.errorText}>{error}</Text>}
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '500',
marginBottom: 8,
color: '#333',
},
required: {
color: '#FF3B30',
},
input: {
borderWidth: 1,
borderColor: '#E5E5E7',
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 16,
backgroundColor: '#FFFFFF',
},
focused: {
borderColor: '#007AFF',
},
error: {
borderColor: '#FF3B30',
},
errorText: {
fontSize: 12,
color: '#FF3B30',
marginTop: 4,
},
});
```
## Header Component (CHeader)
```typescript
// src/components/common/CHeader/CHeader.tsx
import React from 'react';
import {View, Text, TouchableOpacity, StyleSheet, StatusBar} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
interface CHeaderProps {
title: string;
showBackButton?: boolean;
onBackPress?: () => void;
rightComponent?: React.ReactNode;
backgroundColor?: string;
}
export const CHeader: React.FC<CHeaderProps> = ({
title,
showBackButton = true,
onBackPress,
rightComponent,
backgroundColor = '#FFFFFF',
}) => {
const insets = useSafeAreaInsets();
return (
<View style={[styles.container, {paddingTop: insets.top, backgroundColor}]}>
<StatusBar barStyle="dark-content" backgroundColor={backgroundColor} />
<View style={styles.header}>
{showBackButton && (
<TouchableOpacity onPress={onBackPress} style={styles.backButton}>
<Text style={styles.backText}></Text>
</TouchableOpacity>
)}
<Text style={styles.title}>{title}</Text>
<View style={styles.rightContainer}>{rightComponent}</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
borderBottomWidth: 1,
borderBottomColor: '#E5E5E7',
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
},
backButton: {
padding: 8,
},
backText: {
fontSize: 24,
color: '#007AFF',
},
title: {
flex: 1,
fontSize: 18,
fontWeight: '600',
textAlign: 'center',
color: '#000',
},
rightContainer: {
minWidth: 40,
alignItems: 'flex-end',
},
});
```
## AlertModal Component
```typescript
// src/components/common/AlertModal/AlertModal.tsx
import React from 'react';
import {
Modal,
View,
Text,
TouchableOpacity,
StyleSheet,
Dimensions,
} from 'react-native';
interface AlertModalProps {
visible: boolean;
title: string;
message: string;
onConfirm?: () => void;
onCancel?: () => void;
confirmText?: string;
cancelText?: string;
type?: 'info' | 'warning' | 'error' | 'success';
}
export const AlertModal: React.FC<AlertModalProps> = ({
visible,
title,
message,
onConfirm,
onCancel,
confirmText = 'OK',
cancelText = 'Cancel',
type = 'info',
}) => {
return (
<Modal visible={visible} transparent animationType="fade">
<View style={styles.overlay}>
<View style={styles.container}>
<Text style={[styles.title, styles[`${type}Title`]]}>{title}</Text>
<Text style={styles.message}>{message}</Text>
<View style={styles.buttonContainer}>
{onCancel && (
<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<Text style={styles.cancelText}>{cancelText}</Text>
</TouchableOpacity>
)}
{onConfirm && (
<TouchableOpacity
style={[styles.confirmButton, styles[`${type}Button`]]}
onPress={onConfirm}>
<Text style={styles.confirmText}>{confirmText}</Text>
</TouchableOpacity>
)}
</View>
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
container: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 20,
width: Dimensions.get('window').width - 40,
maxWidth: 400,
},
title: {
fontSize: 18,
fontWeight: '600',
marginBottom: 12,
textAlign: 'center',
},
infoTitle: {
color: '#007AFF',
},
warningTitle: {
color: '#FF9500',
},
errorTitle: {
color: '#FF3B30',
},
successTitle: {
color: '#34C759',
},
message: {
fontSize: 16,
color: '#666',
textAlign: 'center',
marginBottom: 20,
lineHeight: 22,
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
cancelButton: {
flex: 1,
paddingVertical: 12,
marginRight: 8,
borderRadius: 8,
borderWidth: 1,
borderColor: '#E5E5E7',
},
confirmButton: {
flex: 1,
paddingVertical: 12,
marginLeft: 8,
borderRadius: 8,
},
infoButton: {
backgroundColor: '#007AFF',
},
warningButton: {
backgroundColor: '#FF9500',
},
errorButton: {
backgroundColor: '#FF3B30',
},
successButton: {
backgroundColor: '#34C759',
},
cancelText: {
textAlign: 'center',
fontSize: 16,
color: '#666',
},
confirmText: {
textAlign: 'center',
fontSize: 16,
color: '#FFFFFF',
fontWeight: '600',
},
});
```
## Dropdown Component
```typescript
// src/components/common/Dropdown/Dropdown.tsx
import React, {useState} from 'react';
import {
View,
Text,
TouchableOpacity,
FlatList,
StyleSheet,
Modal,
} from 'react-native';
interface DropdownItem {
label: string;
value: string;
}
interface DropdownProps {
items: DropdownItem[];
selectedValue?: string;
onSelect: (item: DropdownItem) => void;
placeholder?: string;
label?: string;
}
export const Dropdown: React.FC<DropdownProps> = ({
items,
selectedValue,
onSelect,
placeholder = 'Select an option',
label,
}) => {
const [isVisible, setIsVisible] = useState(false);
const selectedItem = items.find(item => item.value === selectedValue);
return (
<View style={styles.container}>
{label && <Text style={styles.label}>{label}</Text>}
<TouchableOpacity
style={styles.selector}
onPress={() => setIsVisible(true)}>
<Text style={[styles.selectorText, !selectedItem && styles.placeholder]}>
{selectedItem ? selectedItem.label : placeholder}
</Text>
<Text style={styles.arrow}></Text>
</TouchableOpacity>
<Modal visible={isVisible} transparent animationType="fade">
<TouchableOpacity
style={styles.overlay}
onPress={() => setIsVisible(false)}>
<View style={styles.dropdown}>
<FlatList
data={items}
keyExtractor={item => item.value}
renderItem={({item}) => (
<TouchableOpacity
style={styles.item}
onPress={() => {
onSelect(item);
setIsVisible(false);
}}>
<Text style={styles.itemText}>{item.label}</Text>
</TouchableOpacity>
)}
/>
</View>
</TouchableOpacity>
</Modal>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '500',
marginBottom: 8,
color: '#333',
},
selector: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
borderColor: '#E5E5E7',
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: '#FFFFFF',
},
selectorText: {
fontSize: 16,
color: '#333',
},
placeholder: {
color: '#999',
},
arrow: {
fontSize: 12,
color: '#666',
},
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
dropdown: {
backgroundColor: '#FFFFFF',
borderRadius: 8,
maxHeight: 200,
width: '80%',
},
item: {
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
itemText: {
fontSize: 16,
color: '#333',
},
});
```
## RadioButton Component
```typescript
// src/components/common/RadioButton/RadioButton.tsx
import React from 'react';
import {View, Text, TouchableOpacity, StyleSheet} from 'react-native';
interface RadioOption {
label: string;
value: string;
}
interface RadioButtonProps {
options: RadioOption[];
selectedValue?: string;
onSelect: (value: string) => void;
label?: string;
}
export const RadioButton: React.FC<RadioButtonProps> = ({
options,
selectedValue,
onSelect,
label,
}) => {
return (
<View style={styles.container}>
{label && <Text style={styles.label}>{label}</Text>}
{options.map(option => (
<TouchableOpacity
key={option.value}
style={styles.option}
onPress={() => onSelect(option.value)}>
<View style={styles.radio}>
{selectedValue === option.value && <View style={styles.selected} />}
</View>
<Text style={styles.optionText}>{option.label}</Text>
</TouchableOpacity>
))}
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '500',
marginBottom: 8,
color: '#333',
},
option: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
},
radio: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: '#007AFF',
marginRight: 12,
alignItems: 'center',
justifyContent: 'center',
},
selected: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: '#007AFF',
},
optionText: {
fontSize: 16,
color: '#333',
},
});
```