file-tree-folder.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { useEffect } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import classNames from 'classnames'
  4. import Icon from '../../../shared/components/icon'
  5. import {
  6. useFileTreeSelectable,
  7. useSelectableEntity,
  8. } from '../contexts/file-tree-selectable'
  9. import { useDroppable } from '../contexts/file-tree-draggable'
  10. import FileTreeItemInner from './file-tree-item/file-tree-item-inner'
  11. import FileTreeFolderList from './file-tree-folder-list'
  12. import usePersistedState from '../../../shared/hooks/use-persisted-state'
  13. import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
  14. import MaterialIcon from '@/shared/components/material-icon'
  15. import { Folder } from '../../../../../types/folder'
  16. import { Doc } from '../../../../../types/doc'
  17. import { FileRef } from '../../../../../types/file-ref'
  18. function FileTreeFolder({
  19. name,
  20. id,
  21. folders,
  22. docs,
  23. files,
  24. }: {
  25. name: string
  26. id: string
  27. folders: Folder[]
  28. docs: Doc[]
  29. files: FileRef[]
  30. }) {
  31. const { t } = useTranslation()
  32. const { isSelected, props: selectableEntityProps } = useSelectableEntity(
  33. id,
  34. 'folder'
  35. )
  36. const { selectedEntityParentIds } = useFileTreeSelectable()
  37. const [expanded, setExpanded] = usePersistedState(
  38. `folder.${id}.expanded`,
  39. false
  40. )
  41. useEffect(() => {
  42. if (selectedEntityParentIds.has(id)) {
  43. setExpanded(true)
  44. }
  45. }, [id, selectedEntityParentIds, setExpanded])
  46. function handleExpandCollapseClick() {
  47. setExpanded(!expanded)
  48. }
  49. const { isOver: isOverRoot, dropRef: dropRefRoot } = useDroppable(id)
  50. const { isOver: isOverList, dropRef: dropRefList } = useDroppable(id)
  51. const icons = (
  52. <BootstrapVersionSwitcher
  53. bs3={
  54. <>
  55. <button
  56. onClick={handleExpandCollapseClick}
  57. aria-label={expanded ? t('collapse') : t('expand')}
  58. >
  59. <Icon
  60. type={expanded ? 'angle-down' : 'angle-right'}
  61. fw
  62. className="file-tree-expand-icon"
  63. />
  64. </button>
  65. <Icon
  66. type={expanded ? 'folder-open' : 'folder'}
  67. fw
  68. className="file-tree-folder-icon"
  69. />
  70. </>
  71. }
  72. bs5={
  73. <>
  74. <button
  75. onClick={handleExpandCollapseClick}
  76. aria-label={expanded ? t('collapse') : t('expand')}
  77. >
  78. <MaterialIcon
  79. type={expanded ? 'expand_more' : 'chevron_right'}
  80. className="file-tree-expand-icon"
  81. />
  82. </button>
  83. <MaterialIcon
  84. type={expanded ? 'folder_open' : 'folder'}
  85. className="file-tree-folder-icon"
  86. />
  87. </>
  88. }
  89. />
  90. )
  91. return (
  92. <>
  93. <li
  94. // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
  95. role="treeitem"
  96. // aria-selected is provided in selectableEntityProps
  97. {...selectableEntityProps}
  98. aria-expanded={expanded}
  99. aria-label={name}
  100. tabIndex={0}
  101. ref={dropRefRoot}
  102. className={classNames(selectableEntityProps.className, {
  103. 'dnd-droppable-hover': isOverRoot || isOverList,
  104. })}
  105. >
  106. <FileTreeItemInner
  107. id={id}
  108. name={name}
  109. type="folder"
  110. isSelected={isSelected}
  111. icons={icons}
  112. />
  113. </li>
  114. {expanded ? (
  115. <FileTreeFolderList
  116. folders={folders}
  117. docs={docs}
  118. files={files}
  119. dropRef={dropRefList}
  120. />
  121. ) : null}
  122. </>
  123. )
  124. }
  125. export default FileTreeFolder