dropdownButton.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import {forwardRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import Button, {ButtonLabel, ButtonProps} from 'sentry/components/button';
  4. import {IconChevron} from 'sentry/icons';
  5. import space from 'sentry/styles/space';
  6. interface DropdownButtonProps extends Omit<ButtonProps, 'prefix'> {
  7. /**
  8. * Whether the menu associated with this button is visually detached.
  9. */
  10. detached?: boolean;
  11. /**
  12. * Forward a ref to the button's root
  13. */
  14. forwardedRef?: React.Ref<typeof Button>;
  15. /**
  16. * Should the bottom border become transparent when open?
  17. */
  18. hideBottomBorder?: boolean;
  19. /**
  20. * Whether or not the button should render as open
  21. */
  22. isOpen?: boolean;
  23. /**
  24. * The fixed prefix text to show in the button eg: 'Sort By'
  25. */
  26. prefix?: React.ReactNode;
  27. /**
  28. * Button color
  29. */
  30. priority?: 'default' | 'primary' | 'form';
  31. /**
  32. * Align chevron to the right of dropdown button
  33. */
  34. rightAlignChevron?: boolean;
  35. /**
  36. * Should a chevron icon be shown?
  37. */
  38. showChevron?: boolean;
  39. }
  40. const DropdownButton = ({
  41. children,
  42. forwardedRef,
  43. prefix,
  44. isOpen = false,
  45. showChevron = false,
  46. hideBottomBorder = true,
  47. detached = false,
  48. disabled = false,
  49. priority = 'form',
  50. rightAlignChevron = false,
  51. ...props
  52. }: DropdownButtonProps) => {
  53. return (
  54. <StyledButton
  55. {...props}
  56. type="button"
  57. aria-haspopup="listbox"
  58. aria-expanded={detached ? isOpen : undefined}
  59. hasPrefix={!!prefix}
  60. disabled={disabled}
  61. priority={priority}
  62. isOpen={isOpen}
  63. hideBottomBorder={hideBottomBorder}
  64. detached={detached}
  65. ref={forwardedRef}
  66. >
  67. {prefix && <LabelText>{prefix}</LabelText>}
  68. {children}
  69. {showChevron && (
  70. <StyledChevron
  71. rightAlignChevron={rightAlignChevron}
  72. size="xs"
  73. direction={isOpen ? 'up' : 'down'}
  74. />
  75. )}
  76. </StyledButton>
  77. );
  78. };
  79. DropdownButton.defaultProps = {
  80. showChevron: true,
  81. };
  82. const StyledChevron = styled(IconChevron, {
  83. shouldForwardProp: prop => prop !== 'rightAlignChevron',
  84. })<{
  85. rightAlignChevron: boolean;
  86. }>`
  87. margin-left: 0.33em;
  88. @media (max-width: ${p => p.theme.breakpoints.small}) {
  89. position: ${p => p.rightAlignChevron && 'absolute'};
  90. right: ${p => p.rightAlignChevron && `${space(2)}`};
  91. }
  92. `;
  93. const StyledButton = styled(Button)<
  94. Required<
  95. Pick<
  96. DropdownButtonProps,
  97. 'isOpen' | 'disabled' | 'hideBottomBorder' | 'detached' | 'priority'
  98. >
  99. > & {
  100. hasPrefix: boolean;
  101. }
  102. >`
  103. border-bottom-right-radius: ${p =>
  104. p.isOpen && !p.detached ? 0 : p.theme.borderRadius};
  105. border-bottom-left-radius: ${p => (p.isOpen && !p.detached ? 0 : p.theme.borderRadius)};
  106. position: relative;
  107. z-index: 2;
  108. ${p => (p.isOpen || p.disabled) && 'box-shadow: none'};
  109. ${p => p.hasPrefix && `${ButtonLabel} {font-weight: 400;}`}
  110. &,
  111. &:active,
  112. &:focus,
  113. &:hover {
  114. ${p =>
  115. p.isOpen &&
  116. p.hideBottomBorder &&
  117. !p.detached &&
  118. `border-bottom-color: transparent;`}
  119. }
  120. `;
  121. const LabelText = styled('span')`
  122. font-weight: 600;
  123. padding-right: ${space(0.75)};
  124. &:after {
  125. content: ':';
  126. }
  127. `;
  128. export default forwardRef<typeof Button, DropdownButtonProps>((props, ref) => (
  129. <DropdownButton forwardedRef={ref} {...props} />
  130. ));