132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { X } from 'lucide-react-native';
|
|
import { useCallback } from 'react';
|
|
import {
|
|
Pressable,
|
|
type StyleProp,
|
|
StyleSheet,
|
|
Text,
|
|
type TextStyle,
|
|
View,
|
|
type ViewStyle,
|
|
} from 'react-native';
|
|
|
|
import { useTheme } from '@/theme/useTheme';
|
|
|
|
export type ThemeChipSelectedVariant = 'gradient' | 'accent';
|
|
|
|
export type ThemeChipProps = {
|
|
label: string;
|
|
value?: string;
|
|
selected?: boolean;
|
|
selectedVariant?: ThemeChipSelectedVariant;
|
|
disabled?: boolean;
|
|
showCross?: boolean;
|
|
showRemoveIcon?: boolean;
|
|
onSelect?: (value: string, nextSelected: boolean) => void;
|
|
style?: StyleProp<ViewStyle>;
|
|
textStyle?: StyleProp<TextStyle>;
|
|
testID?: string;
|
|
accessibilityLabel?: string;
|
|
};
|
|
|
|
const CHIP_HEIGHT = 34;
|
|
|
|
export function ThemeChip({
|
|
label,
|
|
value,
|
|
selected = false,
|
|
selectedVariant = 'accent',
|
|
disabled = false,
|
|
showCross,
|
|
showRemoveIcon = true,
|
|
onSelect,
|
|
style,
|
|
textStyle,
|
|
testID,
|
|
accessibilityLabel,
|
|
}: ThemeChipProps) {
|
|
const { colors, gradients, gradientDirection, borderRadius, typography } = useTheme();
|
|
|
|
const resolvedValue = value ?? label;
|
|
const isGradient = selected && selectedVariant === 'gradient';
|
|
const showSelectedIcon = selected && (showCross ?? showRemoveIcon);
|
|
|
|
const handlePress = useCallback(() => {
|
|
if (disabled) {
|
|
return;
|
|
}
|
|
|
|
onSelect?.(resolvedValue, !selected);
|
|
}, [disabled, onSelect, resolvedValue, selected]);
|
|
|
|
const containerStyle: ViewStyle = {
|
|
minHeight: CHIP_HEIGHT,
|
|
borderRadius: borderRadius.chip,
|
|
paddingHorizontal: 14,
|
|
borderWidth: 1,
|
|
alignSelf: 'flex-start',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 8,
|
|
opacity: disabled ? 0.45 : 1,
|
|
};
|
|
|
|
const accentSelectedStyle: ViewStyle = {
|
|
backgroundColor: colors.primarySoft,
|
|
borderColor: colors.primary,
|
|
};
|
|
|
|
const idleStyle: ViewStyle = {
|
|
backgroundColor: colors.surface,
|
|
borderColor: '#2A2A3C',
|
|
};
|
|
|
|
const textColor = isGradient
|
|
? colors.textPrimary
|
|
: selected
|
|
? colors.textOnPrimarySoft
|
|
: colors.textPrimary;
|
|
|
|
return (
|
|
<Pressable
|
|
testID={testID}
|
|
disabled={disabled}
|
|
onPress={handlePress}
|
|
accessibilityRole="button"
|
|
accessibilityState={{ disabled, selected }}
|
|
accessibilityLabel={accessibilityLabel ?? label}
|
|
style={[styles.root, style]}
|
|
>
|
|
{isGradient ? (
|
|
<LinearGradient
|
|
colors={gradients.signature}
|
|
start={gradientDirection.start}
|
|
end={gradientDirection.end}
|
|
style={[containerStyle, { borderColor: 'transparent' }]}
|
|
>
|
|
<Text style={[styles.label, typography.chip, { color: textColor }, textStyle]}>{label}</Text>
|
|
{showSelectedIcon ? <X color={textColor} size={12} strokeWidth={2.3} /> : null}
|
|
</LinearGradient>
|
|
) : (
|
|
<View style={[containerStyle, selected ? accentSelectedStyle : idleStyle]}>
|
|
<Text style={[styles.label, typography.chip, { color: textColor }, textStyle]}>{label}</Text>
|
|
{showSelectedIcon ? <X color={textColor} size={12} strokeWidth={2.3} /> : null}
|
|
</View>
|
|
)}
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
export default ThemeChip;
|
|
|
|
const styles = StyleSheet.create({
|
|
root: {
|
|
alignSelf: 'flex-start',
|
|
},
|
|
label: {
|
|
fontWeight: '600',
|
|
},
|
|
});
|