menu-bar-dropdown.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {
  2. Dropdown,
  3. DropdownMenu,
  4. DropdownToggle,
  5. } from '@/shared/components/dropdown/dropdown-menu'
  6. import { FC, forwardRef, useCallback } from 'react'
  7. import classNames from 'classnames'
  8. import { useNestableDropdown } from '@/shared/hooks/use-nestable-dropdown'
  9. import { NestableDropdownContextProvider } from '@/shared/context/nestable-dropdown-context'
  10. import { AnchorProps } from 'react-bootstrap'
  11. import MaterialIcon from '../material-icon'
  12. import { DropdownMenuProps } from '@/shared/components/types/dropdown-menu-props'
  13. type MenuBarDropdownProps = {
  14. title: string
  15. id: string
  16. className?: string
  17. align?: 'start' | 'end'
  18. }
  19. export const MenuBarDropdown: FC<
  20. React.PropsWithChildren<MenuBarDropdownProps>
  21. > = ({ title, children, id, className, align = 'start' }) => {
  22. const { menuId, selected, setSelected } = useNestableDropdown()
  23. const onToggle = useCallback(
  24. (show: boolean) => {
  25. setSelected(show ? id : null)
  26. },
  27. [id, setSelected]
  28. )
  29. const onHover = useCallback(() => {
  30. setSelected(prev => {
  31. if (prev === null) {
  32. return null
  33. }
  34. return id
  35. })
  36. }, [id, setSelected])
  37. const active = selected === id
  38. return (
  39. <Dropdown show={active} align={align} onToggle={onToggle} autoClose>
  40. <DropdownToggle
  41. id={`${menuId}-${id}`}
  42. variant="secondary"
  43. className={classNames(className, 'menu-bar-toggle')}
  44. onMouseEnter={onHover}
  45. >
  46. {title}
  47. </DropdownToggle>
  48. {active && (
  49. <NestableDropdownMenu renderOnMount id={`${menuId}-${id}`}>
  50. {children}
  51. </NestableDropdownMenu>
  52. )}
  53. </Dropdown>
  54. )
  55. }
  56. const NestableDropdownMenu: FC<
  57. React.PropsWithChildren<DropdownMenuProps & { id: string }>
  58. > = ({ children, id, ...props }) => {
  59. return (
  60. <DropdownMenu {...props}>
  61. <NestableDropdownContextProvider id={id}>
  62. {children}
  63. </NestableDropdownContextProvider>
  64. </DropdownMenu>
  65. )
  66. }
  67. const NestedDropdownToggle: FC<React.PropsWithChildren> = forwardRef<
  68. HTMLAnchorElement,
  69. AnchorProps
  70. >(function NestedDropdownToggle(
  71. { children, className, onMouseEnter, id },
  72. ref
  73. ) {
  74. return (
  75. // eslint-disable-next-line jsx-a11y/anchor-is-valid
  76. <a
  77. id={id}
  78. href="#"
  79. ref={ref}
  80. onMouseEnter={onMouseEnter}
  81. onClick={onMouseEnter}
  82. className={classNames(
  83. className,
  84. 'nested-dropdown-toggle',
  85. 'dropdown-item'
  86. )}
  87. role="menuitem"
  88. aria-haspopup
  89. >
  90. {children}
  91. <MaterialIcon type="chevron_right" />
  92. </a>
  93. )
  94. })
  95. export const NestedMenuBarDropdown: FC<
  96. React.PropsWithChildren<{ id: string; title: string }>
  97. > = ({ children, id, title }) => {
  98. const { menuId, selected, setSelected } = useNestableDropdown()
  99. const select = useCallback(() => {
  100. setSelected(id)
  101. }, [id, setSelected])
  102. const onToggle = useCallback(
  103. (show: boolean) => {
  104. // Only handle opening
  105. if (show) {
  106. setSelected(id)
  107. }
  108. },
  109. [setSelected, id]
  110. )
  111. const active = selected === id
  112. return (
  113. <Dropdown
  114. align="start"
  115. drop="end"
  116. show={active}
  117. autoClose
  118. onToggle={onToggle}
  119. as="li"
  120. role="none"
  121. >
  122. <DropdownToggle
  123. id={`${menuId}-${id}`}
  124. onMouseEnter={select}
  125. className={classNames({ 'nested-dropdown-toggle-shown': active })}
  126. as={NestedDropdownToggle}
  127. >
  128. {title}
  129. </DropdownToggle>
  130. {active && (
  131. <NestableDropdownMenu renderOnMount id={`${menuId}-${id}`}>
  132. {children}
  133. </NestableDropdownMenu>
  134. )}
  135. </Dropdown>
  136. )
  137. }