dropdownButton.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import {forwardRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import Button, {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. disabled={disabled}
  60. priority={priority}
  61. isOpen={isOpen}
  62. hideBottomBorder={hideBottomBorder}
  63. detached={detached}
  64. ref={forwardedRef}
  65. >
  66. {prefix && <LabelText>{prefix}</LabelText>}
  67. {children}
  68. {showChevron && (
  69. <StyledChevron
  70. rightAlignChevron={rightAlignChevron}
  71. size="xs"
  72. direction={isOpen ? 'up' : 'down'}
  73. />
  74. )}
  75. </StyledButton>
  76. );
  77. };
  78. DropdownButton.defaultProps = {
  79. showChevron: true,
  80. };
  81. const StyledChevron = styled(IconChevron, {
  82. shouldForwardProp: prop => prop !== 'rightAlignChevron',
  83. })<{
  84. rightAlignChevron: boolean;
  85. }>`
  86. margin-left: 0.33em;
  87. @media (max-width: ${p => p.theme.breakpoints[0]}) {
  88. position: ${p => p.rightAlignChevron && 'absolute'};
  89. right: ${p => p.rightAlignChevron && `${space(2)}`};
  90. }
  91. `;
  92. const StyledButton = styled(Button)<
  93. Required<
  94. Pick<
  95. DropdownButtonProps,
  96. 'isOpen' | 'disabled' | 'hideBottomBorder' | 'detached' | 'priority'
  97. >
  98. >
  99. >`
  100. border-bottom-right-radius: ${p =>
  101. p.isOpen && !p.detached ? 0 : p.theme.borderRadius};
  102. border-bottom-left-radius: ${p => (p.isOpen && !p.detached ? 0 : p.theme.borderRadius)};
  103. position: relative;
  104. z-index: 2;
  105. ${p => (p.isOpen || p.disabled) && 'box-shadow: none'};
  106. &,
  107. &:active,
  108. &:focus,
  109. &:hover {
  110. ${p =>
  111. p.isOpen &&
  112. p.hideBottomBorder &&
  113. !p.detached &&
  114. `border-bottom-color: transparent;`}
  115. }
  116. `;
  117. const LabelText = styled('span')`
  118. font-weight: 400;
  119. padding-right: ${space(0.75)};
  120. &:after {
  121. content: ':';
  122. }
  123. `;
  124. export default forwardRef<typeof Button, DropdownButtonProps>((props, ref) => (
  125. <DropdownButton forwardedRef={ref} {...props} />
  126. ));