@woosmap/ui 2.45.0 → 2.49.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woosmap/ui",
3
- "version": "2.45.0",
3
+ "version": "2.49.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/WebGeoServices/ui.git"
@@ -25,12 +25,14 @@ export default class Button extends Component {
25
25
  active,
26
26
  children,
27
27
  testId,
28
+ iconRight,
28
29
  ...rest
29
30
  } = this.props;
30
31
  const classes = cl(
31
32
  'btn',
32
33
  { 'btn--has-icon': icon || isLoading },
33
34
  { 'btn--icon': isBtnIcon || (icon && !label) },
35
+ { 'btn--icon-right': iconRight },
34
36
  size ? `btn--${size}` : null,
35
37
  type ? `btn--${type}` : null,
36
38
  { active },
@@ -54,8 +56,9 @@ export default class Button extends Component {
54
56
  className={classes}
55
57
  data-testid={testId}
56
58
  >
57
- {iconComponent}
59
+ {!iconRight ? iconComponent : null}
58
60
  {label && <span className="btn__label">{isLoading ? tr('Loading...') : label}</span>}
61
+ {iconRight ? iconComponent : null}
59
62
  {children}
60
63
  </button>
61
64
  );
@@ -76,6 +79,7 @@ Button.defaultProps = {
76
79
  isLoading: false,
77
80
  active: false,
78
81
  onClick: null,
82
+ iconRight: false,
79
83
  };
80
84
 
81
85
  Button.propTypes = {
@@ -104,10 +108,11 @@ Button.propTypes = {
104
108
  ]),
105
109
  isLoading: PropTypes.bool,
106
110
  active: PropTypes.bool,
107
- label: PropTypes.string,
111
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
108
112
  icon: PropTypes.string,
109
113
  iconSize: PropTypes.number,
110
114
  className: PropTypes.string,
111
115
  onClick: PropTypes.func,
112
116
  children: PropTypes.node,
117
+ iconRight: PropTypes.bool,
113
118
  };
@@ -23,7 +23,7 @@ const Template = (args) => {
23
23
  <div className="flex mbi">
24
24
  <Button icon="add" />
25
25
  <Button label={label} />
26
- <Button label={label} icon="marker" />
26
+ <Button label={label} icon="marker" iconRight />
27
27
  <Button label={label} isLoading />
28
28
  <Button label={label} disabled />
29
29
  </div>
@@ -82,7 +82,7 @@ const Template = (args) => {
82
82
  </span>
83
83
  <span>
84
84
  There is a labeled and iconed button link here : &nbsp;
85
- <Button label={label} icon="marker" type="link" />
85
+ <Button label={label} icon="marker" iconRight type="link" />
86
86
  </span>
87
87
  <span>
88
88
  There is a iconed button link here : &nbsp; <Button label={label} icon="clock" type="link" />
@@ -45,9 +45,20 @@ class DropdownMenu extends Component {
45
45
  };
46
46
 
47
47
  render() {
48
- const { direction, children, closeCb, testId, ...rest } = this.props;
48
+ const { direction, children, closeCb, testId, isSection, ...rest } = this.props;
49
49
  const childrenWithProps = mapChildrenWithProps(children, this.childrenRefs, closeCb);
50
-
50
+ if (isSection) {
51
+ return (
52
+ <div
53
+ role="menu"
54
+ className={cl('dropdown__menu', direction, 'dropdown__menu--section')}
55
+ data-testid={testId}
56
+ {...rest}
57
+ >
58
+ {childrenWithProps}
59
+ </div>
60
+ );
61
+ }
51
62
  return (
52
63
  <ul role="menu" className={cl('dropdown__menu', direction)} data-testid={testId} {...rest}>
53
64
  {childrenWithProps}
@@ -60,15 +71,50 @@ DropdownMenu.defaultProps = {
60
71
  direction: 'sw',
61
72
  closeCb: null,
62
73
  testId: 'dropdown-menu',
74
+ isSection: false,
63
75
  };
64
76
 
65
77
  DropdownMenu.propTypes = {
66
78
  children: PropTypes.node.isRequired,
67
79
  closeCb: PropTypes.func,
68
80
  testId: PropTypes.string,
81
+ isSection: PropTypes.bool,
69
82
  direction: PropTypes.oneOf(['ne', 'e', 'se', 's', 'sw', 'w']),
70
83
  };
71
84
 
85
+ class DropdownMenuSection extends Component {
86
+ constructor(props) {
87
+ super(props);
88
+ this.childrenRefs = {};
89
+ }
90
+
91
+ close = () => {
92
+ closeChildren(this.childrenRefs);
93
+ };
94
+
95
+ render() {
96
+ const { title, children, closeCb, ...rest } = this.props;
97
+ const childrenWithProps = mapChildrenWithProps(children, this.childrenRefs, closeCb);
98
+ return (
99
+ <div className="dropdown__menu__section">
100
+ {title && <div className="dropdown__menu__section__title">{title}</div>}
101
+ <ul {...rest}>{childrenWithProps}</ul>
102
+ </div>
103
+ );
104
+ }
105
+ }
106
+
107
+ DropdownMenuSection.defaultProps = {
108
+ title: null,
109
+ closeCb: null,
110
+ };
111
+
112
+ DropdownMenuSection.propTypes = {
113
+ children: PropTypes.node.isRequired,
114
+ title: PropTypes.string,
115
+ closeCb: PropTypes.func,
116
+ };
117
+
72
118
  class DropdownSeparator extends Component {
73
119
  render() {
74
120
  const { testId } = this.props;
@@ -243,6 +289,28 @@ class Dropdown extends Component {
243
289
  this.setOpen(false);
244
290
  };
245
291
 
292
+ onMouseEnter = () => {
293
+ const { openOnMouseEnter } = this.props;
294
+ if (openOnMouseEnter) {
295
+ if (this.mouseLeaveTimeout) {
296
+ clearTimeout(this.mouseLeaveTimeout);
297
+ }
298
+ this.setOpen(true);
299
+ }
300
+ };
301
+
302
+ onMouseLeave = () => {
303
+ const { openOnMouseEnter } = this.props;
304
+ if (openOnMouseEnter) {
305
+ if (this.mouseLeaveTimeout) {
306
+ clearTimeout(this.mouseLeaveTimeout);
307
+ }
308
+ this.mouseLeaveTimeout = setTimeout(() => {
309
+ this.setOpen(false);
310
+ }, 500);
311
+ }
312
+ };
313
+
246
314
  onClick = (e) => {
247
315
  const { onClick } = this.props;
248
316
  const { open } = this.state;
@@ -273,6 +341,7 @@ class Dropdown extends Component {
273
341
  closeCb,
274
342
  disableCloseOutside,
275
343
  testId,
344
+ openOnMouseEnter,
276
345
  ...rest
277
346
  } = this.props;
278
347
  const { open } = this.state;
@@ -283,6 +352,8 @@ class Dropdown extends Component {
283
352
  ref={this.clickOutsideRef}
284
353
  className={cl('dropdown', { open }, `dropdown--${size}`, className)}
285
354
  {...rest}
355
+ onMouseEnter={this.onMouseEnter}
356
+ onMouseLeave={this.onMouseLeave}
286
357
  >
287
358
  <Button
288
359
  disabled={disabled}
@@ -311,6 +382,7 @@ Dropdown.defaultProps = {
311
382
  label: null,
312
383
  className: null,
313
384
  closeCb: null,
385
+ openOnMouseEnter: false,
314
386
  btnFace: 'link-flex',
315
387
  btnFaceIcon: 'caret-bottom',
316
388
  btnFaceIconSize: 24,
@@ -323,7 +395,8 @@ Dropdown.propTypes = {
323
395
  onDoubleClick: PropTypes.func,
324
396
  disabled: PropTypes.bool,
325
397
  disableCloseOutside: PropTypes.bool,
326
- label: PropTypes.string,
398
+ openOnMouseEnter: PropTypes.bool,
399
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
327
400
  testId: PropTypes.string,
328
401
  children: PropTypes.node.isRequired,
329
402
  className: PropTypes.string,
@@ -336,6 +409,7 @@ Dropdown.propTypes = {
336
409
 
337
410
  export default Object.assign(withClickOutside(Dropdown, '.popover__content'), {
338
411
  Menu: DropdownMenu,
412
+ MenuSection: DropdownMenuSection,
339
413
  Item: DropdownItem,
340
414
  ButtonItem: DropdownButtonItem,
341
415
  ConfirmButtonItem: DropdownConfirmButtonItem,
@@ -52,3 +52,32 @@ const Template = () => (
52
52
  );
53
53
  export const Default = Template.bind({});
54
54
  Default.args = {};
55
+
56
+ const MultiMenuTemplate = () => (
57
+ <div className="mbi" id="menuid">
58
+ <Dropdown btnFace="link" btnFaceIcon="clock" label="My dropdown" openOnMouseEnter>
59
+ <Dropdown.Menu isSection direction="se">
60
+ <Dropdown.MenuSection title="Section title">
61
+ <Dropdown.ButtonItem btnIcon="delete" isImportant btnLabel="Delete" />
62
+ <Dropdown.ConfirmButtonItem
63
+ btnIcon="delete"
64
+ isImportant
65
+ btnLabel="Delete confirm"
66
+ onConfirm={() => null}
67
+ />
68
+ </Dropdown.MenuSection>
69
+ <Dropdown.MenuSection title="Section title 2">
70
+ <Dropdown.ButtonItem btnIcon="delete" isImportant btnLabel="Delete" />
71
+ <Dropdown.ConfirmButtonItem
72
+ btnIcon="delete"
73
+ isImportant
74
+ btnLabel="Delete confirm"
75
+ onConfirm={() => null}
76
+ />
77
+ </Dropdown.MenuSection>
78
+ </Dropdown.Menu>
79
+ </Dropdown>
80
+ </div>
81
+ );
82
+ export const MultiMenuTemplateDropdown = MultiMenuTemplate.bind({});
83
+ MultiMenuTemplateDropdown.args = {};
@@ -50,9 +50,12 @@
50
50
  right 0
51
51
  left auto
52
52
 
53
+
53
54
  &.open
54
55
  .dropdown__menu
55
56
  display block
57
+ &--section
58
+ display flex
56
59
 
57
60
  &__item
58
61
  flexMiddle()
@@ -16,6 +16,7 @@ import { ReactComponent as ArrowBotton } from '../../icons/arrow-bottom.svg';
16
16
  import { ReactComponent as ArrowLeft } from '../../icons/arrow-left.svg';
17
17
  import { ReactComponent as ArrowRight } from '../../icons/arrow-right.svg';
18
18
  import { ReactComponent as ArrowTop } from '../../icons/arrow-top.svg';
19
+ import { ReactComponent as ArrowLink } from '../../icons/arrow-link.svg';
19
20
  import { ReactComponent as Asset } from '../../icons/asset.svg';
20
21
  import { ReactComponent as AssetAddFile } from '../../icons/asset-add-file.svg';
21
22
  import { ReactComponent as AssetAdd } from '../../icons/asset-add.svg';
@@ -134,6 +135,7 @@ const Icons = {
134
135
  'arrow-left': ArrowLeft,
135
136
  'arrow-right': ArrowRight,
136
137
  'arrow-top': ArrowTop,
138
+ 'arrow-link': ArrowLink,
137
139
  asset: Asset,
138
140
  'asset-add-file': AssetAddFile,
139
141
  'asset-add': AssetAdd,
@@ -15,6 +15,10 @@ class Modal extends Component {
15
15
  this.modalRef = React.createRef();
16
16
  }
17
17
 
18
+ componentDidMount() {
19
+ this.handleEscFunction('add');
20
+ }
21
+
18
22
  componentDidUpdate() {
19
23
  const { isLoading } = this.state;
20
24
  const { hasErrors } = this.props;
@@ -23,6 +27,10 @@ class Modal extends Component {
23
27
  }
24
28
  }
25
29
 
30
+ componentWillUnmount() {
31
+ this.handleEscFunction();
32
+ }
33
+
26
34
  handleClickOutside = () => {
27
35
  this.close();
28
36
  };
@@ -31,6 +39,20 @@ class Modal extends Component {
31
39
  this.setState({ isLoading: loading });
32
40
  }
33
41
 
42
+ handleEscFunction = (key) => {
43
+ const { closesWithEscape } = this.props;
44
+ if (closesWithEscape) {
45
+ if (key === 'add') document.addEventListener('keydown', this.escFunction, false);
46
+ else document.removeEventListener('keydown', this.escFunction, false);
47
+ }
48
+ };
49
+
50
+ escFunction = (event) => {
51
+ if (event.keyCode === 27) {
52
+ this.close();
53
+ }
54
+ };
55
+
34
56
  open = () => {
35
57
  this.setState({ open: true });
36
58
  };
@@ -141,6 +163,7 @@ Modal.defaultProps = {
141
163
  validateBtnProps: {},
142
164
  mainButtonType: 'primary',
143
165
  testId: 'modal',
166
+ closesWithEscape: false,
144
167
  };
145
168
 
146
169
  Modal.propTypes = {
@@ -160,6 +183,7 @@ Modal.propTypes = {
160
183
  validateBtnProps: PropTypes.object,
161
184
  mainButtonType: PropTypes.string,
162
185
  testId: PropTypes.string,
186
+ closesWithEscape: PropTypes.bool,
163
187
  };
164
188
 
165
189
  export default withClickOutside(Modal, '.ignore-click-outside-modal');
@@ -78,3 +78,15 @@ const TemplateConfirmationModal = () => {
78
78
  };
79
79
  export const ModalConfirmation = TemplateConfirmationModal.bind({});
80
80
  Default.args = {};
81
+
82
+ const TemplateEscapeModal = () => {
83
+ const modalRef = React.createRef();
84
+ return (
85
+ <div style={{ paddingLeft: '50px' }}>
86
+ <Button onClick={() => modalRef.current.open()} label="Open modal" />
87
+ <Modal ref={modalRef} closesWithEscape title="My modal" />
88
+ </div>
89
+ );
90
+ };
91
+ export const ModalEscape = TemplateEscapeModal.bind({});
92
+ Default.args = {};
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom/extend-expect';
4
4
  import { act } from 'react-dom/test-utils';
5
5
  import userEvent from '@testing-library/user-event';
@@ -56,7 +56,7 @@ it('is hidden after clicking cancel', () => {
56
56
  }
57
57
  });
58
58
 
59
- it('is hidden after clicking cancel', () => {
59
+ it('is hidden after clicking validate', () => {
60
60
  const modalRef = React.createRef();
61
61
  const validateCb = jest.fn();
62
62
  render(
@@ -72,3 +72,19 @@ it('is hidden after clicking cancel', () => {
72
72
  userEvent.click(validate);
73
73
  expect(validateCb.mock.calls.length).toBe(1);
74
74
  });
75
+
76
+ it('is closing after escaping if it is set to be closed with it', () => {
77
+ const modalRef = React.createRef();
78
+ const closeCb = jest.fn();
79
+ render(
80
+ <Modal ref={modalRef} closesWithEscape closeCb={closeCb}>
81
+ content
82
+ </Modal>
83
+ );
84
+ act(() => {
85
+ modalRef.current.open();
86
+ });
87
+ expect(closeCb).not.toHaveBeenCalled();
88
+ fireEvent.keyDown(document, { keyCode: 27 });
89
+ expect(closeCb).toHaveBeenCalled();
90
+ });
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m20.217 11.6-4.708-4.7a.56.56 0 1 0-.792.792l3.753 3.748H4.179a.56.56 0 1 0 0 1.12H18.47l-3.753 3.752a.56.56 0 0 0 .792.792l4.708-4.704a.559.559 0 0 0 0-.8Z"/></svg>
@@ -256,8 +256,12 @@
256
256
  padding 0
257
257
  text-decoration none
258
258
  &.btn--has-icon
259
- .icon
260
- margin-right .3rem
259
+ &:not(.btn--icon-right)
260
+ .icon
261
+ margin-right .3rem
262
+ &.btn--icon-right
263
+ .icon
264
+ margin-left .3rem
261
265
  &--link
262
266
  &--link-primary
263
267
  &--link-info
@@ -265,15 +269,28 @@
265
269
  trans()
266
270
  position relative
267
271
  top .7rem
268
- left -.3rem
269
- &.btn--small
272
+ &:not(.btn--icon-right)
270
273
  .icon
271
- top .5rem
272
274
  left -.3rem
273
- &.btn--large
275
+ &.btn--small
276
+ .icon
277
+ top .5rem
278
+ left -.3rem
279
+ &.btn--large
280
+ .icon
281
+ top 1.2rem
282
+ left -.3rem
283
+ &.btn--icon-right
274
284
  .icon
275
- top 1.2rem
276
- left -.3rem
285
+ right -.3rem
286
+ &.btn--small
287
+ .icon
288
+ top .5rem
289
+ rigth -.3rem
290
+ &.btn--large
291
+ .icon
292
+ top 1.2rem
293
+ rigth -.3rem
277
294
  &--link-flex
278
295
  &--link-flex-info
279
296
  display flex
@@ -340,8 +357,11 @@
340
357
  trans()
341
358
 
342
359
  &--has-icon
343
- &:not(.btn--icon):not(.btn--link):not(.btn--link-primary):not(.btn--link-info):not(.btn--link-flex):not(.btn--link-flex-info):not(.btn--loading):not(.btn--mini):not(.btn--small):not(.btn--large):not(.btn--dropdown-item):not(.btn--tab):not(.btn--sidebar-link)
360
+ &:not(.btn--icon):not(.btn--link):not(.btn--link-primary):not(.btn--link-info):not(.btn--link-flex):not(.btn--link-flex-info):not(.btn--loading):not(.btn--mini):not(.btn--small):not(.btn--large):not(.btn--dropdown-item):not(.btn--tab):not(.btn--sidebar-link):not(.btn--icon-right)
344
361
  padding 0 $padding 0 $padding - .6
362
+ &:not(.btn--icon):not(.btn--link):not(.btn--link-primary):not(.btn--link-info):not(.btn--link-flex):not(.btn--link-flex-info):not(.btn--loading):not(.btn--mini):not(.btn--small):not(.btn--large):not(.btn--dropdown-item):not(.btn--tab):not(.btn--sidebar-link)
363
+ &.btn--icon-right
364
+ padding 0 $padding - .6 0 $padding
345
365
  &--loading
346
366
  &:not(.btn--icon):not(.btn--mini):not(.btn--small):not(.btn--large):not(.btn--sidebar-link)
347
367
  padding-left $padding - .4