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.3KB

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