dropdownControl.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import DropdownBubble from 'app/components/dropdownBubble';
  4. import DropdownButton from 'app/components/dropdownButton';
  5. import DropdownMenu, {GetActorPropsFn, GetMenuPropsFn} from 'app/components/dropdownMenu';
  6. import MenuItem from 'app/components/menuItem';
  7. type ButtonPriority = React.ComponentProps<typeof DropdownButton>['priority'];
  8. type DefaultProps = {
  9. /**
  10. * Should the menu contents always be rendered? Defaults to true.
  11. * Set to false to have menu contents removed from the DOM on close.
  12. */
  13. alwaysRenderMenu: boolean;
  14. /**
  15. * Width of the menu. Defaults to 100% of the button width.
  16. */
  17. menuWidth: string;
  18. };
  19. type ChildrenArgs = {
  20. isOpen: boolean;
  21. getMenuProps: GetMenuPropsFn;
  22. };
  23. type ButtonArgs = {
  24. isOpen: boolean;
  25. getActorProps: GetActorPropsFn;
  26. };
  27. type Props = DefaultProps & {
  28. children:
  29. | ((args: ChildrenArgs) => React.ReactElement)
  30. | React.ReactElement
  31. | Array<React.ReactElement>;
  32. /**
  33. * String or element for the button contents.
  34. */
  35. label?: React.ReactNode;
  36. /**
  37. * A closure that returns a styled button. Function will get {isOpen, getActorProps}
  38. * as arguments. Use this if you need to style/replace the dropdown button.
  39. */
  40. button?: (args: ButtonArgs) => React.ReactNode;
  41. /**
  42. * Align the dropdown menu to the right. (Default aligns to left)
  43. */
  44. alignRight?: boolean;
  45. /**
  46. * Props to pass to DropdownButton
  47. */
  48. buttonProps?: React.ComponentProps<typeof DropdownButton>;
  49. /**
  50. * This makes the dropdown menu blend (e.g. corners are not rounded) with its
  51. * actor (opener) component
  52. */
  53. blendWithActor?: boolean;
  54. priority?: ButtonPriority;
  55. className?: string;
  56. };
  57. /*
  58. * A higher level dropdown component that helps with building complete dropdowns
  59. * including the button + menu options. Use the `button` or `label` prop to set
  60. * the button content and `children` to provide menu options.
  61. */
  62. class DropdownControl extends React.Component<Props> {
  63. static defaultProps: DefaultProps = {
  64. alwaysRenderMenu: true,
  65. menuWidth: '100%',
  66. };
  67. renderButton(isOpen: boolean, getActorProps: GetActorPropsFn) {
  68. const {label, button, buttonProps, priority} = this.props;
  69. if (button) {
  70. return button({isOpen, getActorProps});
  71. }
  72. return (
  73. <StyledDropdownButton
  74. priority={priority}
  75. {...getActorProps(buttonProps)}
  76. isOpen={isOpen}
  77. >
  78. {label}
  79. </StyledDropdownButton>
  80. );
  81. }
  82. renderChildren(isOpen: boolean, getMenuProps: GetMenuPropsFn) {
  83. const {children, alignRight, menuWidth, blendWithActor, priority} = this.props;
  84. if (typeof children === 'function') {
  85. return children({isOpen, getMenuProps});
  86. }
  87. const alignMenu = alignRight ? 'right' : 'left';
  88. return (
  89. <Content
  90. {...getMenuProps()}
  91. priority={priority}
  92. alignMenu={alignMenu}
  93. width={menuWidth}
  94. isOpen={isOpen}
  95. blendWithActor={blendWithActor}
  96. blendCorner
  97. >
  98. {children}
  99. </Content>
  100. );
  101. }
  102. render() {
  103. const {alwaysRenderMenu, className} = this.props;
  104. return (
  105. <Container className={className}>
  106. <DropdownMenu alwaysRenderMenu={alwaysRenderMenu}>
  107. {({isOpen, getMenuProps, getActorProps}) => (
  108. <React.Fragment>
  109. {this.renderButton(isOpen, getActorProps)}
  110. {this.renderChildren(isOpen, getMenuProps)}
  111. </React.Fragment>
  112. )}
  113. </DropdownMenu>
  114. </Container>
  115. );
  116. }
  117. }
  118. const Container = styled('div')`
  119. display: inline-block;
  120. position: relative;
  121. `;
  122. const StyledDropdownButton = styled(DropdownButton)`
  123. z-index: ${p => p.theme.zIndex.dropdownAutocomplete.actor};
  124. white-space: nowrap;
  125. `;
  126. const Content = styled(DropdownBubble)<{isOpen: boolean; priority?: ButtonPriority}>`
  127. display: ${p => (p.isOpen ? 'block' : 'none')};
  128. border-color: ${p => p.theme.button[p.priority || 'form'].border};
  129. `;
  130. const DropdownItem = styled(MenuItem)`
  131. font-size: ${p => p.theme.fontSizeMedium};
  132. `;
  133. export default DropdownControl;
  134. export {DropdownItem, Content};