file-tree-folder.jsx 2.7 KB

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