Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

AbstractButton.tsx 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. import React, { Component, ReactElement, ReactNode } from 'react';
  2. import { WithTranslation } from 'react-i18next';
  3. import { GestureResponderEvent } from 'react-native';
  4. import { IStore } from '../../../app/types';
  5. import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants';
  6. import { combineStyles } from '../../styles/functions.any';
  7. import { Styles } from './AbstractToolboxItem';
  8. import ToolboxItem from './ToolboxItem';
  9. export interface IProps extends WithTranslation {
  10. /**
  11. * Function to be called after the click handler has been processed.
  12. */
  13. afterClick?: Function;
  14. /**
  15. * The button's key.
  16. */
  17. buttonKey?: string;
  18. /**
  19. * Whether or not the button is displayed in a context menu.
  20. */
  21. contextMenu?: boolean;
  22. /**
  23. * An extra class name to be added at the end of the element's class name
  24. * in order to enable custom styling.
  25. */
  26. customClass?: string;
  27. /**
  28. * Extra styles which will be applied in conjunction with `styles` or
  29. * `toggledStyles` when the button is disabled;.
  30. */
  31. disabledStyles?: Styles;
  32. /**
  33. * Redux dispatch function.
  34. */
  35. dispatch: IStore['dispatch'];
  36. /**
  37. * External handler for click action.
  38. */
  39. handleClick?: Function;
  40. /**
  41. * Notify mode for `toolbarButtonClicked` event -
  42. * whether to only notify or to also prevent button click routine.
  43. */
  44. notifyMode?: string;
  45. /**
  46. * Whether to show the label or not.
  47. */
  48. showLabel?: boolean;
  49. /**
  50. * Collection of styles for the button.
  51. */
  52. styles?: Styles;
  53. /**
  54. * Collection of styles for the button, when in toggled state.
  55. */
  56. toggledStyles?: Styles;
  57. /**
  58. * From which direction the tooltip should appear, relative to the button.
  59. */
  60. tooltipPosition?: string;
  61. /**
  62. * Whether this button is visible or not.
  63. */
  64. visible?: boolean;
  65. }
  66. /**
  67. * Default style for disabled buttons.
  68. */
  69. export const defaultDisabledButtonStyles = {
  70. iconStyle: {
  71. opacity: 0.5
  72. },
  73. labelStyle: {
  74. opacity: 0.5
  75. },
  76. style: undefined,
  77. underlayColor: undefined
  78. };
  79. /**
  80. * An abstract implementation of a button.
  81. */
  82. export default class AbstractButton<P extends IProps, S=any> extends Component<P, S> {
  83. static defaultProps = {
  84. afterClick: undefined,
  85. disabledStyles: defaultDisabledButtonStyles,
  86. showLabel: false,
  87. styles: undefined,
  88. toggledStyles: undefined,
  89. tooltipPosition: 'top',
  90. visible: true
  91. };
  92. /**
  93. * A succinct description of what the button does. Used by accessibility
  94. * tools and torture tests.
  95. *
  96. * If `toggledAccessibilityLabel` is defined, this is used only when the
  97. * button is not toggled on.
  98. *
  99. * @abstract
  100. */
  101. accessibilityLabel: string;
  102. /**
  103. * This is the same as `accessibilityLabel`, replacing it when the button
  104. * is toggled on.
  105. *
  106. * @abstract
  107. */
  108. toggledAccessibilityLabel: string;
  109. labelProps: Object;
  110. /**
  111. * The icon of this button.
  112. *
  113. * @abstract
  114. */
  115. icon: Object;
  116. /**
  117. * The text associated with this button. When `showLabel` is set to
  118. * {@code true}, it will be displayed alongside the icon.
  119. *
  120. * @abstract
  121. */
  122. label: string;
  123. /**
  124. * The label for this button, when toggled.
  125. */
  126. toggledLabel: string;
  127. /**
  128. * The icon of this button, when toggled.
  129. *
  130. * @abstract
  131. */
  132. toggledIcon: Object;
  133. /**
  134. * The text to display in the tooltip. Used only on web.
  135. *
  136. * If `toggleTooltip` is defined, this is used only when the button is not
  137. * toggled on.
  138. *
  139. * @abstract
  140. */
  141. tooltip?: string;
  142. /**
  143. * The text to display in the tooltip when the button is toggled on.
  144. *
  145. * Used only on web.
  146. *
  147. * @abstract
  148. */
  149. toggledTooltip?: string;
  150. /**
  151. * Initializes a new {@code AbstractButton} instance.
  152. *
  153. * @param {IProps} props - The React {@code Component} props to initialize
  154. * the new {@code AbstractButton} instance with.
  155. */
  156. constructor(props: P) {
  157. super(props);
  158. // Bind event handlers so they are only bound once per instance.
  159. this._onClick = this._onClick.bind(this);
  160. }
  161. /**
  162. * Helper function to be implemented by subclasses, which should be used
  163. * to handle a key being down.
  164. *
  165. * @protected
  166. * @returns {void}
  167. */
  168. _onKeyDown() {
  169. // To be implemented by subclass.
  170. }
  171. /**
  172. * Helper function to be implemented by subclasses, which should be used
  173. * to handle the button being clicked / pressed.
  174. *
  175. * @protected
  176. * @returns {void}
  177. */
  178. _handleClick() {
  179. // To be implemented by subclass.
  180. }
  181. /**
  182. * Helper function to be implemented by subclasses, which may return a
  183. * new React Element to be appended at the end of the button.
  184. *
  185. * @protected
  186. * @returns {ReactElement|null}
  187. */
  188. _getElementAfter(): ReactElement | null {
  189. return null;
  190. }
  191. /**
  192. * Gets the current icon, taking the toggled state into account. If no
  193. * toggled icon is provided, the regular icon will also be used in the
  194. * toggled state.
  195. *
  196. * @private
  197. * @returns {string}
  198. */
  199. _getIcon() {
  200. return (
  201. this._isToggled() ? this.toggledIcon : this.icon
  202. ) || this.icon;
  203. }
  204. /**
  205. * Gets the current label, taking the toggled state into account. If no
  206. * toggled label is provided, the regular label will also be used in the
  207. * toggled state.
  208. *
  209. * @private
  210. * @returns {string}
  211. */
  212. _getLabel() {
  213. return (this._isToggled() ? this.toggledLabel : this.label)
  214. || this.label;
  215. }
  216. /**
  217. * Gets the current accessibility label, taking the toggled state into
  218. * account. If no toggled label is provided, the regular accessibility label
  219. * will also be used in the toggled state.
  220. *
  221. * The accessibility label is not visible in the UI, it is meant to be
  222. * used by assistive technologies, mainly screen readers.
  223. *
  224. * @private
  225. * @returns {string}
  226. */
  227. _getAccessibilityLabel() {
  228. return (this._isToggled()
  229. ? this.toggledAccessibilityLabel
  230. : this.accessibilityLabel
  231. ) || this.accessibilityLabel;
  232. }
  233. /**
  234. * Gets the current styles, taking the toggled state into account. If no
  235. * toggled styles are provided, the regular styles will also be used in the
  236. * toggled state.
  237. *
  238. * @private
  239. * @returns {?Styles}
  240. */
  241. _getStyles(): Styles | undefined {
  242. const { disabledStyles, styles, toggledStyles } = this.props;
  243. const buttonStyles
  244. = (this._isToggled() ? toggledStyles : styles) || styles;
  245. if (this._isDisabled() && buttonStyles && disabledStyles) {
  246. return {
  247. iconStyle: combineStyles(
  248. buttonStyles.iconStyle ?? {}, disabledStyles.iconStyle ?? {}),
  249. labelStyle: combineStyles(
  250. buttonStyles.labelStyle ?? {}, disabledStyles.labelStyle ?? {}),
  251. style: combineStyles(
  252. buttonStyles.style ?? {}, disabledStyles.style ?? {}),
  253. underlayColor:
  254. disabledStyles.underlayColor || buttonStyles.underlayColor
  255. };
  256. }
  257. return buttonStyles;
  258. }
  259. /**
  260. * Get the tooltip to display when hovering over the button.
  261. *
  262. * @private
  263. * @returns {string}
  264. */
  265. _getTooltip() {
  266. return (this._isToggled() ? this.toggledTooltip : this.tooltip)
  267. || this.tooltip
  268. || '';
  269. }
  270. /**
  271. * Helper function to be implemented by subclasses, which must return a
  272. * boolean value indicating if this button is disabled or not.
  273. *
  274. * @protected
  275. * @returns {boolean}
  276. */
  277. _isDisabled() {
  278. return false;
  279. }
  280. /**
  281. * Helper function to be implemented by subclasses, which must return a
  282. * {@code boolean} value indicating if this button is toggled or not or
  283. * undefined if the button is not toggleable.
  284. *
  285. * @protected
  286. * @returns {?boolean}
  287. */
  288. _isToggled(): boolean | undefined {
  289. return undefined;
  290. }
  291. /**
  292. * Handles clicking / pressing the button.
  293. *
  294. * @param {Object} e - Event.
  295. * @private
  296. * @returns {void}
  297. */
  298. _onClick(e?: React.MouseEvent | GestureResponderEvent) {
  299. const { afterClick, buttonKey, handleClick, notifyMode } = this.props;
  300. if (typeof APP !== 'undefined' && notifyMode) {
  301. APP.API.notifyToolbarButtonClicked(
  302. buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
  303. );
  304. }
  305. if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
  306. if (handleClick) {
  307. handleClick();
  308. }
  309. this._handleClick();
  310. }
  311. afterClick?.(e);
  312. // blur after click to release focus from button to allow PTT.
  313. // @ts-ignore
  314. e?.currentTarget?.blur && e.currentTarget.blur();
  315. }
  316. /**
  317. * Implements React's {@link Component#render()}.
  318. *
  319. * @inheritdoc
  320. * @returns {React$Node}
  321. */
  322. render(): ReactNode {
  323. const props: any = {
  324. ...this.props,
  325. accessibilityLabel: this._getAccessibilityLabel(),
  326. elementAfter: this._getElementAfter(),
  327. icon: this._getIcon(),
  328. label: this._getLabel(),
  329. labelProps: this.labelProps,
  330. styles: this._getStyles(),
  331. toggled: this._isToggled(),
  332. tooltip: this._getTooltip()
  333. };
  334. return (
  335. <ToolboxItem
  336. disabled = { this._isDisabled() }
  337. onClick = { this._onClick }
  338. onKeyDown = { this._onKeyDown }
  339. { ...props } />
  340. );
  341. }
  342. }