import React, { useState } from "react";
import type { LayoutChangeEvent } from "react-native";
import { TouchableOpacity, View } from "react-native";
import Reanimated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { EASE_CUBIC_IN_OUT } from "./constants";
import { useStyles } from "./Disclosure.style";
import { tokens as staticTokens } from "../utils/design";
import { Icon } from "../Icon";
import { useAtlantisTheme } from "../AtlantisThemeContext";
const ReanimatedView = Reanimated.View;
const ReanimatedScrollView = Reanimated.ScrollView;
interface DisclosureProps {
/**
* Specifies the main content of the disclosure component.
* It can be any React Node - simple text, JSX, or a complex React component.
*/
readonly content: React.ReactNode;
/**
* Defines the header of the disclosure component.
* Similar to `content`, it can be any React Node.
*/
readonly header: React.ReactNode;
/**
* A boolean that determines whether the disclosure component is in an open or closed state.
* If `open` is true, the disclosure is in an open state; if false, it's closed.
*/
readonly open: boolean;
/**
* A boolean that indicates whether the disclosure component is empty or not.
* If `isEmpty` is `true`, there is no content in the disclosure; if false, there is some content.
*/
readonly isEmpty: boolean;
/**
* An optional property that determines the duration of the opening and closing animation of the disclosure component.
* It's defined in milliseconds.
* @default tokens["timing-slowest"]
*/
readonly animationDuration?: number;
/**
* A function that is called whenever the disclosure component is toggled between its open and closed states.
*/
onToggle(): void;
}
export function Disclosure({
content,
header,
open,
onToggle,
isEmpty,
animationDuration = staticTokens["timing-slowest"],
}: DisclosureProps) {
const styles = useStyles();
return (
);
}
type DisclosureHeaderProps = Pick<
DisclosureProps,
"header" | "onToggle" | "isEmpty" | "open" | "animationDuration"
> & {
readonly styles: ReturnType;
};
function DisclosureHeader({
header,
onToggle,
isEmpty,
open,
animationDuration,
styles,
}: DisclosureHeaderProps) {
const rotateZ = useSharedValue(0);
rotateZ.value = withTiming(open ? 0 : -180, {
easing: Easing.bezier(...EASE_CUBIC_IN_OUT),
duration: animationDuration,
});
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ rotateZ: `${rotateZ.value}deg` }],
};
});
const { tokens } = useAtlantisTheme();
return (
{header}
{!isEmpty && (
)}
);
}
type DisclosureContentProps = Pick<
DisclosureProps,
"content" | "open" | "animationDuration"
> & {
readonly styles: ReturnType;
};
function DisclosureContent({
content,
open,
animationDuration,
styles,
}: DisclosureContentProps) {
const [maxHeight, setMaxHeight] = useState(0);
const height = useSharedValue(0);
const onContentLayoutChange = (event: LayoutChangeEvent) => {
const newHeight = event.nativeEvent.layout.height;
setMaxHeight(newHeight);
};
height.value = withTiming(open ? maxHeight : 0, {
duration: animationDuration,
easing: Easing.bezier(...EASE_CUBIC_IN_OUT),
});
const animatedStyle = useAnimatedStyle(() => {
return {
height: height.value,
};
}, []);
return (
{content}
);
}