file-tree-folder.jsx 2.7 KB

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