use-expand-collapse.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import { useRef, useState, useLayoutEffect } from 'react'
  2. import classNames from 'classnames'
  3. function useExpandCollapse({
  4. initiallyExpanded = false,
  5. collapsedSize = 0,
  6. dimension = 'height',
  7. classes = { container: '', containerCollapsed: '' },
  8. } = {}) {
  9. const ref = useRef<{ scrollHeight: number; scrollWidth: number }>()
  10. const [isExpanded, setIsExpanded] = useState(initiallyExpanded)
  11. const [sizing, setSizing] = useState<{
  12. size: number | null
  13. needsExpandCollapse: boolean | null
  14. }>({
  15. size: null,
  16. needsExpandCollapse: null,
  17. })
  18. useLayoutEffect(() => {
  19. const expandCollapseEl = ref.current
  20. if (expandCollapseEl) {
  21. const expandedSize =
  22. dimension === 'height'
  23. ? expandCollapseEl.scrollHeight
  24. : expandCollapseEl.scrollWidth
  25. const needsExpandCollapse = expandedSize > collapsedSize
  26. if (isExpanded) {
  27. setSizing({ size: expandedSize, needsExpandCollapse })
  28. } else {
  29. setSizing({
  30. size: needsExpandCollapse ? collapsedSize : expandedSize,
  31. needsExpandCollapse,
  32. })
  33. }
  34. }
  35. }, [isExpanded, collapsedSize, dimension])
  36. const expandableClasses = classNames(
  37. 'expand-collapse-container',
  38. classes.container,
  39. !isExpanded ? classes.containerCollapsed : null
  40. )
  41. function handleToggle() {
  42. setIsExpanded(!isExpanded)
  43. }
  44. return {
  45. isExpanded,
  46. needsExpandCollapse: sizing.needsExpandCollapse,
  47. expandableProps: {
  48. ref,
  49. style: {
  50. [dimension === 'height' ? 'height' : 'width']: `${sizing.size}px`,
  51. },
  52. className: expandableClasses,
  53. },
  54. toggleProps: {
  55. onClick: handleToggle,
  56. },
  57. }
  58. }
  59. export default useExpandCollapse