dropdown-menu.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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={classnames(className, {
  77. 'dropdown-item-description-container': description,
  78. })}
  79. role="menuitem"
  80. {...props}
  81. ref={ref}
  82. >
  83. {leadingIconComponent}
  84. {children}
  85. {trailingIconComponent}
  86. {description && (
  87. <span className="dropdown-item-description">{description}</span>
  88. )}
  89. </BS5DropdownItem>
  90. )
  91. }
  92. function EmptyLeadingIcon() {
  93. return <span className="dropdown-item-leading-icon-empty" />
  94. }
  95. const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, {
  96. EmptyLeadingIcon,
  97. })
  98. export { ForwardReferredDropdownItem as DropdownItem }
  99. export const DropdownToggleCustom = forwardRef<HTMLButtonElement, ButtonProps>(
  100. ({ children, className, ...props }, ref) => (
  101. <BS5Button
  102. ref={ref}
  103. className={classnames('custom-toggle', className)}
  104. {...props}
  105. >
  106. {children}
  107. <MaterialIcon type="expand_more" />
  108. </BS5Button>
  109. )
  110. )
  111. DropdownToggleCustom.displayName = 'CustomCaret'
  112. export const DropdownToggle = forwardRef<
  113. typeof BS5DropdownToggle,
  114. DropdownToggleProps
  115. >((props, ref) => <BS5DropdownToggle {...props} ref={ref} />)
  116. DropdownToggle.displayName = 'DropdownToggle'
  117. export const DropdownMenu = forwardRef<
  118. typeof BS5DropdownMenu,
  119. DropdownMenuProps
  120. >(({ as = 'ul', ...props }, ref) => {
  121. return <BS5DropdownMenu as={as} role="menu" {...props} ref={ref} />
  122. })
  123. DropdownMenu.displayName = 'DropdownMenu'
  124. export function DropdownDivider({ as = 'li', ...props }: DropdownDividerProps) {
  125. return <BS5DropdownDivider as={as} {...props} />
  126. }
  127. export function DropdownHeader({ as = 'li', ...props }: DropdownHeaderProps) {
  128. return <BS5DropdownHeader as={as} {...props} />
  129. }