dropdown-menu.tsx 3.6 KB

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