Files
OpenFlare/frontend/components/animate-ui/primitives/radix/collapsible.tsx
T
2026-07-13 15:44:03 +08:00

111 lines
3.1 KiB
TypeScript

'use client';
import * as React from 'react';
import { Collapsible as CollapsiblePrimitive } from 'radix-ui';
import { AnimatePresence, type HTMLMotionProps, motion } from 'motion/react';
import { getStrictContext } from '@/lib/utils/get-strict-context';
import { useControlledState } from '@/hooks/use-controlled-state';
type CollapsibleContextType = {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
};
const [CollapsibleProvider, useCollapsible] =
getStrictContext<CollapsibleContextType>('CollapsibleContext');
type CollapsibleProps = React.ComponentProps<typeof CollapsiblePrimitive.Root>;
function Collapsible(props: CollapsibleProps) {
const [isOpen, setIsOpen] = useControlledState({
value: props?.open,
defaultValue: props?.defaultOpen,
onChange: props?.onOpenChange,
});
return (
<CollapsibleProvider value={{ isOpen, setIsOpen }}>
<CollapsiblePrimitive.Root
data-slot='collapsible'
{...props}
onOpenChange={setIsOpen}
/>
</CollapsibleProvider>
);
}
type CollapsibleTriggerProps = React.ComponentProps<
typeof CollapsiblePrimitive.Trigger
>;
function CollapsibleTrigger(props: CollapsibleTriggerProps) {
return (
<CollapsiblePrimitive.Trigger data-slot='collapsible-trigger' {...props} />
);
}
type CollapsibleContentProps = Omit<
React.ComponentProps<typeof CollapsiblePrimitive.Content>,
'asChild' | 'forceMount'
> &
HTMLMotionProps<'div'> & {
keepRendered?: boolean;
};
function CollapsibleContent({
keepRendered = false,
transition = { duration: 0.35, ease: 'easeInOut' },
...props
}: CollapsibleContentProps) {
const { isOpen } = useCollapsible();
return (
<AnimatePresence>
{keepRendered ? (
<CollapsiblePrimitive.Content asChild forceMount>
<motion.div
key='collapsible-content'
data-slot='collapsible-content'
layout
initial={{ opacity: 0, height: 0, overflow: 'hidden', y: 20 }}
animate={
isOpen
? { opacity: 1, height: 'auto', overflow: 'hidden', y: 0 }
: { opacity: 0, height: 0, overflow: 'hidden', y: 20 }
}
transition={transition}
{...props}
/>
</CollapsiblePrimitive.Content>
) : (
isOpen && (
<CollapsiblePrimitive.Content asChild forceMount>
<motion.div
key='collapsible-content'
data-slot='collapsible-content'
layout
initial={{ opacity: 0, height: 0, overflow: 'hidden', y: 20 }}
animate={{ opacity: 1, height: 'auto', overflow: 'hidden', y: 0 }}
exit={{ opacity: 0, height: 0, overflow: 'hidden', y: 20 }}
transition={transition}
{...props}
/>
</CollapsiblePrimitive.Content>
)
)}
</AnimatePresence>
);
}
export {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
useCollapsible,
type CollapsibleProps,
type CollapsibleTriggerProps,
type CollapsibleContentProps,
type CollapsibleContextType,
};