Commit 63f6f8af by tungnq

TODO: Đã hoàn thiện được componet chip trong email input fix được vùng giao diện…

TODO: Đã hoàn thiện được componet chip trong email input fix được vùng giao diện chết do wapper tạo ra không còn chỉ nhấn vào title mới sang được chế độ show input
parent a0073857
Subproject commit c95f83687e728b84c9eb4df75e9a89ad0afb1b1a
import React, {useRef, useState} from 'react';
import {View, TextInput, TouchableOpacity, Image} from 'react-native';
import {View, TextInput, TouchableOpacity, Image, Text} from 'react-native';
import Chip from './chip';
import {BACKSPACE, DELIMITERS} from '../../config/constants';
import {isValidEmail} from '../../config/Functions';
import {Text} from 'react-native';
import R from '../../assets/R';
/**
* FUNCTIONALITY: Component cho phép nhập email và hiển thị dạng chip
@param {Object} props
- Props của component
@param {string[]} props.entries
- Danh sách email ban đầu
@param {Function} props.onSubmit
- Callback khi submit danh sách email
@param {string[]} props.delimiters
- Các ký tự phân tách để tự động thêm email
@param {Object} props.containerStyle
- Style cho container chính
@param {Object} props.chipContainerStyle
- Style cho từng chip
@param {Object} props.chipTextStyle
- Style cho text trong chip
@param {Object} props.inputContainerStyle
- Style cho container input
@param {Object} props.inputStyle
- Style cho TextInput
@param {React.Element} props.chipImage
- Icon hiển thị trong chip (thường là nút X)
@param {string} props.keyboardAppearance
- Appearance của keyboard
@param {string} props.clearButtonMode
- Mode của clear button
@param {string} props.placeholder
- Placeholder text cho input
@param {string} props.placeholderTextColor
- Màu placeholder
@param {string} props.autoCapitalize
- Auto capitalize setting
@param {boolean} props.autoCorrect
- Bật/tắt auto correct
@param {boolean} props.autoFocus
- Auto focus khi mount
@param {boolean} props.blurOnSubmit
- Blur input khi submit
@param {string} props.keyboardType
- Loại keyboard
@param {Object} props.TextInputProps
- Props bổ sung cho TextInput
@param {string} props.widthContainerInput
- Chiều rộng của container input
*/
export const EmailChipInput = ({
title,
entries,
......@@ -71,7 +24,6 @@ export const EmailChipInput = ({
placeholder,
autoCapitalize = 'none',
autoFocus = false,
blurOnSubmit = true,
keyboardType = 'email-address',
widthContainerInput = '100%',
}) => {
......@@ -79,81 +31,71 @@ export const EmailChipInput = ({
const [emails, setEmails] = useState(entries);
const [value, setValue] = useState('');
const [isExpanded, setIsExpanded] = useState(false);
const [showInput, setShowInput] = useState(false);
const [showInput, setShowInput] = useState(true); // mở sẵn
// Thu gọn chỉ khi blur & không có giá trị
const onBlur = () => {
if (!value) {
setShowInput(false);
}
if (!value) setShowInput(false);
};
// Chip -> chuyển sang edit
const handleOnPressChip = index => {
setValue(emails[index]);
setEmails(emails.filter((_, i) => i !== index));
// UI/UX: Focus vào input để tiếp tục chỉnh sửa
return ref.current?.focus();
setShowInput(true); // remount TextInput -> autoFocus
};
// PERFORMANCE: Tối ưu việc lấy entry cuối cùng
const lastEntry = emails[emails.length - 1];
// FUNCTIONALITY: Xử lý thay đổi text trong input
// Nhập text + thêm qua delimiter
const onTextChange = inputValue => {
// BUG: Tránh duplicate khi edit email cuối cùng
if (inputValue === lastEntry) {
return setValue('');
}
if (inputValue === lastEntry) return setValue('');
// FEATURE: Tự động thêm email khi gặp delimiter
if (
isValidEmail(inputValue) &&
delimiters.some(delimiter => inputValue.endsWith(delimiter))
delimiters.some(d => inputValue.endsWith(d))
) {
setEmails([
...emails,
inputValue.substring(0, inputValue.length - 1), // Loại bỏ delimiter
]);
const cleaned = inputValue.slice(0, -1).trim();
if (cleaned) {
const newEntries = [...emails, cleaned];
setEmails(newEntries);
setValue('');
onSubmit?.(newEntries);
setShowInput(true); // remount vẫn giữ mở
} else {
setValue('');
}
return;
}
setValue(inputValue);
};
// FUNCTIONALITY: Xử lý phím backspace để xóa chip
// Backspace khi trống -> edit chip cuối
const onKeyPress = ({nativeEvent: {key}}) => {
// FEATURE: Backspace khi input trống sẽ edit chip cuối cùng
if (!value && key === BACKSPACE && lastEntry) {
setValue(lastEntry);
setEmails(emails.filter(email => email !== lastEntry));
setEmails(emails.filter(e => e !== lastEntry));
setShowInput(true); // remount -> autoFocus
}
};
// FUNCTIONALITY: Xử lý blur/submit để finalize email
const handleBlur = () => {
// VALIDATION: Nếu email không hợp lệ hoặc rỗng, chỉ submit danh sách hiện tại
if (!isValidEmail(value)) {
return onSubmit(emails);
}
// FEATURE: Thêm email hiện tại vào danh sách và submit
const newEntries = [...emails, value];
// Submit bằng Enter nhưng KHÔNG đóng input
const submitCurrent = () => {
const v = value.trim();
if (!isValidEmail(v)) return;
const newEntries = [...emails, v];
setEmails(newEntries);
setValue('');
onSubmit(newEntries);
onSubmit?.(newEntries);
setShowInput(true); // vẫn mở để nhập tiếp
};
// FUNCTIONALITY: Toggle chế độ hiển thị chip
const toggleExpanded = () => {
setIsExpanded(!isExpanded);
};
const toggleExpanded = () => setIsExpanded(!isExpanded);
// FUNCTIONALITY: Render chip theo chế độ hiển thị
const renderChips = () => {
if (emails.length === 0) return null;
// Nếu chỉ có 1 chip hoặc đang ở chế độ mở rộng, hiển thị tất cả
if (emails.length === 1 || isExpanded) {
return emails.map((email, index) => (
<Chip
......@@ -169,7 +111,6 @@ export const EmailChipInput = ({
));
}
// Chế độ thu gọn: chỉ hiển thị chip đầu + text số lượng
return (
<>
<Chip
......@@ -190,29 +131,39 @@ export const EmailChipInput = ({
marginLeft: 5,
alignSelf: 'center',
}}>
+{emails.length - 1} more
+{emails.length - 1}
</Text>
</>
);
};
// Khi input ẩn: dùng TouchableOpacity để mở.
// Khi input hiện: dùng View + pointerEvents để không chặn touch của chip/nút X.
const Wrapper = showInput ? View : TouchableOpacity;
const wrapperProps = showInput
? {pointerEvents: 'box-none'}
: {
onPress: () => setShowInput(true),
activeOpacity: 0.7,
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
};
return (
<View style={containerStyle}>
<View
style={[
{
style={{
flexDirection: 'row',
flexWrap: emails.length > 0 ? 'wrap' : 'nowrap',
// flexWrap: emails.length > 0 ? 'wrap' : 'nowrap',
alignItems: 'center',
},
]}>
{/* FEATURE: Render danh sách email chips */}
<TouchableOpacity
onPress={() => setShowInput(true)}
}}>
<Wrapper
{...wrapperProps}
style={{
minHeight: 35,
flexDirection: 'row',
flexWrap: emails.length > 0 ? 'wrap' : 'nowrap',
alignItems: 'center',
flex:1,
}}>
<Text
style={{
......@@ -224,48 +175,24 @@ export const EmailChipInput = ({
{title}
</Text>
{renderChips()}
</TouchableOpacity>
{/* FEATURE: Nút toggle thu gọn/phóng to */}
{emails.length > 1 && (
<View style={{position: 'absolute', right: -3, top: 0}}>
<TouchableOpacity
onPress={toggleExpanded}
style={{
backgroundColor: R.colors.blue4,
borderRadius: 15,
padding: 3,
}}>
<Image
source={isExpanded ? R.images.icBack2 : R.images.icDrop}
style={{
width: 25,
height: 25,
tintColor: R.colors.black,
}}
/>
</TouchableOpacity>
</View>
)}
{showInput && (
<View
style={[
{width: '100%', height: 35, widthContainerInput},
{ height: 35, widthContainerInput,},
inputContainerStyle,
]}>
<TextInput
value={value}
ref={ref}
value={value}
onChangeText={onTextChange}
onKeyPress={onKeyPress}
onSubmitEditing={handleBlur}
onBlur={onBlur}
blurOnSubmit={blurOnSubmit}
onSubmitEditing={submitCurrent} // nhập liên tiếp
onBlur={onBlur} // chỉ đóng khi rỗng
blurOnSubmit={false} // Enter không blur
keyboardType={keyboardType}
clearButtonMode={clearButtonMode}
autoCorrect={autoCorrect}
autoFocus={autoFocus}
autoFocus={showInput || autoFocus} // <-- tự focus khi mount
placeholder={placeholder}
autoCapitalize={autoCapitalize}
keyboardAppearance={keyboardAppearance}
......@@ -277,8 +204,8 @@ export const EmailChipInput = ({
fontFamily: R.fonts.fontMedium,
fontWeight: '600',
padding: 0,
height: '100%',
paddingHorizontal: 0,
height: 35,
paddingHorizontal: 10,
textAlignVertical: 'center',
},
inputStyle,
......@@ -287,10 +214,28 @@ export const EmailChipInput = ({
/>
</View>
)}
</Wrapper>
{emails.length > 1 && (
<View style={{position: 'absolute', right: -3, top: 0}}>
<TouchableOpacity
onPress={toggleExpanded}
style={{
backgroundColor: R.colors.blue4,
borderRadius: 15,
padding: 3,
}}>
<Image
source={isExpanded ? R.images.icBack2 : R.images.icDrop}
style={{width: 25, height: 25, tintColor: R.colors.black}}
/>
</TouchableOpacity>
</View>
)}
</View>
</View>
);
};
// PERFORMANCE: Memoize component để tránh re-render
export default React.memo(EmailChipInput);
......@@ -52,6 +52,21 @@ const styles = StyleSheet.create({
iconClose:{
width:15,
height:15
},
inputContainer:{
height:35,
flexDirection:'row',
alignItems:'center',
borderBottomWidth:1,
borderColor:R.colors.blue4,
borderRadius:5,
},
input:{
padding:0,
paddingHorizontal:10,
paddingVertical:0
}
})
......
import React from 'react';
import {Text, View, TouchableOpacity, StyleSheet, Image} from 'react-native';
import {Text, View, TouchableOpacity, StyleSheet, Image, TextInput} from 'react-native';
import styles from './style';
import R from '../../../assets/R';
import Button from '../../../components/Button';
......@@ -31,7 +31,6 @@ const renderHeader = () => {
style={styles.container}>
{renderHeader()}
<View style={styles.body}>
<EmailChipInput
title="To"
delimiters={dataList}
......@@ -41,13 +40,84 @@ const renderHeader = () => {
chipTextStyle={{ color:R.colors.black }}
containerStyle={{
borderBottomWidth:1,
borderColor: R.colors.blue4,
borderColor: R.colors.grayBorderInputTextHeader,
borderRadius: 5,
marginBottom:10
}}
chipImage={
<Image source={R.images.icCancel} style={styles.iconClose} tintColor={R.colors.black}/>
}
/>
<EmailChipInput
title="CC"
delimiters={dataList}
entries={chip}
onSubmit={handleChange}
chipContainerStyle={{ backgroundColor: R.colors.blue4}}
chipTextStyle={{ color:R.colors.black }}
containerStyle={{
borderBottomWidth:1,
borderColor: R.colors.grayBorderInputTextHeader,
borderRadius: 5,
marginBottom:10
}}
chipImage={
<Image source={R.images.icCancel} style={styles.iconClose} tintColor={R.colors.black}/>
}
/>
<EmailChipInput
title="BCC"
delimiters={dataList}
entries={chip}
onSubmit={handleChange}
chipContainerStyle={{ backgroundColor: R.colors.blue4}}
chipTextStyle={{ color:R.colors.black }}
containerStyle={{
borderBottomWidth:1,
borderColor: R.colors.grayBorderInputTextHeader,
borderRadius: 5,
marginBottom:10
}}
chipImage={
<Image source={R.images.icCancel} style={styles.iconClose} tintColor={R.colors.black}/>
}
/>
<EmailChipInput
title="From"
delimiters={dataList}
entries={chip}
onSubmit={handleChange}
chipContainerStyle={{ backgroundColor: R.colors.blue4}}
chipTextStyle={{ color:R.colors.black }}
containerStyle={{
borderBottomWidth:1,
borderColor: R.colors.grayBorderInputTextHeader,
borderRadius: 5,
marginBottom:10
}}
chipImage={
<Image source={R.images.icCancel} style={styles.iconClose} tintColor={R.colors.black}/>
}
/>
<View style={styles.inputContainer}>
<Text>
Subject
</Text>
<TextInput
placeholder="Subject"
style={styles.input}
/>
</View>
<View style={styles.inputContainer}>
<Text>
Content
</Text>
<TextInput
placeholder="Content"
style={styles.input}
/>
</View>
</View>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment