Animated Chip Group
An animated chip selector where items expand on selection
Last updated on
Manual
Install the following dependencies:
npm install react-native-reanimatedCopy and paste the following code into your project.
component/molecules/Chip.tsx
import { SFSymbol, SymbolView } from "expo-symbols";import React, { useState } from "react";import { StyleSheet } from "react-native";import Animated, { LinearTransition } from "react-native-reanimated";import type { ChipGroupProps, ChipItem } from "./Chip.types";import { AnimatedChip } from "./AnimatedChip";export const ChipGroup: React.FC<ChipGroupProps<ChipItem>> = ({ chips, onChange, containerStyle, selectedIndex,}) => { const [internalIndex, setInternalIndex] = useState(0); const activeIndex = selectedIndex ?? internalIndex; const handlePress = (index: number) => { if (selectedIndex === undefined) { setInternalIndex(index); } onChange?.(index); }; return ( <Animated.View style={[styles.container, containerStyle]} layout={LinearTransition} > {chips.map((item, index) => ( <AnimatedChip key={index} label={item.label} inActiveBackgroundColor={item.inActiveBackgroundColor} activeColor={item.activeColor} icon={item.icon} labelColor={item.labelColor} isActive={activeIndex === index} onPress={() => handlePress(index)} /> ))} </Animated.View> );};const styles = StyleSheet.create({ container: { flexDirection: "row", gap: 8, },});Usage
import { View, Text, StyleSheet } from "react-native";import { GestureHandlerRootView } from "react-native-gesture-handler";import { StatusBar } from "expo-status-bar";import { useFonts } from "expo-font";import { SymbolView } from "expo-symbols";import { ChipGroup } from "@/components";import { useState } from "react";export default function App() { const [fontLoaded] = useFonts({ SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"), HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"), }); const [selected, setSelected] = useState(0); const chips = [ { label: "All", activeColor: "#fff", inActiveBackgroundColor: "#1a1a1a", labelColor: "#000", icon: () => ( <SymbolView resizeMode="scaleAspectFit" name="square.grid.2x2.fill" size={18} tintColor={selected === 0 ? "#000" : "#555"} /> ), }, { label: "Music", activeColor: "#ff375f", inActiveBackgroundColor: "#1a1a1a", labelColor: "#fff", icon: () => ( <SymbolView resizeMode="scaleAspectFit" name="music.note" size={18} tintColor={selected === 1 ? "#fff" : "#555"} /> ), }, { label: "Videos", activeColor: "#5e5ce6", inActiveBackgroundColor: "#1a1a1a", labelColor: "#fff", icon: () => ( <SymbolView name="play.fill" size={18} tintColor={selected === 2 ? "#fff" : "#555"} resizeMode="scaleAspectFit" /> ), }, ]; return ( <GestureHandlerRootView style={styles.container}> <StatusBar style="light" /> <View style={styles.content}> <Text style={[ styles.title, fontLoaded && { fontFamily: "HelveticaNowDisplay" }, ]} > Media </Text> <Text style={[ styles.subtitle, fontLoaded && { fontFamily: "SfProRounded" }, ]} > Browse your collection </Text> <View style={styles.chipWrapper}> <ChipGroup chips={chips} selectedIndex={selected} onChange={setSelected} /> </View> </View> </GestureHandlerRootView> );}const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#0a0a0a", }, content: { paddingHorizontal: 20, paddingTop: 100, gap: 8, }, title: { fontSize: 36, fontWeight: "700", color: "#fff", }, subtitle: { fontSize: 15, color: "#444", }, chipWrapper: { marginTop: 24, },});Props
ChipItem
React Native Reanimated
