UNPKG

@mantine/hooks

Version:

A collection of 50+ hooks for state and UI management

1 lines 7.14 kB
{"version":3,"file":"use-horizontal-collapse.mjs","names":[],"sources":["../../src/use-collapse/use-horizontal-collapse.ts"],"sourcesContent":["import React, { CSSProperties, useEffectEvent, useRef, useState } from 'react';\nimport { flushSync } from 'react-dom';\nimport { useDidUpdate } from '../use-did-update/use-did-update';\nimport { mergeRefs } from '../use-merged-ref/use-merged-ref';\n\nfunction getAutoWidthDuration(width: number | string) {\n if (!width || typeof width === 'string') {\n return 0;\n }\n const constant = width / 36;\n return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10);\n}\n\nexport function getElementWidth(elementRef: React.RefObject<HTMLElement | null>) {\n return elementRef.current ? elementRef.current.scrollWidth : 'auto';\n}\n\nexport interface UseHorizontalCollapseInput {\n /** Expanded state */\n expanded: boolean;\n\n /** Transition duration in milliseconds, by default calculated based on content width */\n transitionDuration?: number;\n\n /** Transition timing function, `ease` by default */\n transitionTimingFunction?: string;\n\n /** Called when transition ends */\n onTransitionEnd?: () => void;\n\n /** Called when transition starts */\n onTransitionStart?: () => void;\n\n /** If true, collapsed content is kept in the DOM and hidden with `display: none` styles */\n keepMounted?: boolean;\n}\n\ninterface GetHorizontalCollapsePropsInput {\n style?: CSSProperties;\n ref?: React.Ref<HTMLDivElement>;\n}\n\ninterface GetHorizontalCollapsePropsReturnValue {\n 'aria-hidden': boolean;\n inert: boolean;\n ref: React.RefCallback<HTMLDivElement>;\n onTransitionEnd: (event: React.TransitionEvent<Element>) => void;\n style: React.CSSProperties;\n}\n\nexport type UseHorizontalCollapseState = 'entering' | 'entered' | 'exiting' | 'exited';\n\nexport interface UseHorizontalCollapseReturnValue {\n /** Current transition state */\n state: UseHorizontalCollapseState;\n\n /** Props to pass down to the collapsible element */\n getCollapseProps: (\n input?: GetHorizontalCollapsePropsInput\n ) => GetHorizontalCollapsePropsReturnValue;\n}\n\nexport function useHorizontalCollapse({\n transitionDuration,\n transitionTimingFunction = 'ease',\n onTransitionEnd,\n onTransitionStart,\n expanded,\n keepMounted,\n}: UseHorizontalCollapseInput): UseHorizontalCollapseReturnValue {\n const collapsedStyles = {\n width: 0,\n overflow: 'hidden',\n ...(keepMounted ? {} : { display: 'none' }),\n };\n\n const onTransitionStartEvent = useEffectEvent(() => onTransitionStart?.());\n\n const elementRef = useRef<HTMLElement>(null);\n const [styles, setStylesRaw] = useState<CSSProperties>(expanded ? {} : collapsedStyles);\n const [state, setState] = useState<UseHorizontalCollapseState>(expanded ? 'entered' : 'exited');\n const setStyles = (newStyles: React.SetStateAction<CSSProperties>) => {\n flushSync(() => setStylesRaw(newStyles));\n };\n\n const mergeStyles = (newStyles: CSSProperties) => {\n setStyles((oldStyles) => ({ ...oldStyles, ...newStyles }));\n };\n\n const getTransitionStyles = (width: number | string) => {\n const duration = transitionDuration ?? getAutoWidthDuration(width);\n return {\n transition: `width ${duration}ms ${transitionTimingFunction}, opacity ${duration}ms ${transitionTimingFunction}`,\n };\n };\n\n useDidUpdate(() => {\n const shouldTransition = transitionDuration !== 0;\n\n if (shouldTransition) {\n onTransitionStartEvent();\n }\n\n if (expanded) {\n window.requestAnimationFrame(() => {\n flushSync(() => setState('entering'));\n mergeStyles({ willChange: 'width', display: 'block', overflow: 'hidden' });\n window.requestAnimationFrame(() => {\n const width = getElementWidth(elementRef);\n mergeStyles({ ...getTransitionStyles(width), width });\n });\n });\n } else {\n window.requestAnimationFrame(() => {\n flushSync(() => setState('exiting'));\n const width = getElementWidth(elementRef);\n mergeStyles({ ...getTransitionStyles(width), willChange: 'width', width });\n window.requestAnimationFrame(() => mergeStyles({ width: 0, overflow: 'hidden' }));\n });\n }\n }, [expanded]);\n\n const handleTransitionEnd = (event: React.TransitionEvent): void => {\n if (event.target !== elementRef.current || event.propertyName !== 'width') {\n return;\n }\n\n if (expanded) {\n const width = getElementWidth(elementRef);\n\n if (width === styles.width) {\n setStyles({});\n } else {\n mergeStyles({ width });\n }\n\n setState('entered');\n onTransitionEnd?.();\n } else if (styles.width === 0) {\n setStyles(collapsedStyles);\n setState('exited');\n onTransitionEnd?.();\n }\n };\n\n return {\n state,\n getCollapseProps: (input) => ({\n 'aria-hidden': !expanded,\n inert: !expanded,\n ref: mergeRefs(elementRef, input?.ref),\n onTransitionEnd: handleTransitionEnd,\n style: { boxSizing: 'border-box', ...input?.style, ...styles },\n }),\n };\n}\n\nexport namespace useHorizontalCollapse {\n export type Input = UseHorizontalCollapseInput;\n export type ReturnValue = UseHorizontalCollapseReturnValue;\n export type State = UseHorizontalCollapseState;\n}\n"],"mappings":";;;;;;AAKA,SAAS,qBAAqB,OAAwB;CACpD,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAET,MAAM,WAAW,QAAQ;CACzB,OAAO,KAAK,OAAO,IAAI,KAAK,YAAY,MAAO,WAAW,KAAK,EAAE;AACnE;AAEA,SAAgB,gBAAgB,YAAiD;CAC/E,OAAO,WAAW,UAAU,WAAW,QAAQ,cAAc;AAC/D;AA+CA,SAAgB,sBAAsB,EACpC,oBACA,2BAA2B,QAC3B,iBACA,mBACA,UACA,eAC+D;CAC/D,MAAM,kBAAkB;EACtB,OAAO;EACP,UAAU;EACV,GAAI,cAAc,CAAC,IAAI,EAAE,SAAS,OAAO;CAC3C;CAEA,MAAM,yBAAyB,qBAAqB,oBAAoB,CAAC;CAEzE,MAAM,aAAa,OAAoB,IAAI;CAC3C,MAAM,CAAC,QAAQ,gBAAgB,SAAwB,WAAW,CAAC,IAAI,eAAe;CACtF,MAAM,CAAC,OAAO,YAAY,SAAqC,WAAW,YAAY,QAAQ;CAC9F,MAAM,aAAa,cAAmD;EACpE,gBAAgB,aAAa,SAAS,CAAC;CACzC;CAEA,MAAM,eAAe,cAA6B;EAChD,WAAW,eAAe;GAAE,GAAG;GAAW,GAAG;EAAU,EAAE;CAC3D;CAEA,MAAM,uBAAuB,UAA2B;EACtD,MAAM,WAAW,sBAAsB,qBAAqB,KAAK;EACjE,OAAO,EACL,YAAY,SAAS,SAAS,KAAK,yBAAyB,YAAY,SAAS,KAAK,2BACxF;CACF;CAEA,mBAAmB;EAGjB,IAFyB,uBAAuB,GAG9C,uBAAuB;EAGzB,IAAI,UACF,OAAO,4BAA4B;GACjC,gBAAgB,SAAS,UAAU,CAAC;GACpC,YAAY;IAAE,YAAY;IAAS,SAAS;IAAS,UAAU;GAAS,CAAC;GACzE,OAAO,4BAA4B;IACjC,MAAM,QAAQ,gBAAgB,UAAU;IACxC,YAAY;KAAE,GAAG,oBAAoB,KAAK;KAAG;IAAM,CAAC;GACtD,CAAC;EACH,CAAC;OAED,OAAO,4BAA4B;GACjC,gBAAgB,SAAS,SAAS,CAAC;GACnC,MAAM,QAAQ,gBAAgB,UAAU;GACxC,YAAY;IAAE,GAAG,oBAAoB,KAAK;IAAG,YAAY;IAAS;GAAM,CAAC;GACzE,OAAO,4BAA4B,YAAY;IAAE,OAAO;IAAG,UAAU;GAAS,CAAC,CAAC;EAClF,CAAC;CAEL,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,uBAAuB,UAAuC;EAClE,IAAI,MAAM,WAAW,WAAW,WAAW,MAAM,iBAAiB,SAChE;EAGF,IAAI,UAAU;GACZ,MAAM,QAAQ,gBAAgB,UAAU;GAExC,IAAI,UAAU,OAAO,OACnB,UAAU,CAAC,CAAC;QAEZ,YAAY,EAAE,MAAM,CAAC;GAGvB,SAAS,SAAS;GAClB,kBAAkB;EACpB,OAAO,IAAI,OAAO,UAAU,GAAG;GAC7B,UAAU,eAAe;GACzB,SAAS,QAAQ;GACjB,kBAAkB;EACpB;CACF;CAEA,OAAO;EACL;EACA,mBAAmB,WAAW;GAC5B,eAAe,CAAC;GAChB,OAAO,CAAC;GACR,KAAK,UAAU,YAAY,OAAO,GAAG;GACrC,iBAAiB;GACjB,OAAO;IAAE,WAAW;IAAc,GAAG,OAAO;IAAO,GAAG;GAAO;EAC/D;CACF;AACF"}