Amba-Agent-App/components/ui/accordion.tsx
2026-01-16 00:22:35 +03:00

122 lines
3.8 KiB
TypeScript

import * as AccordionPrimitive from '@rn-primitives/accordion';
import * as React from 'react';
import { Pressable } from 'react-native';
import Animated, {
Extrapolation,
FadeIn,
FadeOutUp,
LayoutAnimationConfig,
LinearTransition,
interpolate,
useAnimatedStyle,
useDerivedValue,
withTiming,
} from 'react-native-reanimated';
import { ChevronDown } from '~/components/ui/icons';
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>
<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 = 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'>
<AccordionPrimitive.Header className='flex'>
<AccordionPrimitive.Trigger ref={ref} {...props} asChild>
<Trigger
className={cn(
'flex flex-row items-center justify-between py-4 group',
className
)}
>
{typeof children === 'function' ? children({ pressed: false }) : 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'
)}
ref={ref}
{...props}
>
<InnerContent className={cn('pb-4', className)}>{children}</InnerContent>
</AccordionPrimitive.Content>
</TextClassContext.Provider>
);
});
function InnerContent({ children, className }: { children: React.ReactNode; className?: string }) {
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 };