@jetbrains/ring-ui 4.1.0-beta.3 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +17 -15
  3. package/babel.config.js +3 -2
  4. package/components/alert/alert.js +9 -3
  5. package/components/alert/alert.test.js +21 -48
  6. package/components/alert/container.css +1 -1
  7. package/components/alert/container.test.js +3 -13
  8. package/components/alert-service/alert-service.examples.css +18 -0
  9. package/components/alert-service/alert-service.examples.js +21 -0
  10. package/components/alert-service/alert-service.js +10 -3
  11. package/components/analytics/analytics__fus-plugin.js +3 -3
  12. package/components/analytics/analytics__ga-plugin.js +2 -2
  13. package/components/auth/auth.test.js +14 -7
  14. package/components/auth/auth__core.js +64 -33
  15. package/components/auth-dialog/auth-dialog.css +2 -3
  16. package/components/auth-dialog/auth-dialog.js +4 -1
  17. package/components/auth-dialog/auth-dialog.test.js +3 -19
  18. package/components/avatar/avatar.css +4 -1
  19. package/components/avatar/avatar.examples.js +3 -2
  20. package/components/avatar/avatar.js +34 -6
  21. package/components/avatar/avatar.test.js +20 -17
  22. package/components/avatar/fallback-avatar.js +136 -0
  23. package/components/avatar-editor-ng/avatar-editor-ng.css +2 -2
  24. package/components/avatar-editor-ng/avatar-editor-ng.js +2 -1
  25. package/components/avatar-editor-ng/{avatar-editor-ng.html → avatar-editor-ng__template.js} +2 -2
  26. package/components/badge/badge.test.js +13 -20
  27. package/components/button/button.css +2 -2
  28. package/components/button/button.js +4 -1
  29. package/components/button/button.test.js +32 -33
  30. package/components/button-group/button-group.js +1 -1
  31. package/components/button-group/caption.js +1 -1
  32. package/components/button-ng/button-ng.js +1 -1
  33. package/components/button-set-ng/button-set-ng.js +3 -1
  34. package/components/checkbox/checkbox.css +1 -1
  35. package/components/code/code.js +2 -5
  36. package/components/confirm/confirm.js +1 -0
  37. package/components/confirm-service/confirm-service.js +5 -5
  38. package/components/content-layout/content-layout.css +1 -1
  39. package/components/data-list/data-list.css +1 -1
  40. package/components/date-picker/date-input.js +5 -4
  41. package/components/date-picker/date-picker.css +34 -22
  42. package/components/date-picker/date-picker.js +16 -14
  43. package/components/date-picker/date-popup.js +22 -7
  44. package/components/date-picker/month-names.js +8 -5
  45. package/components/date-picker/month.js +6 -2
  46. package/components/date-picker/weekdays.js +10 -2
  47. package/components/dialog/dialog.examples.js +3 -1
  48. package/components/dialog/dialog.js +10 -5
  49. package/components/dialog/dialog.test.js +1 -1
  50. package/components/dialog/dialog__body-scroll-preventer.js +51 -31
  51. package/components/dialog-ng/dialog-ng.js +10 -10
  52. package/components/dialog-ng/{dialog-ng.html → dialog-ng__template.js} +2 -2
  53. package/components/dropdown/dropdown.examples.js +36 -1
  54. package/components/dropdown/dropdown.test.js +2 -2
  55. package/components/dropdown-menu/dropdown-menu.examples.js +47 -0
  56. package/components/dropdown-menu/dropdown-menu.js +117 -0
  57. package/components/dropdown-menu/dropdown-menu.test.js +76 -0
  58. package/components/error-bubble/error-bubble-legacy.css +1 -1
  59. package/components/error-bubble/error-bubble.css +1 -1
  60. package/components/error-bubble/error-bubble.examples.js +1 -1
  61. package/components/error-page/error-page.css +2 -2
  62. package/components/footer-ng/footer-ng.js +13 -3
  63. package/components/form/form.css +2 -2
  64. package/components/form-ng/form-ng.js +3 -1
  65. package/components/global/global.css +1 -1
  66. package/components/global/theme.js +1 -1
  67. package/components/global/variables.css +8 -1
  68. package/components/grid/grid.css +10 -9
  69. package/components/header/header.css +1 -1
  70. package/components/header/header.examples.js +7 -8
  71. package/components/header/profile.js +10 -11
  72. package/components/http/http.js +1 -1
  73. package/components/icon/icon.css +5 -4
  74. package/components/input/input-legacy.css +7 -7
  75. package/components/island/header.js +2 -2
  76. package/components/island/island.css +8 -3
  77. package/components/island-legacy/island-legacy.css +3 -1
  78. package/components/list/list.js +6 -1
  79. package/components/list/list__custom.js +9 -3
  80. package/components/list/list__item.js +8 -2
  81. package/components/list/list__link.js +2 -1
  82. package/components/loader-inline/loader-inline.css +1 -1
  83. package/components/loader-screen/loader-screen.css +1 -1
  84. package/components/message/message.css +1 -1
  85. package/components/message/message.examples.js +8 -7
  86. package/components/pager/pager.js +5 -3
  87. package/components/permissions/permissions.js +1 -1
  88. package/components/popup/popup.js +1 -1
  89. package/components/popup/popup.test.js +15 -13
  90. package/components/progress-bar/progress-bar.css +1 -1
  91. package/components/progress-bar/progress-bar.examples.js +3 -3
  92. package/components/progress-bar/progress-bar.js +5 -2
  93. package/components/progress-bar/progress-bar.test.js +12 -13
  94. package/components/progress-bar-ng/progress-bar-ng.examples.js +3 -3
  95. package/components/query-assist/query-assist.css +13 -3
  96. package/components/query-assist/query-assist.examples.js +3 -4
  97. package/components/query-assist/query-assist.js +56 -12
  98. package/components/query-assist/query-assist.test.js +37 -5
  99. package/components/save-field-ng/save-field-ng.css +0 -3
  100. package/components/save-field-ng/save-field-ng.js +3 -1
  101. package/components/save-field-ng/{save-field-ng.html → save-field-ng__template.js} +2 -2
  102. package/components/select/select.css +12 -7
  103. package/components/select/select.examples.js +13 -0
  104. package/components/select/select.js +30 -43
  105. package/components/select/select.test.js +4 -5
  106. package/components/select/select__popup.js +1 -0
  107. package/components/shortcuts-hint-ng/shortcuts-hint-ng.css +1 -1
  108. package/components/shortcuts-hint-ng/shortcuts-hint-ng.js +1 -1
  109. package/components/shortcuts-hint-ng/{shortcuts-hint-ng.html → shortcuts-hint-ng__template.js} +2 -2
  110. package/components/sidebar/sidebar.css +1 -0
  111. package/components/sidebar-ng/sidebar-ng.js +6 -2
  112. package/components/sidebar-ng/{sidebar-ng__button.html → sidebar-ng__button-template.js} +2 -2
  113. package/components/sidebar-ng/{sidebar-ng.html → sidebar-ng__template.js} +2 -2
  114. package/components/table/header.js +9 -1
  115. package/components/table/row.js +2 -1
  116. package/components/table/table.css +2 -1
  117. package/components/table-legacy/table-legacy.css +2 -2
  118. package/components/table-legacy/table-legacy__toolbar.css +2 -2
  119. package/components/table-legacy-ng/table-legacy-ng.js +38 -5
  120. package/components/table-legacy-ng/table-legacy-ng__pager.js +7 -1
  121. package/components/tabs/collapsible-tab.js +2 -2
  122. package/components/tabs/collapsible-tabs.js +5 -9
  123. package/components/tabs/tab-link.js +4 -2
  124. package/components/tabs/tabs.css +32 -5
  125. package/components/tabs-ng/tabs-ng.js +4 -2
  126. package/components/tabs-ng/{tabs-ng.html → tabs-ng__template.js} +6 -2
  127. package/components/tag/tag.css +5 -2
  128. package/components/tag/tag.examples.js +3 -0
  129. package/components/tag/tag.js +19 -16
  130. package/components/tags-input/tag-input.examples.js +1 -1
  131. package/components/tags-input/tags-input.js +5 -2
  132. package/components/template-ng/template-ng.js +1 -1
  133. package/components/tooltip/tooltip.js +7 -2
  134. package/components/user-agreement/user-agreement.css +1 -5
  135. package/components/user-agreement/user-agreement.examples.js +7 -6
  136. package/components/user-agreement/user-agreement.js +11 -3
  137. package/package.json +85 -83
  138. package/webpack.config.js +14 -10
  139. package/components/button-set-ng/button-set-ng.html +0 -1
  140. package/components/footer-ng/footer-ng.html +0 -13
  141. package/components/form-ng/form-ng__error-bubble.html +0 -3
  142. package/components/table-legacy-ng/table-legacy-ng.html +0 -4
  143. package/components/table-legacy-ng/table-legacy-ng__column.html +0 -12
  144. package/components/table-legacy-ng/table-legacy-ng__header.html +0 -4
  145. package/components/table-legacy-ng/table-legacy-ng__pager.html +0 -7
  146. package/components/table-legacy-ng/table-legacy-ng__row.html +0 -12
  147. package/components/table-legacy-ng/table-legacy-ng__title.html +0 -9
  148. package/dist/_helpers/_rollupPluginBabelHelpers.js +0 -123
  149. package/dist/_helpers/background-flow.js +0 -232
  150. package/dist/_helpers/badge.js +0 -3
  151. package/dist/_helpers/button.js +0 -145
  152. package/dist/_helpers/clickableLink.js +0 -65
  153. package/dist/_helpers/data-tests.js +0 -15
  154. package/dist/_helpers/disable-hover-hoc.js +0 -407
  155. package/dist/_helpers/dom.js +0 -101
  156. package/dist/_helpers/get-uid.js +0 -15
  157. package/dist/_helpers/linear-function.js +0 -17
  158. package/dist/_helpers/list.js +0 -1327
  159. package/dist/_helpers/logo.js +0 -36
  160. package/dist/_helpers/memoize.js +0 -17
  161. package/dist/_helpers/popup.js +0 -691
  162. package/dist/_helpers/popup.target.js +0 -27
  163. package/dist/_helpers/rerender-hoc.js +0 -49
  164. package/dist/_helpers/schedule-raf.js +0 -31
  165. package/dist/_helpers/sniffer.js +0 -6
  166. package/dist/_helpers/theme.js +0 -40
  167. package/dist/_helpers/url.js +0 -125
  168. package/dist/alert-service.js +0 -149
  169. package/dist/alert.js +0 -284
  170. package/dist/analytics.js +0 -116
  171. package/dist/auth-dialog-service.js +0 -56
  172. package/dist/auth-dialog.js +0 -122
  173. package/dist/auth.js +0 -1744
  174. package/dist/avatar.js +0 -152
  175. package/dist/badge.js +0 -52
  176. package/dist/button-group.js +0 -48
  177. package/dist/button-set.js +0 -27
  178. package/dist/button-toolbar.js +0 -30
  179. package/dist/button.js +0 -12
  180. package/dist/caret.js +0 -262
  181. package/dist/checkbox.js +0 -108
  182. package/dist/confirm-service.js +0 -102
  183. package/dist/confirm.js +0 -113
  184. package/dist/content-layout.js +0 -184
  185. package/dist/contenteditable.js +0 -81
  186. package/dist/data-list.js +0 -466
  187. package/dist/date-picker.js +0 -1398
  188. package/dist/dialog.js +0 -223
  189. package/dist/dropdown.js +0 -250
  190. package/dist/error-bubble.js +0 -56
  191. package/dist/error-message.js +0 -53
  192. package/dist/footer.js +0 -124
  193. package/dist/grid.js +0 -148
  194. package/dist/group.js +0 -34
  195. package/dist/header.js +0 -658
  196. package/dist/heading.js +0 -76
  197. package/dist/http.js +0 -207
  198. package/dist/hub-source.js +0 -130
  199. package/dist/icon.js +0 -211
  200. package/dist/input.js +0 -228
  201. package/dist/island.js +0 -314
  202. package/dist/link.js +0 -117
  203. package/dist/list.js +0 -29
  204. package/dist/loader-inline.js +0 -165
  205. package/dist/loader-screen.js +0 -45
  206. package/dist/loader.js +0 -338
  207. package/dist/login-dialog.js +0 -173
  208. package/dist/logo.js +0 -8
  209. package/dist/message.js +0 -226
  210. package/dist/old-browsers-message.js +0 -129
  211. package/dist/pager.js +0 -325
  212. package/dist/panel.js +0 -34
  213. package/dist/permissions.js +0 -466
  214. package/dist/popup-menu.js +0 -93
  215. package/dist/popup.js +0 -16
  216. package/dist/progress-bar.js +0 -111
  217. package/dist/proxy-attrs.js +0 -19
  218. package/dist/query-assist.js +0 -1081
  219. package/dist/radio.js +0 -112
  220. package/dist/select.js +0 -1920
  221. package/dist/selection.js +0 -213
  222. package/dist/shortcuts.js +0 -307
  223. package/dist/storage.js +0 -373
  224. package/dist/style.css +0 -1
  225. package/dist/tab-trap.js +0 -174
  226. package/dist/table.js +0 -903
  227. package/dist/tabs.js +0 -721
  228. package/dist/tag.js +0 -187
  229. package/dist/tags-input.js +0 -440
  230. package/dist/tags-list.js +0 -91
  231. package/dist/text.js +0 -38
  232. package/dist/toggle.js +0 -80
  233. package/dist/tooltip.js +0 -202
  234. package/dist/user-card.js +0 -218
@@ -13,7 +13,7 @@ import Button from '../button/button';
13
13
 
14
14
  import {PopupTarget} from '../popup/popup.target';
15
15
 
16
- import ScrollPreventer from './dialog__body-scroll-preventer';
16
+ import {preventerFactory as scrollPreventerFactory} from './dialog__body-scroll-preventer';
17
17
  import styles from './dialog.css';
18
18
 
19
19
  /**
@@ -24,6 +24,7 @@ function noop() {}
24
24
 
25
25
  export default class Dialog extends PureComponent {
26
26
  static propTypes = {
27
+ label: PropTypes.string,
27
28
  className: PropTypes.string,
28
29
  contentClassName: PropTypes.string,
29
30
  children: PropTypes.oneOfType([
@@ -47,6 +48,7 @@ export default class Dialog extends PureComponent {
47
48
  };
48
49
 
49
50
  static defaultProps = {
51
+ label: 'Dialog',
50
52
  onOverlayClick: noop,
51
53
  onEscPress: noop,
52
54
  onCloseClick: noop,
@@ -72,16 +74,18 @@ export default class Dialog extends PureComponent {
72
74
  }
73
75
 
74
76
  componentWillUnmount() {
75
- ScrollPreventer.reset();
77
+ this.scrollPreventer.reset();
76
78
  }
77
79
 
80
+ scrollPreventer = scrollPreventerFactory(getUID('preventer-'));
81
+
78
82
  uid = getUID('dialog-');
79
83
 
80
84
  toggleScrollPreventer() {
81
85
  if (this.props.show) {
82
- ScrollPreventer.prevent();
86
+ this.scrollPreventer.prevent();
83
87
  } else {
84
- ScrollPreventer.reset();
88
+ this.scrollPreventer.reset();
85
89
  }
86
90
  }
87
91
 
@@ -115,7 +119,7 @@ export default class Dialog extends PureComponent {
115
119
  render() {
116
120
  const {show, showCloseButton, onOverlayClick, onCloseAttempt, onEscPress, onCloseClick,
117
121
  children, className, contentClassName, trapFocus, 'data-test': dataTest, closeButtonInside,
118
- portalTarget, ...restProps} = this.props;
122
+ portalTarget, label, ...restProps} = this.props;
119
123
  const classes = classNames(styles.container, className);
120
124
  const shortcutsMap = this.getShortcutsMap();
121
125
 
@@ -148,6 +152,7 @@ export default class Dialog extends PureComponent {
148
152
  className={classNames(styles.content, contentClassName)}
149
153
  data-test="ring-dialog"
150
154
  role="dialog"
155
+ aria-label={label}
151
156
  >
152
157
  {children}
153
158
  {showCloseButton &&
@@ -6,7 +6,7 @@ import styles from './dialog.css';
6
6
 
7
7
  describe('Dialog', () => {
8
8
  const children = <div/>;
9
- const mountDialog = props => mount(<Dialog {...props} trapFocus={false}/>);
9
+ const mountDialog = props => mount(<Dialog label="Dialog" {...props} trapFocus={false}/>);
10
10
 
11
11
  it('should create component', () => {
12
12
  mountDialog({show: true, children}).should.have.type(Dialog);
@@ -2,38 +2,58 @@ import scrollbarWidth from 'scrollbar-width';
2
2
 
3
3
  import styles from './dialog.css';
4
4
 
5
- let isPrevented = false;
5
+ const isPrevented = new Set();
6
6
  let previousDocumentWidth = null;
7
7
 
8
- export default {
9
- prevent() {
10
- if (isPrevented) {
11
- return;
12
- }
13
- isPrevented = true;
14
- const documentHasScroll = document.documentElement.scrollHeight > window.innerHeight ||
15
- getComputedStyle(document.documentElement).overflowY === 'scroll';
16
- document.documentElement.classList.add(styles.documentWithoutScroll);
17
-
18
- const scrollWidth = scrollbarWidth();
19
-
20
- if (documentHasScroll && scrollWidth > 0) {
21
- previousDocumentWidth = document.documentElement.style.width;
22
- document.documentElement.style.width = `calc(100% - ${scrollWidth}px)`;
23
- }
24
- },
25
-
26
- reset() {
27
- if (!isPrevented) {
28
- return;
29
- }
30
- isPrevented = false;
31
-
32
- document.documentElement.classList.remove(styles.documentWithoutScroll);
33
-
34
- if (previousDocumentWidth !== null) {
35
- document.documentElement.style.width = previousDocumentWidth;
36
- previousDocumentWidth = null;
37
- }
8
+ const prevent = key => {
9
+ if (isPrevented.has(key)) {
10
+ return;
11
+ }
12
+
13
+ isPrevented.add(key);
14
+
15
+ if (isPrevented.size > 1) {
16
+ return;
17
+ }
18
+
19
+ const documentHasScroll = document.documentElement.scrollHeight > window.innerHeight ||
20
+ getComputedStyle(document.documentElement).overflowY === 'scroll';
21
+ document.documentElement.classList.add(styles.documentWithoutScroll);
22
+
23
+ const scrollWidth = scrollbarWidth();
24
+
25
+ if (documentHasScroll && scrollWidth > 0) {
26
+ previousDocumentWidth = document.documentElement.style.width;
27
+ document.documentElement.style.width = `calc(100% - ${scrollWidth}px)`;
28
+ }
29
+ };
30
+
31
+ const reset = key => {
32
+ if (isPrevented.size === 0) {
33
+ return;
34
+ }
35
+
36
+ isPrevented.delete(key);
37
+
38
+ if (isPrevented.size > 0) {
39
+ return;
40
+ }
41
+
42
+ document.documentElement.classList.remove(styles.documentWithoutScroll);
43
+
44
+ if (previousDocumentWidth !== null) {
45
+ document.documentElement.style.width = previousDocumentWidth;
46
+ previousDocumentWidth = null;
38
47
  }
39
48
  };
49
+
50
+ export const preventerFactory = key => {
51
+ const preventerKey = key || Math.random();
52
+
53
+ return {
54
+ prevent: () => prevent(preventerKey),
55
+ reset: () => reset(preventerKey)
56
+ };
57
+ };
58
+
59
+ export default preventerFactory('default-preventer');
@@ -1,7 +1,5 @@
1
1
  import angular from 'angular';
2
2
 
3
- import angularSanitize from 'angular-sanitize';
4
-
5
3
  import {createFocusTrap} from 'focus-trap';
6
4
 
7
5
  import {getRect, getStyles} from '../global/dom';
@@ -10,12 +8,13 @@ import shortcuts from '../shortcuts/core';
10
8
  import RingButton from '../button-ng/button-ng';
11
9
  import PromisedClickNg from '../promised-click-ng/promised-click-ng';
12
10
  import rgCompilerModuleName from '../compiler-ng/compiler-ng';
13
- import ScrollPreventer from '../dialog/dialog__body-scroll-preventer';
11
+ import {preventerFactory as scrollPreventerFactory} from '../dialog/dialog__body-scroll-preventer';
14
12
  import '../form/form.css';
15
13
  import dialogStyles from '../dialog/dialog.css';
16
14
  import islandStyles from '../island/island.css';
17
15
 
18
16
  import styles from './dialog-ng.css';
17
+ import dialogTemplate from './dialog-ng__template';
19
18
 
20
19
  /**
21
20
  * @name Dialog Ng
@@ -23,12 +22,12 @@ import styles from './dialog-ng.css';
23
22
 
24
23
  const angularModule = angular.module(
25
24
  'Ring.dialog',
26
- [angularSanitize, RingButton, PromisedClickNg, rgCompilerModuleName]
25
+ [RingButton, PromisedClickNg, rgCompilerModuleName]
27
26
  );
28
27
 
29
28
  class DialogController extends RingAngularComponent {
30
29
  static $inject = ['$scope', '$q', 'dialog', '$element', 'dialogInSidebar', '$compile',
31
- '$injector', '$controller', 'rgCompiler'];
30
+ '$injector', '$controller', 'rgCompiler', '$sce'];
32
31
 
33
32
  constructor(...args) {
34
33
  super(...args);
@@ -36,6 +35,7 @@ class DialogController extends RingAngularComponent {
36
35
  this.styles = styles;
37
36
  this.dialogStyles = dialogStyles;
38
37
  this.islandStyles = islandStyles;
38
+ this.scrollPreventer = scrollPreventerFactory();
39
39
 
40
40
  this.$inject.$q((resolve, reject) => {
41
41
  this.resolve = resolve;
@@ -134,7 +134,7 @@ class DialogController extends RingAngularComponent {
134
134
  const {$q, $scope} = this.$inject;
135
135
 
136
136
  if (!this.inSidebar) {
137
- ScrollPreventer.prevent();
137
+ this.scrollPreventer.prevent();
138
138
  if (config && config.trapFocus === true) {
139
139
  this.focusTrap.activate();
140
140
  }
@@ -195,7 +195,7 @@ class DialogController extends RingAngularComponent {
195
195
 
196
196
  hide = () => {
197
197
  if (!this.inSidebar) {
198
- ScrollPreventer.reset();
198
+ this.scrollPreventer.reset();
199
199
  }
200
200
 
201
201
  this.active = false;
@@ -272,7 +272,7 @@ class DialogController extends RingAngularComponent {
272
272
  }];
273
273
  this.serverErrorFields.push(errorField);
274
274
  } else {
275
- this.error = this.getErrorMessage(errorResponse);
275
+ this.error = this.$inject.$sce.trustAsHtml(this.getErrorMessage(errorResponse));
276
276
  }
277
277
  };
278
278
 
@@ -364,7 +364,7 @@ class DialogService extends RingAngularComponent {
364
364
 
365
365
  unregister = () => {
366
366
  Reflect.deleteProperty(this, 'ctrl');
367
- }
367
+ };
368
368
  }
369
369
 
370
370
  class DialogInSidebarService extends DialogService {
@@ -490,7 +490,7 @@ function rgDialogDirective($timeout) {
490
490
  active: '=?'
491
491
  },
492
492
  replace: true,
493
- template: require('./dialog-ng.html'),
493
+ template: dialogTemplate,
494
494
  controllerAs: 'dialog',
495
495
  link
496
496
  };
@@ -1,4 +1,4 @@
1
- <div
1
+ export default `<div
2
2
  ng-show="dialog.active"
3
3
  ng-class="[!dialog.inSidebar && dialog.dialogStyles.container]"
4
4
  ng-click="dialog.handleClick($event)"
@@ -66,4 +66,4 @@
66
66
  <div tabindex="-1" ng-show="false" data-anchor="focus-trap-fallback"></div>
67
67
  </div>
68
68
  </div>
69
- </div>
69
+ </div>`;
@@ -3,12 +3,14 @@ import chevronDownIcon from '@jetbrains/icons/chevron-down';
3
3
 
4
4
  import reactDecorator from '../../.storybook/react-decorator';
5
5
 
6
+ import {ActiveItemContext} from '../list/list';
7
+
6
8
  import Popup from '@jetbrains/ring-ui/components/popup/popup';
7
9
  import PopupMenu from '@jetbrains/ring-ui/components/popup-menu/popup-menu';
8
10
  import Button from '@jetbrains/ring-ui/components/button/button';
9
11
  import Link from '@jetbrains/ring-ui/components/link/link';
10
12
  import {Input} from '@jetbrains/ring-ui/components/input/input';
11
-
13
+ import getUID from '@jetbrains/ring-ui/components/global/get-uid';
12
14
  import Dropdown from '@jetbrains/ring-ui/components/dropdown/dropdown';
13
15
 
14
16
  export default {
@@ -45,6 +47,39 @@ export const withCustomAnchorAndPopup = () => (
45
47
  </Dropdown>
46
48
  );
47
49
 
50
+ export const withCustomAnchorAndPopupAndContentAccessibilityHandling = () => {
51
+ const listId = getUID('popup-menu-list-id');
52
+
53
+ return (
54
+ <ActiveItemContext.Provider>
55
+ <Dropdown anchor={({active}) => (
56
+ <ActiveItemContext.ValueContext.Consumer>
57
+ {activeItemId => {
58
+ const anchorAriaProps = active && activeItemId
59
+ ? {'aria-owns': listId, 'aria-activedescendant': activeItemId}
60
+ : {};
61
+ return (
62
+ <Button
63
+ {...anchorAriaProps}
64
+ delayed
65
+ >Edit</Button>
66
+ );
67
+ }}
68
+ </ActiveItemContext.ValueContext.Consumer>
69
+ )}
70
+ >
71
+ <PopupMenu
72
+ id={listId}
73
+ ariaLabel="My options menu"
74
+ closeOnSelect
75
+ activateFirstItem
76
+ data={['Cut', 'Copy', 'Paste'].map(label => ({label, key: label.toLowerCase()}))}
77
+ />
78
+ </Dropdown>
79
+ </ActiveItemContext.Provider>
80
+ );
81
+ };
82
+
48
83
  withCustomAnchorAndPopup.storyName = 'with custom anchor and popup';
49
84
 
50
85
  export const withActiveClassName = () => (
@@ -76,11 +76,11 @@ describe('Dropdown', () => {
76
76
  popup.isVisible().should.be.false;
77
77
  });
78
78
 
79
- it('should hide popup on outside click', done => {
79
+ it('should hide popup on outside pointer down event', done => {
80
80
  mountDropdown();
81
81
  Simulate.click(anchor);
82
82
  setTimeout(() => {
83
- document.dispatchEvent(new Event('click'));
83
+ document.dispatchEvent(new PointerEvent('pointerdown'));
84
84
  popup.isVisible().should.be.false;
85
85
  done();
86
86
  }, 0);
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+
3
+ import reactDecorator from '../../.storybook/react-decorator';
4
+
5
+ import DropdownMenu from '@jetbrains/ring-ui/components/dropdown-menu/dropdown-menu';
6
+
7
+ export default {
8
+ title: 'Components/DropdownMenu',
9
+ decorators: [reactDecorator()],
10
+
11
+ parameters: {
12
+ notes: 'Displays a menu in a dropdown.',
13
+ hermione: {
14
+ actions: [
15
+ {type: 'click', selector: '[data-test~=ring-dropdown]'},
16
+ {
17
+ type: 'capture',
18
+ name: 'dropdown',
19
+ selector: ['[data-test~=ring-dropdown]', '[data-test~=ring-popup]']
20
+ }
21
+ ]
22
+ },
23
+ a11y: {element: '*[data-test~=ring-dropdown]'}
24
+ }
25
+ };
26
+
27
+ export const basic = () => {
28
+ const data = [
29
+ {label: 'Item'},
30
+ {label: 'Link to jetbrains.com', href: 'http://www.jetbrains.com'},
31
+ {rgItemType: DropdownMenu.ListProps.Type.SEPARATOR},
32
+ {rgItemType: DropdownMenu.ListProps.Type.LINK, label: 'Link Item'},
33
+ {
34
+ rgItemType: DropdownMenu.ListProps.Type.LINK,
35
+ label: 'Link Item With Additional Class',
36
+ className: 'test'
37
+ },
38
+ {rgItemType: DropdownMenu.ListProps.Type.SEPARATOR, description: 'Separator With Description'},
39
+ {rgItemType: DropdownMenu.ListProps.Type.TITLE, label: 'Title'},
40
+ {rgItemType: DropdownMenu.ListProps.Type.ITEM, label: '1 Element in group'},
41
+ {rgItemType: DropdownMenu.ListProps.Type.ITEM, label: '2 Element in group'}
42
+ ];
43
+
44
+ return <DropdownMenu data={data} anchor={'Click me!'}/>;
45
+ };
46
+
47
+ basic.storyName = 'DropdownMenu';
@@ -0,0 +1,117 @@
1
+ import React, {useMemo, cloneElement} from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import List, {ActiveItemContext} from '../list/list';
5
+ import Dropdown from '../dropdown/dropdown';
6
+ import PopupMenu from '../popup-menu/popup-menu';
7
+ import getUID from '../global/get-uid';
8
+ import Anchor from '../dropdown/anchor';
9
+
10
+ const {children, ...dropdownPropTypes} = Dropdown.propTypes || {};
11
+ const {
12
+ id: idPropType,
13
+ data: dataPropType,
14
+ ariaLabel: ariaLabelPropType,
15
+ onSelect: onSelectPropType
16
+ } = PopupMenu.propTypes || {};
17
+
18
+ const defaultAriaLabel = 'Dropdown menu';
19
+
20
+ function DropdownAnchorWrapper({anchor, pinned, active, activeListItemId, listId, ...restProps}) {
21
+ const anchorAriaProps = useMemo(() => ({
22
+ ...(listId ? {'aria-haspopup': 'true'} : {}),
23
+ ...(activeListItemId ? {'aria-activedescendant': activeListItemId, 'aria-owns': listId} : {}),
24
+ ...(active ? {'aria-expanded': 'true'} : {})
25
+ }), [active, activeListItemId, listId]);
26
+
27
+ const anchorProps = useMemo(
28
+ () => ({active, pinned, ...restProps, ...anchorAriaProps}),
29
+ [pinned, active, restProps, anchorAriaProps]
30
+ );
31
+
32
+ const anchorComponentProps = useMemo(
33
+ () => ({...anchorProps, pinned: `${anchorProps.pinned}`}),
34
+ [anchorProps]
35
+ );
36
+
37
+ if (typeof anchor === 'string') {
38
+ return (
39
+ <Anchor
40
+ {...anchorComponentProps}
41
+ >{anchor}</Anchor>
42
+ );
43
+ }
44
+ if (typeof anchor === 'function') {
45
+ return anchor(({active, pinned, ...restProps}), anchorAriaProps);
46
+ }
47
+ if (!Array.isArray(anchor)) {
48
+ return cloneElement(
49
+ anchor,
50
+ typeof anchor.type === 'string' ? anchorAriaProps : anchorComponentProps
51
+ );
52
+ }
53
+ return (
54
+ <div {...anchorAriaProps}>{anchor}</div>
55
+ );
56
+ }
57
+
58
+ DropdownAnchorWrapper.propTypes = {
59
+ anchor: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func]).isRequired,
60
+ pinned: PropTypes.bool,
61
+ active: PropTypes.bool,
62
+ activeListItemId: PropTypes.string,
63
+ listId: PropTypes.string
64
+ };
65
+
66
+ const DropdownMenu = React.forwardRef(function DropdownMenu(
67
+ {id, anchor, ariaLabel, data, onSelect, menuProps, ...restDropdownProps},
68
+ forwardedRef
69
+ ) {
70
+ const listId = useMemo(() => id || getUID('dropdown-menu-list'), [id]);
71
+
72
+ return (
73
+ <ActiveItemContext.Provider>
74
+ <Dropdown
75
+ anchor={({pinned, active, ...restAnchorProps}) => (
76
+ <ActiveItemContext.ValueContext.Consumer>
77
+ {activeItemId => (
78
+ <DropdownAnchorWrapper
79
+ anchor={anchor}
80
+ pinned={pinned}
81
+ active={active}
82
+ activeListItemId={activeItemId}
83
+ listId={listId}
84
+ {...restAnchorProps}
85
+ />
86
+ )}
87
+ </ActiveItemContext.ValueContext.Consumer>
88
+ )}
89
+ {...restDropdownProps}
90
+ >
91
+ <PopupMenu
92
+ ref={forwardedRef}
93
+ id={listId}
94
+ ariaLabel={ariaLabel || defaultAriaLabel}
95
+ closeOnSelect
96
+ activateFirstItem
97
+ data={data}
98
+ onSelect={onSelect}
99
+ {...menuProps}
100
+ />
101
+ </Dropdown>
102
+ </ActiveItemContext.Provider>
103
+ );
104
+ });
105
+
106
+ DropdownMenu.propTypes = {
107
+ id: idPropType,
108
+ data: dataPropType,
109
+ ariaLabel: ariaLabelPropType,
110
+ onSelect: onSelectPropType,
111
+ menuProps: PropTypes.object,
112
+ ...dropdownPropTypes
113
+ };
114
+
115
+ DropdownMenu.ListProps = List.ListProps;
116
+
117
+ export default DropdownMenu;
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import {shallow, mount} from 'enzyme';
3
+
4
+ import PopupMenu from '../popup-menu/popup-menu';
5
+ import Anchor from '../dropdown/anchor';
6
+
7
+ import DropdownMenu from './dropdown-menu';
8
+
9
+ const waitForCondition = (condition, rejectMessage) => new Promise((resolve, reject) => {
10
+ const interval = 10;
11
+ const maxWaitingTime = 2000;
12
+ let remainingTime = maxWaitingTime;
13
+
14
+ const intervalId = setInterval(() => {
15
+ if (condition()) {
16
+ clearInterval(intervalId);
17
+ resolve();
18
+ } else if (remainingTime < 0) {
19
+ clearInterval(intervalId);
20
+ reject(new Error(rejectMessage));
21
+ } else {
22
+ remainingTime -= interval;
23
+ }
24
+ }, interval);
25
+ });
26
+
27
+ describe('Dropdown Menu', () => {
28
+ const shallowDropdownMenu = props => shallow(<DropdownMenu id="test-list-id" {...props}/>);
29
+ const mountDropdownMenu = props => mount(<DropdownMenu id="test-list-id" {...props}/>);
30
+
31
+ const mountAndWaitForMenuContent = async props => {
32
+ const wrapper = mountDropdownMenu(props);
33
+
34
+ wrapper.find('button').getDOMNode().click();
35
+ await waitForCondition(
36
+ () => !!wrapper.find(PopupMenu).length,
37
+ 'List was not rendered in a dropdown menu'
38
+ );
39
+
40
+ return wrapper;
41
+ };
42
+
43
+ it('should create component', () => {
44
+ shallowDropdownMenu({anchor: 'Anchor text'}).should.exist;
45
+ });
46
+
47
+ it('should open List', async () => {
48
+ const wrapper = await mountAndWaitForMenuContent({anchor: 'Anchor text'});
49
+
50
+ const list = wrapper.find(PopupMenu).instance().list;
51
+ list.should.exist;
52
+
53
+ //We need it to maintain compatibility between Dropdown Menu and List
54
+ list.props.data.length.should.equal(0);
55
+ });
56
+
57
+ it('should pass params to List', async () => {
58
+ const wrapper = await mountAndWaitForMenuContent({
59
+ anchor: 'Anchor text',
60
+ data: [{key: 'key1'}]
61
+ });
62
+
63
+ shallow(wrapper.find(PopupMenu).instance().list.renderItem({index: 1})).should.exist;
64
+ });
65
+
66
+ it('should add accessibility attributes to anchor', async () => {
67
+ const wrapper = await mountAndWaitForMenuContent({
68
+ anchor: 'Anchor text',
69
+ data: [{key: 'key1'}, {key: 'key2'}]
70
+ });
71
+
72
+ const anchorProps = wrapper.update().find(Anchor).props();
73
+ anchorProps['aria-owns'].should.equal('test-list-id');
74
+ anchorProps['aria-activedescendant'].should.contain(':key1');
75
+ });
76
+ });
@@ -44,7 +44,7 @@
44
44
  width: unit;
45
45
  height: unit;
46
46
 
47
- content: '';
47
+ content: "";
48
48
  transform: rotate(45deg);
49
49
 
50
50
  border: 1px solid var(--ring-popup-border-color);
@@ -46,7 +46,7 @@
46
46
  width: unit;
47
47
  height: unit;
48
48
 
49
- content: '';
49
+ content: "";
50
50
  transform: rotate(45deg);
51
51
 
52
52
  border: 1px solid var(--ring-popup-border-color);
@@ -57,7 +57,7 @@ export const inDialogForm = () => {
57
57
  const {value} = this.state;
58
58
 
59
59
  return (
60
- <Dialog show>
60
+ <Dialog label="Dialog" show>
61
61
  <Header>Dialog example</Header>
62
62
  <Content>
63
63
  <form className="ring-form">
@@ -15,10 +15,10 @@
15
15
  width: 100%;
16
16
  height: 100%;
17
17
 
18
- content: '';
18
+ content: "";
19
19
  }
20
20
 
21
21
  .enabled::before {
22
22
  opacity: 0.06;
23
- background: url('error-page.gif');
23
+ background: url("error-page.gif");
24
24
  }
@@ -4,8 +4,6 @@ import RingAngularComponent from '../global/ring-angular-component';
4
4
  import styles from '../footer/footer.css';
5
5
  import {copyright} from '../footer/footer';
6
6
 
7
- import template from './footer-ng.html';
8
-
9
7
  /**
10
8
  * @name Footer Ng
11
9
  */
@@ -22,7 +20,19 @@ class rgFooterComponent extends RingAngularComponent {
22
20
  right: '?rgFooterRight'
23
21
  };
24
22
 
25
- static template = template;
23
+ static template = `<footer ng-class=":: $ctrl.styles.footer" data-test="ring-footer">
24
+ <div ng-class=":: $ctrl.styles.columnLeft">
25
+ <div ng-transclude="left" ng-class=":: $ctrl.styles.columnItem"></div>
26
+ </div>
27
+
28
+ <div ng-class=":: $ctrl.styles.columnCenter">
29
+ <div ng-transclude="center" ng-class=":: $ctrl.styles.columnItem"></div>
30
+ </div>
31
+
32
+ <div ng-class=":: $ctrl.styles.columnRight">
33
+ <div ng-transclude="right" ng-class=":: $ctrl.styles.columnItem"></div>
34
+ </div>
35
+ </footer>`;
26
36
  }
27
37
 
28
38
  class rgFooterLineComponent extends RingAngularComponent {
@@ -96,7 +96,7 @@
96
96
  display: block;
97
97
  clear: both;
98
98
 
99
- content: '';
99
+ content: "";
100
100
  }
101
101
 
102
102
  :global(.ring-form__group_united) {
@@ -167,7 +167,7 @@
167
167
 
168
168
  height: calc(unit * 3);
169
169
 
170
- content: '';
170
+ content: "";
171
171
  vertical-align: middle;
172
172
  }
173
173
 
@@ -54,7 +54,9 @@ angularModule.directive('rgErrorBubble', function rgErrorBubbleDirective(getForm
54
54
  },
55
55
 
56
56
  replace: true,
57
- template: require('./form-ng__error-bubble.html'),
57
+ template: `<div class="ring-error-bubble" ng-class="{ active: active, 'ring-error-bubble_material': material }" ng-style="style">
58
+ <div ng-repeat="errorMessage in getFormErrorMessages(errorBubble().$error)">{{ errorMessage }}</div>
59
+ </div>`,
58
60
 
59
61
  link: function link(scope, iElement, iAttrs) {
60
62
  scope.style = {};