dropdown-menu.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import React, { forwardRef } from 'react'
  2. import {
  3. Dropdown as BS5Dropdown,
  4. DropdownToggle as BS5DropdownToggle,
  5. DropdownMenu as BS5DropdownMenu,
  6. DropdownItem as BS5DropdownItem,
  7. DropdownDivider as BS5DropdownDivider,
  8. DropdownHeader as BS5DropdownHeader,
  9. Button as BS5Button,
  10. } from 'react-bootstrap'
  11. import type {
  12. DropdownProps,
  13. DropdownItemProps,
  14. DropdownToggleProps,
  15. DropdownMenuProps,
  16. DropdownDividerProps,
  17. DropdownHeaderProps,
  18. } from '@/features/ui/components/types/dropdown-menu-props'
  19. import MaterialIcon from '@/shared/components/material-icon'
  20. import { fixedForwardRef } from '@/utils/react'
  21. import classnames from 'classnames'
  22. export function Dropdown({ ...props }: DropdownProps) {
  23. return <BS5Dropdown {...props} />
  24. }
  25. function DropdownItem(
  26. {
  27. active,
  28. children,
  29. className,
  30. description,
  31. leadingIcon,
  32. trailingIcon,
  33. ...props
  34. }: DropdownItemProps,
  35. ref: React.ForwardedRef<typeof BS5DropdownItem>
  36. ) {
  37. let leadingIconComponent = null
  38. if (leadingIcon) {
  39. if (typeof leadingIcon === 'string') {
  40. leadingIconComponent = (
  41. <MaterialIcon
  42. className="dropdown-item-leading-icon"
  43. type={leadingIcon}
  44. />
  45. )
  46. } else {
  47. leadingIconComponent = (
  48. <span className="dropdown-item-leading-icon" aria-hidden="true">
  49. {leadingIcon}
  50. </span>
  51. )
  52. }
  53. }
  54. let trailingIconComponent = null
  55. if (trailingIcon) {
  56. if (typeof trailingIcon === 'string') {
  57. const trailingIconType = active ? 'check' : trailingIcon
  58. trailingIconComponent = (
  59. <MaterialIcon
  60. className="dropdown-item-trailing-icon"
  61. type={trailingIconType}
  62. />
  63. )
  64. } else {
  65. trailingIconComponent = (
  66. <span className="dropdown-item-trailing-icon" aria-hidden="true">
  67. {trailingIcon}
  68. </span>
  69. )
  70. }
  71. }
  72. return (
  73. <BS5DropdownItem
  74. active={active}
  75. className={className}
  76. role="menuitem"
  77. {...props}
  78. ref={ref}
  79. >
  80. {leadingIconComponent}
  81. <div
  82. className={classnames({
  83. 'dropdown-item-description-container': description,
  84. })}
  85. >
  86. {children}
  87. {trailingIconComponent}
  88. {description && (
  89. <span className="dropdown-item-description">{description}</span>
  90. )}
  91. </div>
  92. </BS5DropdownItem>
  93. )
  94. }
  95. function EmptyLeadingIcon() {
  96. return <span className="dropdown-item-leading-icon-empty" />
  97. }
  98. const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, {
  99. EmptyLeadingIcon,
  100. })
  101. export { ForwardReferredDropdownItem as DropdownItem }
  102. export const DropdownToggleCustom = forwardRef<
  103. HTMLButtonElement,
  104. React.ComponentProps<typeof BS5Button>
  105. >(({ children, className, ...props }, ref) => (
  106. <BS5Button
  107. ref={ref}
  108. className={classnames('custom-toggle', className)}
  109. {...props}
  110. >
  111. {children}
  112. <MaterialIcon type="expand_more" />
  113. </BS5Button>
  114. ))
  115. DropdownToggleCustom.displayName = 'DropdownToggleCustom'
  116. export const DropdownToggle = forwardRef<
  117. typeof BS5DropdownToggle,
  118. DropdownToggleProps
  119. >((props, ref) => <BS5DropdownToggle {...props} ref={ref} />)
  120. DropdownToggle.displayName = 'DropdownToggle'
  121. export const DropdownMenu = forwardRef<
  122. typeof BS5DropdownMenu,
  123. DropdownMenuProps
  124. >(({ as = 'ul', ...props }, ref) => {
  125. return <BS5DropdownMenu as={as} role="menu" {...props} ref={ref} />
  126. })
  127. DropdownMenu.displayName = 'DropdownMenu'
  128. export function DropdownDivider({ as = 'li', ...props }: DropdownDividerProps) {
  129. return <BS5DropdownDivider as={as} {...props} />
  130. }
  131. export function DropdownHeader({ as = 'li', ...props }: DropdownHeaderProps) {
  132. return <BS5DropdownHeader as={as} {...props} />
  133. }