menu-bar-dropdown.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. >
  88. {children}
  89. <MaterialIcon type="chevron_right" />
  90. </a>
  91. )
  92. })
  93. export const NestedMenuBarDropdown: FC<
  94. React.PropsWithChildren<{ id: string; title: string }>
  95. > = ({ children, id, title }) => {
  96. const { menuId, selected, setSelected } = useNestableDropdown()
  97. const select = useCallback(() => {
  98. setSelected(id)
  99. }, [id, setSelected])
  100. const onToggle = useCallback(
  101. (show: boolean) => {
  102. // Only handle opening
  103. if (show) {
  104. setSelected(id)
  105. }
  106. },
  107. [setSelected, id]
  108. )
  109. const active = selected === id
  110. return (
  111. <Dropdown
  112. align="start"
  113. drop="end"
  114. show={active}
  115. autoClose
  116. onToggle={onToggle}
  117. >
  118. <DropdownToggle
  119. id={`${menuId}-${id}`}
  120. onMouseEnter={select}
  121. className={classNames({ 'nested-dropdown-toggle-shown': active })}
  122. as={NestedDropdownToggle}
  123. >
  124. {title}
  125. </DropdownToggle>
  126. {active && (
  127. <NestableDropdownMenu renderOnMount id={`${menuId}-${id}`}>
  128. {children}
  129. </NestableDropdownMenu>
  130. )}
  131. </Dropdown>
  132. )
  133. }