file-tree-folder.js 2.9 KB

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