file-tree-folder.jsx 3.5 KB

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