Accordion
Accordion Primitive
Text Component
Demo
A vertically stacked set of interactive headings that each reveal a section of content.
Installation
npx @react-native-reusables/cli@latest add accordion
Copy/paste the following code to ~/components/ui/accordion.tsx
:
import * as AccordionPrimitive from '@rn-primitives/accordion';import * as React from 'react';import { Platform, Pressable, View } from 'react-native';import Animated, { Extrapolation, FadeIn, FadeOutUp, LayoutAnimationConfig, LinearTransition, interpolate, useAnimatedStyle, useDerivedValue, withTiming,} from 'react-native-reanimated';import { ChevronDown } from '~/lib/icons/ChevronDown';import { cn } from '~/lib/utils';import { TextClassContext } from '~/components/ui/text';
const Accordion = React.forwardRef<AccordionPrimitive.RootRef, AccordionPrimitive.RootProps>( ({ children, ...props }, ref) => { return ( <LayoutAnimationConfig skipEntering> <AccordionPrimitive.Root ref={ref} {...props} asChild={Platform.OS !== 'web'}> <Animated.View layout={LinearTransition.duration(200)}>{children}</Animated.View> </AccordionPrimitive.Root> </LayoutAnimationConfig> ); });
Accordion.displayName = AccordionPrimitive.Root.displayName;
const AccordionItem = React.forwardRef<AccordionPrimitive.ItemRef, AccordionPrimitive.ItemProps>( ({ className, value, ...props }, ref) => { return ( <Animated.View className={'overflow-hidden'} layout={LinearTransition.duration(200)}> <AccordionPrimitive.Item ref={ref} className={cn('border-b border-border', className)} value={value} {...props} /> </Animated.View> ); });AccordionItem.displayName = AccordionPrimitive.Item.displayName;
const Trigger = Platform.OS === 'web' ? View : Pressable;
const AccordionTrigger = React.forwardRef< AccordionPrimitive.TriggerRef, AccordionPrimitive.TriggerProps>(({ className, children, ...props }, ref) => { const { isExpanded } = AccordionPrimitive.useItemContext();
const progress = useDerivedValue(() => isExpanded ? withTiming(1, { duration: 250 }) : withTiming(0, { duration: 200 }) ); const chevronStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${progress.value * 180}deg` }], opacity: interpolate(progress.value, [0, 1], [1, 0.8], Extrapolation.CLAMP), }));
return ( <TextClassContext.Provider value='native:text-lg font-medium web:group-hover:underline'> <AccordionPrimitive.Header className='flex'> <AccordionPrimitive.Trigger ref={ref} {...props} asChild> <Trigger className={cn( 'flex flex-row web:flex-1 items-center justify-between py-4 web:transition-all group web:focus-visible:outline-none web:focus-visible:ring-1 web:focus-visible:ring-muted-foreground', className )} > <>{children}</> <Animated.View style={chevronStyle}> <ChevronDown size={18} className={'text-foreground shrink-0'} /> </Animated.View> </Trigger> </AccordionPrimitive.Trigger> </AccordionPrimitive.Header> </TextClassContext.Provider> );});AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef< AccordionPrimitive.ContentRef, AccordionPrimitive.ContentProps>(({ className, children, ...props }, ref) => { const { isExpanded } = AccordionPrimitive.useItemContext(); return ( <TextClassContext.Provider value='native:text-lg'> <AccordionPrimitive.Content className={cn( 'overflow-hidden text-sm web:transition-all', isExpanded ? 'web:animate-accordion-down' : 'web:animate-accordion-up' )} ref={ref} {...props} > <InnerContent className={cn('pb-4', className)}>{children}</InnerContent> </AccordionPrimitive.Content> </TextClassContext.Provider> );});
function InnerContent({ children, className }: { children: React.ReactNode; className?: string }) { if (Platform.OS === 'web') { return <View className={cn('pb-4', className)}>{children}</View>; } return ( <Animated.View entering={FadeIn} exiting={FadeOutUp.duration(200)} className={cn('pb-4', className)} > {children} </Animated.View> );}
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
Usage
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from '~/components/ui/accordion';import { Text } from '~/components/ui/text';
function Example() { return ( <Accordion type='multiple' collapsible defaultValue={['item-1']} className='w-full max-w-sm native:max-w-md' > <AccordionItem value='item-1'> <AccordionTrigger> <Text>Is it accessible?</Text> </AccordionTrigger> <AccordionContent> <Text>Yes. It adheres to the WAI-ARIA design pattern.</Text> </AccordionContent> </AccordionItem> <AccordionItem value='item-2'> <AccordionTrigger> <Text>What are universal components?</Text> </AccordionTrigger> <AccordionContent> <Text> In the world of React Native, universal components are components that work on both web and native platforms. </Text> </AccordionContent> </AccordionItem> </Accordion> );}
Props
Accordion
Extends View
props
Prop | Type | Note |
---|---|---|
type* | ‘single’ | ‘multiple’ | |
asChild | boolean | (optional) |
defaultValue | (string | undefined) | string[] | (optional) |
value | (string | undefined) | string[] | (optional) |
onValueChange | ((string | undefined) => void) | ((string[]) => void) | (optional) |
dir | ’ltr’ | ‘rtl’ | Web only (optional) |
orientation | ’vertical’ | ‘horizontal’ | Web only (optional) |
AccordionItem
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
value | string | (optional) |
disabled | boolean | (optional) |
AccordionTrigger
Extends Pressable
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
AccordionContent
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
forceMount | true | undefined | (optional) |