You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

StatelessDialog.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. // @flow
  2. import Button, { ButtonGroup } from '@atlaskit/button';
  3. import Modal, { ModalFooter } from '@atlaskit/modal-dialog';
  4. import _ from 'lodash';
  5. import React, { Component } from 'react';
  6. import { translate } from '../../../i18n/functions';
  7. import type { DialogProps } from '../../constants';
  8. /**
  9. * The ID to be used for the cancel button if enabled.
  10. * @type {string}
  11. */
  12. const CANCEL_BUTTON_ID = 'modal-dialog-cancel-button';
  13. /**
  14. * The ID to be used for the ok button if enabled.
  15. * @type {string}
  16. */
  17. const OK_BUTTON_ID = 'modal-dialog-ok-button';
  18. /**
  19. * The type of the React {@code Component} props of {@link StatelessDialog}.
  20. *
  21. * @static
  22. */
  23. type Props = {
  24. ...DialogProps,
  25. /**
  26. * Custom dialog header that replaces the standard heading.
  27. */
  28. customHeader?: React$Element<any> | Function,
  29. /*
  30. * True if listening for the Enter key should be disabled.
  31. */
  32. disableEnter: boolean,
  33. /**
  34. * Disables dismissing the dialog when the blanket is clicked. Enabled
  35. * by default.
  36. */
  37. disableBlanketClickDismiss: boolean,
  38. /**
  39. * If true, the cancel button will not display but cancel actions, like
  40. * clicking the blanket, will cancel.
  41. */
  42. hideCancelButton: boolean,
  43. i18n: Object,
  44. /**
  45. * Whether the dialog is modal. This means clicking on the blanket will
  46. * leave the dialog open. No cancel button.
  47. */
  48. isModal: boolean,
  49. /**
  50. * Disables rendering of the submit button.
  51. */
  52. submitDisabled: boolean,
  53. /**
  54. * Function to be used to retreive translated i18n labels.
  55. */
  56. t: Function,
  57. /**
  58. * Width of the dialog, can be:
  59. * - 'small' (400px), 'medium' (600px), 'large' (800px),
  60. * 'x-large' (968px)
  61. * - integer value for pixel width
  62. * - string value for percentage
  63. */
  64. width: string
  65. };
  66. /**
  67. * Web dialog that uses atlaskit modal-dialog to display dialogs.
  68. */
  69. class StatelessDialog extends Component<Props> {
  70. /**
  71. * The functional component to be used for rendering the modal footer.
  72. */
  73. _Footer: ?Function
  74. _dialogElement: ?HTMLElement;
  75. /**
  76. * Initializes a new {@code StatelessDialog} instance.
  77. *
  78. * @param {Object} props - The read-only properties with which the new
  79. * instance is to be initialized.
  80. */
  81. constructor(props) {
  82. super(props);
  83. // Bind event handlers so they are only bound once for every instance.
  84. this._onCancel = this._onCancel.bind(this);
  85. this._onDialogDismissed = this._onDialogDismissed.bind(this);
  86. this._onKeyDown = this._onKeyDown.bind(this);
  87. this._onSubmit = this._onSubmit.bind(this);
  88. this._renderFooter = this._renderFooter.bind(this);
  89. this._setDialogElement = this._setDialogElement.bind(this);
  90. }
  91. /**
  92. * Implements React's {@link Component#render()}.
  93. *
  94. * @inheritdoc
  95. * @returns {ReactElement}
  96. */
  97. render() {
  98. const {
  99. customHeader,
  100. children,
  101. t /* The following fixes a flow error: */ = _.identity,
  102. titleString,
  103. titleKey,
  104. width
  105. } = this.props;
  106. return (
  107. <Modal
  108. autoFocus = { true }
  109. components = {{
  110. Header: customHeader
  111. }}
  112. footer = { this._renderFooter }
  113. heading = { customHeader ? undefined : titleString || t(titleKey) }
  114. i18n = { this.props.i18n }
  115. onClose = { this._onDialogDismissed }
  116. onDialogDismissed = { this._onDialogDismissed }
  117. shouldCloseOnEscapePress = { true }
  118. width = { width || 'medium' }>
  119. <div
  120. onKeyDown = { this._onKeyDown }
  121. ref = { this._setDialogElement }>
  122. <form
  123. className = 'modal-dialog-form'
  124. id = 'modal-dialog-form'
  125. onSubmit = { this._onSubmit }>
  126. { children }
  127. </form>
  128. </div>
  129. </Modal>
  130. );
  131. }
  132. _renderFooter: () => React$Node;
  133. /**
  134. * Returns a ReactElement to display buttons for closing the modal.
  135. *
  136. * @param {Object} propsFromModalFooter - The props passed in from the
  137. * {@link ModalFooter} component.
  138. * @private
  139. * @returns {ReactElement}
  140. */
  141. _renderFooter(propsFromModalFooter) {
  142. // Filter out falsy (null) values because {@code ButtonGroup} will error
  143. // if passed in anything but buttons with valid type props.
  144. const buttons = [
  145. this._renderOKButton(),
  146. this._renderCancelButton()
  147. ].filter(Boolean);
  148. return (
  149. <ModalFooter showKeyline = { propsFromModalFooter.showKeyline } >
  150. {
  151. /**
  152. * Atlaskit has this empty span (JustifySim) so...
  153. */
  154. }
  155. <span />
  156. <ButtonGroup>
  157. { buttons }
  158. </ButtonGroup>
  159. </ModalFooter>
  160. );
  161. }
  162. _onCancel: () => void;
  163. /**
  164. * Dispatches action to hide the dialog.
  165. *
  166. * @returns {void}
  167. */
  168. _onCancel() {
  169. if (!this.props.isModal) {
  170. const { onCancel } = this.props;
  171. onCancel && onCancel();
  172. }
  173. }
  174. _onDialogDismissed: () => void;
  175. /**
  176. * Handles click on the blanket area.
  177. *
  178. * @returns {void}
  179. */
  180. _onDialogDismissed() {
  181. if (!this.props.disableBlanketClickDismiss) {
  182. this._onCancel();
  183. }
  184. }
  185. _onSubmit: (?string) => void;
  186. /**
  187. * Dispatches the action when submitting the dialog.
  188. *
  189. * @private
  190. * @param {string} value - The submitted value if any.
  191. * @returns {void}
  192. */
  193. _onSubmit(value) {
  194. const { onSubmit } = this.props;
  195. onSubmit && onSubmit(value);
  196. }
  197. /**
  198. * Renders Cancel button.
  199. *
  200. * @private
  201. * @returns {ReactElement|null} The Cancel button if enabled and dialog is
  202. * not modal.
  203. */
  204. _renderCancelButton() {
  205. if (this.props.cancelDisabled
  206. || this.props.isModal
  207. || this.props.hideCancelButton) {
  208. return null;
  209. }
  210. const {
  211. t /* The following fixes a flow error: */ = _.identity
  212. } = this.props;
  213. return (
  214. <Button
  215. appearance = 'subtle'
  216. id = { CANCEL_BUTTON_ID }
  217. key = 'cancel'
  218. onClick = { this._onCancel }
  219. type = 'button'>
  220. { t(this.props.cancelKey || 'dialog.Cancel') }
  221. </Button>
  222. );
  223. }
  224. /**
  225. * Renders OK button.
  226. *
  227. * @private
  228. * @returns {ReactElement|null} The OK button if enabled.
  229. */
  230. _renderOKButton() {
  231. if (this.props.submitDisabled) {
  232. return null;
  233. }
  234. const {
  235. t /* The following fixes a flow error: */ = _.identity
  236. } = this.props;
  237. return (
  238. <Button
  239. appearance = 'primary'
  240. form = 'modal-dialog-form'
  241. id = { OK_BUTTON_ID }
  242. isDisabled = { this.props.okDisabled }
  243. key = 'submit'
  244. onClick = { this._onSubmit }
  245. type = 'button'>
  246. { t(this.props.okKey || 'dialog.Ok') }
  247. </Button>
  248. );
  249. }
  250. _setDialogElement: (?HTMLElement) => void;
  251. /**
  252. * Sets the instance variable for the div containing the component's dialog
  253. * element so it can be accessed directly.
  254. *
  255. * @param {HTMLElement} element - The DOM element for the component's
  256. * dialog.
  257. * @private
  258. * @returns {void}
  259. */
  260. _setDialogElement(element: ?HTMLElement) {
  261. this._dialogElement = element;
  262. }
  263. _onKeyDown: (Object) => void;
  264. /**
  265. * Handles 'Enter' key in the dialog to submit/hide dialog depending on
  266. * the available buttons and their disabled state.
  267. *
  268. * @param {Object} event - The key event.
  269. * @private
  270. * @returns {void}
  271. */
  272. _onKeyDown(event) {
  273. // If the event coming to the dialog has been subject to preventDefault
  274. // we don't handle it here.
  275. if (event.defaultPrevented) {
  276. return;
  277. }
  278. if (event.key === 'Enter' && !this.props.disableEnter) {
  279. event.preventDefault();
  280. event.stopPropagation();
  281. if (this.props.submitDisabled && !this.props.cancelDisabled) {
  282. this._onCancel();
  283. } else if (!this.props.okDisabled) {
  284. this._onSubmit();
  285. }
  286. }
  287. }
  288. }
  289. export default translate(StatelessDialog);