UNPKG

3.5 kBPlain TextView Raw
1import * as React from "react";
2import { createComponent } from "reakit-system/createComponent";
3import { createHook } from "reakit-system/createHook";
4import { useLiveRef } from "reakit-utils/useLiveRef";
5import { isSelfTarget } from "reakit-utils/isSelfTarget";
6import { RoleOptions, RoleHTMLProps, useRole } from "../Role/Role";
7import { DisclosureStateReturn } from "./DisclosureState";
8import { DISCLOSURE_CONTENT_KEYS } from "./__keys";
9
10export type DisclosureContentOptions = RoleOptions &
11 Pick<
12 Partial<DisclosureStateReturn>,
13 "baseId" | "visible" | "animating" | "animated" | "stopAnimation"
14 >;
15
16export type DisclosureContentHTMLProps = RoleHTMLProps;
17
18export type DisclosureContentProps = DisclosureContentOptions &
19 DisclosureContentHTMLProps;
20
21type TransitionState = "enter" | "leave" | null;
22
23export const useDisclosureContent = createHook<
24 DisclosureContentOptions,
25 DisclosureContentHTMLProps
26>({
27 name: "DisclosureContent",
28 compose: useRole,
29 keys: DISCLOSURE_CONTENT_KEYS,
30
31 useProps(
32 options,
33 {
34 onTransitionEnd: htmlOnTransitionEnd,
35 onAnimationEnd: htmlOnAnimationEnd,
36 style: htmlStyle,
37 ...htmlProps
38 }
39 ) {
40 const animating = options.animated && options.animating;
41 const [transition, setTransition] = React.useState<TransitionState>(null);
42 const hidden = !options.visible && !animating;
43 const style = hidden ? { display: "none", ...htmlStyle } : htmlStyle;
44 const onTransitionEndRef = useLiveRef(htmlOnTransitionEnd);
45 const onAnimationEndRef = useLiveRef(htmlOnAnimationEnd);
46 const raf = React.useRef(0);
47
48 React.useEffect(() => {
49 if (!options.animated) return undefined;
50 // Double RAF is needed so the browser has enough time to paint the
51 // default styles before processing the `data-enter` attribute. Otherwise
52 // it wouldn't be considered a transition.
53 // See https://github.com/reakit/reakit/issues/643
54 raf.current = window.requestAnimationFrame(() => {
55 raf.current = window.requestAnimationFrame(() => {
56 if (options.visible) {
57 setTransition("enter");
58 } else if (animating) {
59 setTransition("leave");
60 } else {
61 setTransition(null);
62 }
63 });
64 });
65 return () => window.cancelAnimationFrame(raf.current);
66 }, [options.animated, options.visible, animating]);
67
68 const onEnd = React.useCallback(
69 (event: React.SyntheticEvent) => {
70 if (!isSelfTarget(event)) return;
71 if (!animating) return;
72 // Ignores number animated
73 if (options.animated === true) {
74 options.stopAnimation?.();
75 }
76 },
77 [options.animated, animating, options.stopAnimation]
78 );
79
80 const onTransitionEnd = React.useCallback(
81 (event: React.TransitionEvent) => {
82 onTransitionEndRef.current?.(event);
83 onEnd(event);
84 },
85 [onEnd]
86 );
87
88 const onAnimationEnd = React.useCallback(
89 (event: React.AnimationEvent) => {
90 onAnimationEndRef.current?.(event);
91 onEnd(event);
92 },
93 [onEnd]
94 );
95
96 return {
97 id: options.baseId,
98 "data-enter": transition === "enter" ? "" : undefined,
99 "data-leave": transition === "leave" ? "" : undefined,
100 onTransitionEnd,
101 onAnimationEnd,
102 hidden,
103 style,
104 ...htmlProps,
105 };
106 },
107});
108
109export const DisclosureContent = createComponent({
110 as: "div",
111 useHook: useDisclosureContent,
112});