feat(search): add keyword highlighting to PostCard and improve QRCodeScanner
Add HighlightText component for search result highlighting in PostCard. Refactor QRCodeScanner to use async permission methods with better error handling. Improve SearchScreen with stable function refs and pass highlightKeyword to PostCard. Modernize MessageListScreen search UI with SearchBar and TabBar components. Add emoji virtualization to EmojiPanel and auto-focus input after inserting emoji. BREAKING CHANGE: EmojiPanel onFocusInput prop added for focus management
This commit is contained in:
54
src/components/common/HighlightText.tsx
Normal file
54
src/components/common/HighlightText.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Text, TextStyle, StyleProp } from 'react-native';
|
||||
|
||||
interface HighlightTextProps {
|
||||
text: string;
|
||||
keyword: string;
|
||||
style?: StyleProp<TextStyle>;
|
||||
}
|
||||
|
||||
const HighlightText: React.FC<HighlightTextProps> = ({ text, keyword, style }) => {
|
||||
const parts = useMemo(() => {
|
||||
if (!text || !keyword) return [{ text, highlight: false }];
|
||||
|
||||
const result: Array<{ text: string; highlight: boolean }> = [];
|
||||
const lowerText = text.toLowerCase();
|
||||
const lowerKeyword = keyword.toLowerCase();
|
||||
let lastIndex = 0;
|
||||
|
||||
let searchFrom = 0;
|
||||
while (searchFrom < lowerText.length) {
|
||||
const index = lowerText.indexOf(lowerKeyword, searchFrom);
|
||||
if (index === -1) break;
|
||||
|
||||
if (index > lastIndex) {
|
||||
result.push({ text: text.substring(lastIndex, index), highlight: false });
|
||||
}
|
||||
result.push({ text: text.substring(index, index + keyword.length), highlight: true });
|
||||
lastIndex = index + keyword.length;
|
||||
searchFrom = lastIndex;
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
result.push({ text: text.substring(lastIndex), highlight: false });
|
||||
}
|
||||
|
||||
return result.length > 0 ? result : [{ text, highlight: false }];
|
||||
}, [text, keyword]);
|
||||
|
||||
return (
|
||||
<Text>
|
||||
{parts.map((part, i) =>
|
||||
part.highlight ? (
|
||||
<Text key={i} style={style}>
|
||||
{part.text}
|
||||
</Text>
|
||||
) : (
|
||||
<Text key={i}>{part.text}</Text>
|
||||
)
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default HighlightText;
|
||||
@@ -7,6 +7,7 @@ export { default as Button } from './Button';
|
||||
export { default as Card } from './Card';
|
||||
export { default as Input } from './Input';
|
||||
export { default as Loading } from './Loading';
|
||||
export { default as HighlightText } from './HighlightText';
|
||||
export { default as Text } from './Text';
|
||||
export { default as EmptyState } from './EmptyState';
|
||||
export { default as Divider } from './Divider';
|
||||
|
||||
Reference in New Issue
Block a user