import type { AnimatePresenceProps } from '@tamagui/animate-presence'
import { AnimatePresence, ResetPresence } from '@tamagui/animate-presence'
import { composeEventHandlers, withStaticProperties } from '@tamagui/helpers'
import type { YStackProps } from '@tamagui/stacks'
import { useControllableState } from '@tamagui/use-controllable-state'
import type { GetProps, ViewProps, TamaguiElement } from '@tamagui/web'
import { View, createStyledContext, styled } from '@tamagui/web'
import * as React from 'react'
/* -------------------------------------------------------------------------------------------------
* Collapsible
* -----------------------------------------------------------------------------------------------*/
const COLLAPSIBLE_NAME = 'Collapsible'
type ScopedProps
= P & { __scopeCollapsible?: string }
type CollapsibleContextValue = {
contentId: string
disabled?: boolean
open: boolean
onOpenToggle(): void
}
const { Provider: CollapsibleProvider, useStyledContext: useCollapsibleContext } =
createStyledContext()
interface CollapsibleProps extends ViewProps {
defaultOpen?: boolean
open?: boolean
disabled?: boolean
onOpenChange?(open: boolean): void
}
const _Collapsible = React.forwardRef>(
(props, forwardedRef) => {
const {
__scopeCollapsible,
open: openProp,
defaultOpen,
disabled,
onOpenChange,
...collapsibleProps
} = props
const [open = false, setOpen] = useControllableState({
prop: openProp,
defaultProp: defaultOpen!,
onChange: onOpenChange,
})
return (
setOpen((prevOpen) => !prevOpen),
[setOpen]
)}
>
)
}
)
_Collapsible.displayName = COLLAPSIBLE_NAME
/* -------------------------------------------------------------------------------------------------
* CollapsibleTrigger
* -----------------------------------------------------------------------------------------------*/
const TRIGGER_NAME = 'CollapsibleTrigger'
type CollapsibleTriggerProps = GetProps
const CollapsibleTriggerFrame = styled(View, {
name: TRIGGER_NAME,
render: 'button',
})
const CollapsibleTrigger = CollapsibleTriggerFrame.styleable(
(props: ScopedProps, forwardedRef) => {
const { __scopeCollapsible, children, ...triggerProps } = props
const context = useCollapsibleContext(__scopeCollapsible)
return (
{typeof children === 'function' ? children({ open: context.open }) : children}
)
}
)
CollapsibleTrigger.displayName = TRIGGER_NAME
/* -------------------------------------------------------------------------------------------------
* CollapsibleContent
* -----------------------------------------------------------------------------------------------*/
export interface CollapsibleContentExtraProps extends AnimatePresenceProps {
/**
* Used to force mounting when more control is needed. Useful when
* controlling animation with React animation libraries.
*/
forceMount?: boolean
}
interface CollapsibleContentProps extends CollapsibleContentExtraProps, YStackProps {}
const CONTENT_NAME = 'CollapsibleContent'
const CollapsibleContentFrame = styled(View, {
name: CONTENT_NAME,
})
const CollapsibleContent =
CollapsibleContentFrame.styleable(
(props, forwardedRef) => {
const {
forceMount,
children,
// @ts-expect-error
__scopeCollapsible,
...contentProps
} = props
const context = useCollapsibleContext(__scopeCollapsible)
return (
{forceMount || context.open ? (
{children}
) : null}
)
}
)
CollapsibleContent.displayName = CONTENT_NAME
/* -----------------------------------------------------------------------------------------------*/
function getState(open?: boolean) {
return open ? 'open' : 'closed'
}
const Collapsible = withStaticProperties(_Collapsible, {
Trigger: CollapsibleTrigger,
Content: CollapsibleContent,
})
export {
Collapsible,
CollapsibleContent,
CollapsibleContentFrame,
CollapsibleTrigger,
CollapsibleTriggerFrame,
}
export type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerProps }