menu-bar-dropdown.tsx 3.3 KB

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