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