file-tree-folder.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { useCallback, useEffect } from 'react'
  2. import classNames from 'classnames'
  3. import {
  4. useFileTreeSelectable,
  5. useSelectableEntity,
  6. } from '../contexts/file-tree-selectable'
  7. import { useDroppable } from '../contexts/file-tree-draggable'
  8. import FileTreeItemInner from './file-tree-item/file-tree-item-inner'
  9. import FileTreeFolderList from './file-tree-folder-list'
  10. import usePersistedState from '../../../shared/hooks/use-persisted-state'
  11. import { Folder } from '../../../../../types/folder'
  12. import { Doc } from '../../../../../types/doc'
  13. import { FileRef } from '../../../../../types/file-ref'
  14. import FileTreeFolderIcons from './file-tree-folder-icons'
  15. function FileTreeFolder({
  16. name,
  17. id,
  18. folders,
  19. docs,
  20. files,
  21. }: {
  22. name: string
  23. id: string
  24. folders: Folder[]
  25. docs: Doc[]
  26. files: FileRef[]
  27. }) {
  28. const { isSelected, props: selectableEntityProps } = useSelectableEntity(
  29. id,
  30. 'folder'
  31. )
  32. const { selectedEntityParentIds } = useFileTreeSelectable()
  33. const [expanded, setExpanded] = usePersistedState(
  34. `folder.${id}.expanded`,
  35. false
  36. )
  37. useEffect(() => {
  38. if (selectedEntityParentIds.has(id)) {
  39. setExpanded(true)
  40. }
  41. }, [id, selectedEntityParentIds, setExpanded])
  42. const handleExpandCollapseClick = useCallback(() => {
  43. setExpanded(expanded => !expanded)
  44. }, [setExpanded])
  45. const onClick = useCallback(() => {
  46. handleExpandCollapseClick()
  47. }, [handleExpandCollapseClick])
  48. const { isOver: isOverRoot, dropRef: dropRefRoot } = useDroppable(id)
  49. const { isOver: isOverList, dropRef: dropRefList } = useDroppable(id)
  50. return (
  51. <>
  52. <li
  53. // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
  54. role="treeitem"
  55. // aria-selected is provided in selectableEntityProps
  56. {...selectableEntityProps}
  57. aria-expanded={expanded}
  58. aria-label={name}
  59. ref={dropRefRoot}
  60. className={classNames(selectableEntityProps.className, {
  61. 'dnd-droppable-hover': isOverRoot || isOverList,
  62. })}
  63. translate="no"
  64. >
  65. <FileTreeItemInner
  66. id={id}
  67. name={name}
  68. type="folder"
  69. isSelected={isSelected}
  70. onClick={onClick}
  71. icons={<FileTreeFolderIcons expanded={expanded} />}
  72. />
  73. </li>
  74. {expanded ? (
  75. <FileTreeFolderList
  76. folders={folders}
  77. docs={docs}
  78. files={files}
  79. dropRef={dropRefList}
  80. />
  81. ) : null}
  82. </>
  83. )
  84. }
  85. export default FileTreeFolder