@@ -13,8 +13,10 @@ import React, {
1313 useRef ,
1414 useCallback ,
1515 useLayoutEffect ,
16- RefObject ,
17- ReactNode ,
16+ type RefObject ,
17+ type Dispatch ,
18+ type SetStateAction ,
19+ type ReactNode ,
1820} from 'react' ;
1921
2022import { canUseDOM } from '@/utils/environment' ;
@@ -24,9 +26,18 @@ const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
2426const DefaultAnimationEasing = 'ease-in-out' ;
2527
2628/**
27- * This hook is a very thin wrapper around a `useState`.
28- */
29- export const useCollapsible = ( initialState : boolean | ( ( ) => boolean ) = false ) => {
29+ * This hook is a very thin wrapper around a `useState`.
30+ */
31+ export function useCollapsible ( {
32+ initialState,
33+ } : {
34+ /** The initial state. Will be non-collapsed by default. */
35+ initialState : boolean | ( ( ) => boolean ) ;
36+ } ) : {
37+ collapsed : boolean ;
38+ setCollapsed : Dispatch < SetStateAction < boolean > > ;
39+ toggleCollapsed : ( ) => void ;
40+ } {
3041 const [ collapsed , setCollapsed ] = useState ( initialState ?? false ) ;
3142
3243 const toggleCollapsed = useCallback ( ( ) => {
@@ -52,7 +63,7 @@ const ExpandedStyles = {
5263 height : 'auto' ,
5364} as const ;
5465
55- const applyCollapsedStyle = ( el : HTMLElement , collapsed : boolean ) => {
66+ function applyCollapsedStyle ( el : HTMLElement , collapsed : boolean ) {
5667 const collapsedStyles = collapsed ? CollapsedStyles : ExpandedStyles ;
5768 el . style . display = collapsedStyles . display ;
5869 el . style . overflow = collapsedStyles . overflow ;
@@ -64,8 +75,8 @@ Lex111: Dynamic transition duration is used in Material design, this technique
6475is good for a large number of items.
6576https://material.io/archive/guidelines/motion/duration-easing.html#duration-easing-dynamic-durations
6677https://github.com/mui-org/material-ui/blob/e724d98eba018e55e1a684236a2037e24bcf050c/packages/material-ui/src/styles/createTransitions.js#L40-L43
67- */
68- const getAutoHeightDuration = ( height : number ) => {
78+ */
79+ function getAutoHeightDuration ( height : number ) {
6980 const constant = height / 36 ;
7081 return Math . round ( ( 4 + 15 * constant ** 0.25 + constant / 5 ) * 10 ) ;
7182}
@@ -75,21 +86,21 @@ type CollapsibleAnimationConfig = {
7586 easing ?: string ;
7687} ;
7788
78- const useCollapseAnimation = ( {
89+ function useCollapseAnimation ( {
7990 collapsibleRef,
8091 collapsed,
8192 animation,
8293} : {
8394 collapsibleRef : RefObject < HTMLElement > ;
8495 collapsed : boolean ;
8596 animation ?: CollapsibleAnimationConfig ;
86- } ) => {
97+ } ) {
8798 const mounted = useRef ( false ) ;
8899
89100 useEffect ( ( ) => {
90101 const el = collapsibleRef . current ! ;
91102
92- const getTransitionStyles = ( ) => {
103+ function getTransitionStyles ( ) {
93104 const height = el . scrollHeight ;
94105 const duration = animation ?. duration ?? getAutoHeightDuration ( height ) ;
95106 const easing = animation ?. easing ?? DefaultAnimationEasing ;
@@ -99,7 +110,7 @@ const useCollapseAnimation = ({
99110 } ;
100111 }
101112
102- const applyTransitionStyles = ( ) => {
113+ function applyTransitionStyles ( ) {
103114 const transitionStyles = getTransitionStyles ( ) ;
104115 el . style . transition = transitionStyles . transition ;
105116 el . style . height = transitionStyles . height ;
@@ -114,7 +125,7 @@ const useCollapseAnimation = ({
114125
115126 el . style . willChange = 'height' ;
116127
117- const startAnimation = ( ) => {
128+ function startAnimation ( ) {
118129 const animationFrame = requestAnimationFrame ( ( ) => {
119130 // When collapsing
120131 if ( collapsed ) {
@@ -146,11 +157,11 @@ type CollapsibleElementType = React.ElementType<
146157> ;
147158
148159/**
149- * Prevent hydration layout shift before animations are handled imperatively
150- * with JS
151- */
152- const getSSRStyle = ( collapsed : boolean ) => {
153- if ( ! canUseDOM ) {
160+ * Prevent hydration layout shift before animations are handled imperatively
161+ * with JS
162+ */
163+ function getSSRStyle ( collapsed : boolean ) {
164+ if ( canUseDOM ) {
154165 return undefined ;
155166 }
156167 return collapsed ? CollapsedStyles : ExpandedStyles ;
@@ -165,32 +176,33 @@ type CollapsibleBaseProps = {
165176 /** Configuration of animation, like `duration` and `easing` */
166177 animation ?: CollapsibleAnimationConfig ;
167178 /**
168- * A callback fired when the collapse transition animation ends. Receives
169- * the **new** collapsed state: e.g. when
170- * expanding, `collapsed` will be `false`. You can use this for some "cleanup"
171- * like applying new styles when the container is fully expanded.
172- */
179+ * A callback fired when the collapse transition animation ends. Receives
180+ * the **new** collapsed state: e.g. when
181+ * expanding, `collapsed` will be `false`. You can use this for some "cleanup"
182+ * like applying new styles when the container is fully expanded.
183+ */
173184 onCollapseTransitionEnd ?: ( collapsed : boolean ) => void ;
174185 /** Class name for the underlying DOM element. */
175186 className ?: string ;
176187 /**
177- * This is mostly useful for details/summary component where ssrStyle is not
178- * needed (as details are hidden natively) and can mess up with the browser's
179- * native behavior when JS fails to load or is disabled
180- */
188+ * This is mostly useful for details/summary component where ssrStyle is not
189+ * needed (as details are hidden natively) and can mess up with the browser's
190+ * native behavior when JS fails to load or is disabled
191+ */
181192 disableSSRStyle ?: boolean ;
182193} ;
183194
184- const CollapsibleBase = ( {
195+ function CollapsibleBase ( {
185196 as : As = 'div' ,
186197 collapsed,
187198 children,
188199 animation,
189200 onCollapseTransitionEnd,
190201 className,
191202 disableSSRStyle,
192- } : CollapsibleBaseProps ) => {
203+ } : CollapsibleBaseProps ) {
193204 // any because TS is a pain for HTML element refs, see https://twitter.com/sebastienlorber/status/1412784677795110914
205+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
194206 const collapsibleRef = useRef < any > ( null ) ;
195207
196208 useCollapseAnimation ( { collapsibleRef, collapsed, animation} ) ;
@@ -215,17 +227,17 @@ const CollapsibleBase = ({
215227 ) ;
216228}
217229
218- const CollapsibleLazy = ( { collapsed, ...props } : CollapsibleBaseProps ) => {
230+ function CollapsibleLazy ( { collapsed, ...props } : CollapsibleBaseProps ) {
219231 const [ mounted , setMounted ] = useState ( ! collapsed ) ;
232+ // Updated in effect so that first expansion transition can work
233+ const [ lazyCollapsed , setLazyCollapsed ] = useState ( collapsed ) ;
220234
221235 useIsomorphicLayoutEffect ( ( ) => {
222236 if ( ! collapsed ) {
223237 setMounted ( true ) ;
224238 }
225239 } , [ collapsed ] ) ;
226240
227- // lazyCollapsed updated in effect so that first expansion transition can work
228- const [ lazyCollapsed , setLazyCollapsed ] = useState ( collapsed ) ;
229241 useIsomorphicLayoutEffect ( ( ) => {
230242 if ( mounted ) {
231243 setLazyCollapsed ( collapsed ) ;
@@ -239,22 +251,20 @@ const CollapsibleLazy = ({collapsed, ...props}: CollapsibleBaseProps) => {
239251
240252type CollapsibleProps = CollapsibleBaseProps & {
241253 /**
242- * Delay rendering of the content till first expansion. Marked as required to
243- * force us to think if content should be server-rendered or not. This has
244- * perf impact since it reduces html file sizes, but could undermine SEO.
245- * @see https://github.com/facebook/docusaurus/issues/4753
246- */
254+ * Delay rendering of the content till first expansion. Marked as required to
255+ * force us to think if content should be server-rendered or not. This has
256+ * perf impact since it reduces html file sizes, but could undermine SEO.
257+ * @see https://github.com/facebook/docusaurus/issues/4753
258+ */
247259 lazy : boolean ;
248260} ;
249261
250262/**
251- * A headless component providing smooth and uniform collapsing behavior. The
252- * component will be invisible (zero height) when collapsed. Doesn't provide
253- * interactivity by itself: collapse state is toggled through props.
254- */
255- const Collapsible = ( { lazy, ...props } : CollapsibleProps ) : JSX . Element => {
263+ * A headless component providing smooth and uniform collapsing behavior. The
264+ * component will be invisible (zero height) when collapsed. Doesn't provide
265+ * interactivity by itself: collapse state is toggled through props.
266+ */
267+ export function Collapsible ( { lazy, ...props } : CollapsibleProps ) : JSX . Element {
256268 const Comp = lazy ? CollapsibleLazy : CollapsibleBase ;
257269 return < Comp { ...props } /> ;
258- }
259-
260- export default Collapsible ;
270+ }
0 commit comments