Skip to content

Commit 8195359

Browse files
feat: implemented filter chip
1 parent 8050d48 commit 8195359

File tree

10 files changed

+156
-8
lines changed

10 files changed

+156
-8
lines changed

src/chips/assist-chip/AssistChip.component.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ export interface AssistChipProps<T extends IconProps> extends BaseChipProps {
1616
LeadingIcon?: React.FC<T>;
1717
leadingIconType?: LeadingIconType;
1818
leadingIconProps?: T;
19+
iconSize?: number;
1920
}
2021

2122
export const AssistChip = <T extends IconProps>({
2223
elevated = false,
2324
LeadingIcon,
2425
leadingIconType = LeadingIconType.COMMON,
2526
leadingIconProps = {} as T,
27+
iconSize = 18,
2628
style,
2729
labelStyle,
2830
disabled = false,
@@ -32,16 +34,17 @@ export const AssistChip = <T extends IconProps>({
3234
const colorStyles = useMemo(() => getColorStyles(elevated, disabled, theme), [disabled, elevated, theme]);
3335

3436
const leadingIconPropsMap: Record<LeadingIconType, IconProps> = {
35-
[LeadingIconType.COMMON]: {size: 18, color: colorStyles.icon.color},
36-
[LeadingIconType.FAVICON]: {size: 18},
37-
[LeadingIconType.BRANDED]: {size: 18, style: {opacity: 0.38}},
37+
[LeadingIconType.COMMON]: {size: iconSize, color: colorStyles.icon.color},
38+
[LeadingIconType.FAVICON]: {size: iconSize},
39+
[LeadingIconType.BRANDED]: {size: iconSize, style: {opacity: 0.38}},
3840
};
3941

4042
return (
4143
<BaseChip
4244
style={[elevated && !disabled ? styles.elevatedContainer : styles.outlinedContainer, colorStyles.container, style]}
4345
labelStyle={[colorStyles.label, labelStyle]}
4446
leadingIcon={LeadingIcon ? <LeadingIcon {...leadingIconPropsMap[leadingIconType]} {...leadingIconProps} /> : null}
47+
disabled={disabled}
4548
{...props}
4649
/>
4750
);

src/chips/base-chip/BaseChip.component.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ export interface BaseChipProps extends TouchableOpacityProps {
1212
labelStyle?: StyleProp<TextStyle>;
1313
}
1414

15-
export const BaseChip: React.FC<BaseChipProps> = ({label, leadingIcon, trailingIcon, style, ...props}) => {
15+
export const BaseChip: React.FC<BaseChipProps> = ({label, leadingIcon, trailingIcon, labelStyle, style, ...props}) => {
1616
const {labelLarge} = useTypography();
1717

1818
return (
19-
<TouchableOpacity style={[styles.container, style]} hitSlop={8} {...props} disabled>
19+
<TouchableOpacity style={[styles.container, style]} hitSlop={8} {...props}>
2020
{leadingIcon}
21-
<Text style={[[styles.label, labelLarge]]}>{label}</Text>
21+
<Text style={[[styles.label, labelLarge, labelStyle]]}>{label}</Text>
2222
{trailingIcon}
2323
</TouchableOpacity>
2424
);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, {useMemo} from 'react';
2+
3+
import {useTheme} from '../../theme/useTheme.hook';
4+
import {type IconProps} from '../../icons/icon-props';
5+
import {getDynamicStyles, styles} from './filter-chip.styles';
6+
import {BaseChip, type BaseChipProps} from '../base-chip/BaseChip.component';
7+
import {DoneIcon} from '../../icons';
8+
import {CircularActivityIndicator} from '../../activity-indicators/circular-activity-indicator/CircularActivityIndicator.component';
9+
import Animated, {FadeIn, FadeOut, LinearTransition} from 'react-native-reanimated';
10+
11+
export interface FilterChipProps<T extends IconProps> extends BaseChipProps {
12+
elevated?: boolean;
13+
selected?: boolean;
14+
loading?: boolean;
15+
LeadingIcon?: React.FC<T>;
16+
leadingIconProps?: T;
17+
iconSize?: number;
18+
activityIndicatorSize?: number;
19+
}
20+
21+
export const FilterChip = <T extends IconProps>({
22+
elevated = false,
23+
selected = false,
24+
loading = false,
25+
LeadingIcon,
26+
leadingIconProps = {} as T,
27+
iconSize = 18,
28+
activityIndicatorSize = 38,
29+
style,
30+
labelStyle,
31+
disabled = false,
32+
...props
33+
}: FilterChipProps<T>) => {
34+
const theme = useTheme();
35+
const dynamicStyles = useMemo(() => getDynamicStyles(selected, elevated, disabled, theme), [disabled, elevated, selected, theme]);
36+
37+
const renerCustomLeadingIcon = () =>
38+
LeadingIcon ? (
39+
<Animated.View entering={FadeIn} exiting={FadeOut}>
40+
<LeadingIcon size={iconSize} color={dynamicStyles.leadingIcon.color} {...leadingIconProps} />
41+
</Animated.View>
42+
) : null;
43+
44+
const renderLeadingIcon = () => (selected ? <DoneIcon size={iconSize} color={dynamicStyles.selectedIcon.color} /> : renerCustomLeadingIcon());
45+
46+
return (
47+
<Animated.View layout={LinearTransition}>
48+
<BaseChip
49+
style={[elevated && !disabled ? styles.elevatedContainer : styles.outlinedContainer, dynamicStyles.container, style]}
50+
labelStyle={[dynamicStyles.label, labelStyle]}
51+
leadingIcon={
52+
loading ? <CircularActivityIndicator size={activityIndicatorSize} style={{height: iconSize, width: iconSize}} /> : renderLeadingIcon()
53+
}
54+
disabled={disabled}
55+
{...props}
56+
/>
57+
</Animated.View>
58+
);
59+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {StyleSheet} from 'react-native';
2+
3+
import {type Theme} from '../../theme/theme.types';
4+
import {convertToRGBA} from '../../utils/convert-to-rgba';
5+
6+
export const styles = StyleSheet.create({
7+
outlinedContainer: {
8+
borderWidth: 1,
9+
},
10+
elevatedContainer: {
11+
shadowOffset: {
12+
width: 0,
13+
height: 1,
14+
},
15+
shadowOpacity: 0.15,
16+
shadowRadius: 3,
17+
elevation: 1,
18+
},
19+
});
20+
21+
export const getDynamicStyles = (selected: boolean, elevated: boolean, disabled: boolean, theme: Theme) => {
22+
const labelDisabledColor = convertToRGBA(theme.surface.text as string, 0.38);
23+
const containerDisabledColor = convertToRGBA(theme.surface.textVariant as string, 0.12);
24+
25+
const [disabledUnselectedBackgroundColorByType, disabledUnselectedBorderColorByType] = elevated
26+
? [containerDisabledColor, 'transparent']
27+
: ['transparent', containerDisabledColor];
28+
const enabledBackgroundColorByType = elevated ? theme.surfaceContainer.backgroundLow : 'transparent';
29+
30+
return StyleSheet.create(
31+
disabled
32+
? {
33+
label: {
34+
color: labelDisabledColor,
35+
},
36+
container: {
37+
borderColor: selected ? 'transparent' : disabledUnselectedBorderColorByType,
38+
backgroundColor: selected ? containerDisabledColor : disabledUnselectedBackgroundColorByType,
39+
},
40+
leadingIcon: {
41+
color: labelDisabledColor,
42+
},
43+
selectedIcon: {
44+
color: labelDisabledColor,
45+
},
46+
}
47+
: {
48+
label: {
49+
color: theme.surface.text,
50+
},
51+
container: {
52+
borderColor: theme.outline,
53+
borderWidth: Number(!selected && !elevated),
54+
backgroundColor: selected ? theme.secondaryContainer.background : enabledBackgroundColorByType,
55+
},
56+
leadingIcon: {
57+
color: theme.primary.background,
58+
},
59+
selectedIcon: {
60+
color: theme.secondaryContainer.text,
61+
},
62+
}
63+
);
64+
};

src/chips/input-chip/InputChip.component.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface InputChipProps<T extends IconProps> extends BaseChipProps {
1313
LeadingIcon?: React.FC<T>;
1414
leadingIconProps?: T;
1515
hasTrailingIcon?: boolean;
16+
iconSize?: number;
1617
}
1718

1819
export const InputChip = <T extends IconProps>({
@@ -23,6 +24,7 @@ export const InputChip = <T extends IconProps>({
2324
leadingIconProps = {} as T,
2425
style,
2526
labelStyle,
27+
iconSize = 18,
2628
...props
2729
}: InputChipProps<T>) => {
2830
const {surface, secondaryContainer, outline, primary} = useTheme();
@@ -50,8 +52,8 @@ export const InputChip = <T extends IconProps>({
5052
<BaseChip
5153
style={[dynamicStyles.container, imageUrl ? styles.chipWithAvatarContainer : [], style]}
5254
labelStyle={[dynamicStyles.label, labelStyle]}
53-
leadingIcon={LeadingIcon ? <LeadingIcon size={18} color={primary.background} {...leadingIconProps} /> : renderLeadingAvatar()}
54-
trailingIcon={hasTrailingIcon ? <CloseIcon size={18} color={onContainerColor} /> : null}
55+
leadingIcon={LeadingIcon ? <LeadingIcon size={iconSize} color={primary.background} {...leadingIconProps} /> : renderLeadingAvatar()}
56+
trailingIcon={hasTrailingIcon ? <CloseIcon size={iconSize} color={onContainerColor} /> : null}
5557
{...props}
5658
/>
5759
);

src/chips/suggestion-chip/SuggestionChip.component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const SuggestionChip = <T extends IconProps>({
3030
style={[elevated && !disabled ? styles.elevatedContainer : styles.outlinedContainer, dynamicStyles.container, style]}
3131
labelStyle={[dynamicStyles.label, labelStyle]}
3232
leadingIcon={LeadingIcon ? <LeadingIcon size={18} color={dynamicStyles.icon.color} {...leadingIconProps} /> : null}
33+
disabled={disabled}
3334
{...props}
3435
/>
3536
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import Svg, {Path} from 'react-native-svg';
3+
4+
import {path} from './path.json';
5+
import type {IconProps} from '../icon-props';
6+
7+
const DEFAULT_SIZE = 24;
8+
const DEFAULT_COLOR = '#000';
9+
10+
export const DoneIcon = ({color = DEFAULT_COLOR, size = DEFAULT_SIZE, ...props}: IconProps) => (
11+
<Svg viewBox="0 0 24 24" width={size} height={size} {...props}>
12+
<Path fill={color} d={path} />
13+
</Svg>
14+
);

src/icons/done-icon/path.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"path": "M9 16.17l-4.17-4.17-1.42 1.41 5.59 5.59 12-12-1.41-1.41-10.59 10.58z"
3+
}

src/icons/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export {DoneIcon} from './done-icon/DoneIcon.component';
12
export {EditIcon} from './edit-icon/EditIcon.component';
23
export {PlusIcon} from './plus-icon/PlusIcon.component';
34
export {TodayIcon} from './today-icon/TodayIcon.component';

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export {Badge, type BadgeProps} from './badge/Badge.component';
55
export {Divider, type DividerProps} from './divider/Divider.component';
66

77
export {InputChip, type InputChipProps} from './chips/input-chip/InputChip.component';
8+
export {FilterChip, type FilterChipProps} from './chips/filter-chip/FilterChip.component';
89
export {SuggestionChip, type SuggestionChipProps} from './chips/suggestion-chip/SuggestionChip.component';
910
export {AssistChip, type AssistChipProps, LeadingIconType} from './chips/assist-chip/AssistChip.component';
1011

0 commit comments

Comments
 (0)