@woosmap/ui 3.118.0 → 3.121.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": "3.118.0",
3
+ "version": "3.121.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/WebGeoServices/ui.git"
@@ -5,14 +5,15 @@ import Input from '../Input/Input';
5
5
  import Button from '../Button/Button';
6
6
  import Label from '../Label/Label';
7
7
  import { tr } from '../utils/locale';
8
+ import Select from '../Select/Select';
8
9
 
9
10
  export default class DynamicTag extends Component {
10
11
  constructor(props) {
11
12
  super(props);
12
- const { defaultTags } = props;
13
+ const { defaultTags, isSelector } = props;
13
14
  this.state = {
14
15
  inputValue: '',
15
- tags: defaultTags,
16
+ tags: isSelector ? defaultTags.map((tag) => tag.value) : defaultTags,
16
17
  error: null,
17
18
  };
18
19
  }
@@ -43,6 +44,10 @@ export default class DynamicTag extends Component {
43
44
  });
44
45
  }
45
46
 
47
+ handleChange = (tags) => {
48
+ this.setState({ tags: tags.map((tag) => tag.value) });
49
+ };
50
+
46
51
  getTags = () => {
47
52
  const { tags } = this.state;
48
53
  return tags;
@@ -50,9 +55,23 @@ export default class DynamicTag extends Component {
50
55
 
51
56
  renderInput = () => {
52
57
  const { inputValue, error } = this.state;
53
- const { placeholder, testId } = this.props;
58
+ const { placeholder, testId, isSelector, label, options, defaultTags, color } = this.props;
54
59
 
55
- return (
60
+ return isSelector ? (
61
+ <Select
62
+ aria-label={`${testId}-select`}
63
+ color={color}
64
+ label={label}
65
+ isMulti
66
+ isCreatable
67
+ options={options}
68
+ closeMenuOnSelect={false}
69
+ noOptionsMessage={() => tr('No options found')}
70
+ placeholder={placeholder}
71
+ defaultValue={defaultTags}
72
+ onChange={this.handleChange}
73
+ />
74
+ ) : (
56
75
  <>
57
76
  <Input
58
77
  placeholder={placeholder}
@@ -70,24 +89,26 @@ export default class DynamicTag extends Component {
70
89
 
71
90
  render() {
72
91
  const { tags } = this.state;
73
- const { color, testId } = this.props;
92
+ const { color, testId, isSelector } = this.props;
74
93
  return (
75
94
  <div className={cl('dynamic-tag')} data-testid={testId}>
76
95
  <div className={cl('dynamic-tag__input')}>{this.renderInput()}</div>
77
- <div className={cl('dynamic-tag__tags')}>
78
- {tags.map((item) => (
79
- <Label
80
- color={color}
81
- key={`label_${item}`}
82
- closable
83
- label={item}
84
- closeCb={() => {
85
- this.handleTagRemove(item);
86
- }}
87
- testId={`${testId}-label`}
88
- />
89
- ))}
90
- </div>
96
+ {!isSelector && (
97
+ <div className={cl('dynamic-tag__tags')}>
98
+ {tags.map((item) => (
99
+ <Label
100
+ color={color}
101
+ key={`label_${item}`}
102
+ closable
103
+ label={item}
104
+ closeCb={() => {
105
+ this.handleTagRemove(item);
106
+ }}
107
+ testId={`${testId}-label`}
108
+ />
109
+ ))}
110
+ </div>
111
+ )}
91
112
  </div>
92
113
  );
93
114
  }
@@ -98,6 +119,7 @@ DynamicTag.defaultProps = {
98
119
  placeholder: '',
99
120
  color: '',
100
121
  testId: 'dynamic-tag',
122
+ isSelector: false,
101
123
  };
102
124
 
103
125
  DynamicTag.propTypes = {
@@ -105,4 +127,6 @@ DynamicTag.propTypes = {
105
127
  placeholder: PropTypes.string,
106
128
  color: PropTypes.string,
107
129
  testId: PropTypes.string,
130
+ isSelector: PropTypes.bool,
131
+ ...Select.propTypes,
108
132
  };
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import DynamicTag from './DynamicTag';
3
+
4
+ const Story = {
5
+ title: 'base/DynamicTag',
6
+ component: DynamicTag,
7
+ };
8
+
9
+ export default Story;
10
+
11
+ const Template = (arg) => {
12
+ const { isSelector } = arg;
13
+ const options = [
14
+ { value: 'java', label: 'Java' },
15
+ { value: 'swift', label: 'Swift' },
16
+ { value: 'js', label: 'Js' },
17
+ {
18
+ value: 'python',
19
+ label: 'Python',
20
+ },
21
+ ];
22
+ return (
23
+ <div>
24
+ <DynamicTag
25
+ color="mauve"
26
+ defaultTags={
27
+ isSelector
28
+ ? [
29
+ { label: 'Js', value: 'js' },
30
+ {
31
+ label: 'Python',
32
+ value: 'python',
33
+ },
34
+ ]
35
+ : ['js', 'python']
36
+ }
37
+ isSelector={isSelector}
38
+ isMulti
39
+ options={options}
40
+ />
41
+ </div>
42
+ );
43
+ };
44
+ export const Default = Template.bind({});
45
+ Default.args = {
46
+ isSelector: false,
47
+ };
@@ -1,11 +1,21 @@
1
1
  import React from 'react';
2
- import { render, screen, getByRole } from '@testing-library/react';
2
+ import { render, screen, getByRole, fireEvent } from '@testing-library/react';
3
3
  import { act } from 'react-dom/test-utils';
4
4
  import '@testing-library/jest-dom/extend-expect';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
7
  import DynamicTag from './DynamicTag';
8
8
 
9
+ const options = [
10
+ { value: 'java', label: 'java' },
11
+ { value: 'swift', label: 'swift' },
12
+ { value: 'js', label: 'js' },
13
+ {
14
+ value: 'python',
15
+ label: 'python',
16
+ },
17
+ ];
18
+
9
19
  it('renders a DynamicTag component with default tags', () => {
10
20
  const { container } = render(<DynamicTag defaultTags={['tag1', 'tag 2']} />);
11
21
  expect(container.firstChild).toHaveClass('dynamic-tag');
@@ -68,3 +78,37 @@ it('cant add the same value twice', () => {
68
78
  expect(screen.getByTestId('input-error')).toHaveTextContent('The value you’ve entered is already in the list');
69
79
  expect(ref.current.getTags()).toEqual(['mytag']);
70
80
  });
81
+
82
+ it(
83
+ 'should render DynamicTag with Select behavior and default tags and ' +
84
+ 'should add a new option when enter a value not present in options',
85
+ () => {
86
+ const ref = React.createRef();
87
+ const { container } = render(
88
+ <DynamicTag
89
+ color="mauve"
90
+ defaultTags={[
91
+ { label: 'js', value: 'js' },
92
+ {
93
+ label: 'python',
94
+ value: 'python',
95
+ },
96
+ ]}
97
+ isSelector
98
+ isMulti
99
+ options={options}
100
+ ref={ref}
101
+ />
102
+ );
103
+ const select = screen.getByLabelText('dynamic-tag-select');
104
+ expect(select).toBeInTheDocument();
105
+ expect(ref.current.getTags()).toEqual(['js', 'python']);
106
+ fireEvent.change(select, { target: { value: 'c' } });
107
+ fireEvent.keyDown(select, { key: 'Enter', code: 'Enter', charCode: 13 });
108
+ expect(ref.current.getTags()).toEqual(['js', 'python', 'c']);
109
+ fireEvent.click(select);
110
+ expect(screen.getByText('java')).toBeInTheDocument();
111
+ expect(screen.getByText('swift')).toBeInTheDocument();
112
+ expect(container.querySelectorAll('div.select__item--mauve')[0]).toBeInTheDocument();
113
+ }
114
+ );
@@ -6,6 +6,7 @@ import Animate, { AnimatePresence } from '../Animate/Animate';
6
6
  import Button from '../Button/Button';
7
7
  import withClickOutside from '../withClickOutside/withClickOutside';
8
8
  import { tr } from '../utils/locale';
9
+ import Icon from '../Icon/Icon';
9
10
 
10
11
  class Modal extends Component {
11
12
  constructor(props) {
@@ -13,6 +14,7 @@ class Modal extends Component {
13
14
  const { defaultIsOpen } = this.props;
14
15
  this.state = { open: defaultIsOpen, isLoading: false };
15
16
  this.modalRef = React.createRef();
17
+ this.clickOutsideRef = null;
16
18
  }
17
19
 
18
20
  componentDidMount() {
@@ -72,6 +74,11 @@ class Modal extends Component {
72
74
  }
73
75
  };
74
76
 
77
+ displayCloseButton = () => {
78
+ const { isFull, disableCloseOutside, displayCloseButtonOutside } = this.props;
79
+ return !isFull && !disableCloseOutside && displayCloseButtonOutside;
80
+ };
81
+
75
82
  renderFooter = () => {
76
83
  const { footer, labelValidate, errorLabel, validateCb, validateBtnProps, mainButtonType, testId } = this.props;
77
84
  const { isLoading } = this.state;
@@ -107,10 +114,19 @@ class Modal extends Component {
107
114
  return <div className="modal__footer">{footer}</div>;
108
115
  };
109
116
 
117
+ getRef = () => {
118
+ if (this.displayCloseButton()) {
119
+ this.clickOutsideRef = this.modalRef;
120
+ } else {
121
+ this.clickOutsideRef = null;
122
+ }
123
+ return this.modalRef;
124
+ };
125
+
110
126
  render() {
111
127
  const { open } = this.state;
112
128
 
113
- const { title, children, className, isFull, header, disableCloseOutside, testId } = this.props;
129
+ const { title, children, className, isFull, header, testId } = this.props;
114
130
  const tit = open && title;
115
131
  return ReactDOM.createPortal(
116
132
  <AnimatePresence>
@@ -122,19 +138,13 @@ class Modal extends Component {
122
138
  data-testid={testId}
123
139
  >
124
140
  <Animate className="modal__wrapper">
125
- <div ref={this.modalRef} className="modal__content">
126
- <div className="modal__header">
127
- {header || <h1 className="title">{tit}</h1>}
128
- {!disableCloseOutside && (
129
- <Button
130
- testId={`${testId}-close-button`}
131
- className="modal__close"
132
- icon="close"
133
- type="link-flex"
134
- onClick={this.close}
135
- />
136
- )}
141
+ {this.displayCloseButton() && (
142
+ <div className="modal__close--outside">
143
+ <Icon size={30} icon="close" />
137
144
  </div>
145
+ )}
146
+ <div ref={this.getRef()} className="modal__content">
147
+ <div className="modal__header">{header || <h1 className="title">{tit}</h1>}</div>
138
148
  <div className="modal__body">{children}</div>
139
149
  {this.renderFooter()}
140
150
  </div>
@@ -164,6 +174,7 @@ Modal.defaultProps = {
164
174
  mainButtonType: 'primary',
165
175
  testId: 'modal',
166
176
  closesWithEscape: false,
177
+ displayCloseButtonOutside: false,
167
178
  };
168
179
 
169
180
  Modal.propTypes = {
@@ -184,6 +195,7 @@ Modal.propTypes = {
184
195
  mainButtonType: PropTypes.string,
185
196
  testId: PropTypes.string,
186
197
  closesWithEscape: PropTypes.bool,
198
+ displayCloseButtonOutside: PropTypes.bool,
187
199
  };
188
200
 
189
201
  export default withClickOutside(Modal, '.ignore-click-outside-modal');
@@ -12,11 +12,13 @@ export default Story;
12
12
 
13
13
  const Template = () => {
14
14
  const modalRef = React.createRef();
15
+
15
16
  function open() {
16
17
  if (modalRef.current) {
17
18
  modalRef.current.open();
18
19
  }
19
20
  }
21
+
20
22
  function validate() {
21
23
  if (modalRef.current) {
22
24
  setTimeout(() => {
@@ -24,6 +26,7 @@ const Template = () => {
24
26
  }, 2000);
25
27
  }
26
28
  }
29
+
27
30
  return (
28
31
  <div style={{ paddingLeft: '50px' }}>
29
32
  <Button onClick={open} label="Open modal" />
@@ -84,9 +87,25 @@ const TemplateEscapeModal = () => {
84
87
  return (
85
88
  <div style={{ paddingLeft: '50px' }}>
86
89
  <Button onClick={() => modalRef.current.open()} label="Open modal" />
87
- <Modal ref={modalRef} closesWithEscape title="My modal" />
90
+ <Modal ref={modalRef} closesWithEscape title="My modal">
91
+ content
92
+ </Modal>
88
93
  </div>
89
94
  );
90
95
  };
91
96
  export const ModalEscape = TemplateEscapeModal.bind({});
92
97
  Default.args = {};
98
+
99
+ const TemplateClickOutsideModal = () => {
100
+ const modalRef = React.createRef();
101
+ return (
102
+ <div style={{ paddingLeft: '50px' }}>
103
+ <Button onClick={() => modalRef.current.open()} label="Open modal" />
104
+ <Modal ref={modalRef} displayCloseButtonOutside title="My modal">
105
+ content
106
+ </Modal>
107
+ </div>
108
+ );
109
+ };
110
+ export const ModalClickOutside = TemplateClickOutsideModal.bind({});
111
+ Default.args = {};
@@ -37,6 +37,14 @@
37
37
  overflow-y auto
38
38
  .modal--full &
39
39
  padding 0
40
+ &__close
41
+ &--outside
42
+ position fixed
43
+ top 2rem
44
+ right 2rem
45
+ cursor pointer
46
+ .icon
47
+ fill $light
40
48
  &__footer
41
49
  &__header
42
50
  padding 1.8rem 2.4rem
@@ -17,25 +17,6 @@ it('renders a Modal component ', () => {
17
17
  expect(modal).toHaveClass('modal');
18
18
  });
19
19
 
20
- it('is hidden after clicking close', () => {
21
- const modalRef = React.createRef();
22
- render(<Modal ref={modalRef}>content</Modal>);
23
- act(() => {
24
- modalRef.current.open();
25
- });
26
- const modal = screen.getByTestId('modal');
27
- expect(modal).toHaveClass('modal');
28
-
29
- const close = screen.getByTestId('modal-close-button');
30
- userEvent.click(close);
31
- try {
32
- screen.getByTestId('modal');
33
- expect('Modal displayed').toBeFalsy();
34
- } catch (e) {
35
- // nothink
36
- }
37
- });
38
-
39
20
  it('is hidden after clicking cancel', () => {
40
21
  const modalRef = React.createRef();
41
22
  render(
@@ -88,3 +69,22 @@ it('is closing after escaping if it is set to be closed with it', () => {
88
69
  fireEvent.keyDown(document, { keyCode: 27 });
89
70
  expect(closeCb).toHaveBeenCalled();
90
71
  });
72
+
73
+ it('is closing after click outside if it is set to bet closed with it', () => {
74
+ const modalRef = React.createRef();
75
+ const closeCb = jest.fn();
76
+ render(
77
+ <Modal ref={modalRef} displayCloseButtonOutside closeCb={closeCb}>
78
+ content
79
+ </Modal>
80
+ );
81
+ act(() => {
82
+ modalRef.current.open();
83
+ });
84
+ expect(closeCb).not.toHaveBeenCalled();
85
+ fireEvent.mouseDown(screen.getByText('content'));
86
+ fireEvent.mouseDown(screen.getByText('Validate'));
87
+ expect(closeCb).not.toHaveBeenCalled();
88
+ fireEvent.mouseDown(screen.queryByTestId('icon-close'));
89
+ expect(closeCb).toHaveBeenCalled();
90
+ });
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
2
2
  import ReactSelect from 'react-select';
3
3
  import PropTypes from 'prop-types';
4
4
  import cl from 'classnames';
5
+ import CreatableSelect from 'react-select/creatable';
5
6
 
6
7
  /**
7
8
  * https://react-select.com/styles#styles
@@ -9,9 +10,17 @@ import cl from 'classnames';
9
10
  */
10
11
  export default class Select extends Component {
11
12
  renderInput = (props) => {
12
- const { error, ...rest } = props;
13
+ const { error, isCreatable, color, ...rest } = props;
13
14
 
14
- return (
15
+ return isCreatable ? (
16
+ <CreatableSelect
17
+ className={cl('select__item', color ? `select__item--${color}` : null, {
18
+ error,
19
+ })}
20
+ classNamePrefix="select"
21
+ {...rest}
22
+ />
23
+ ) : (
15
24
  <ReactSelect
16
25
  className={cl('select__item', {
17
26
  error,
@@ -56,6 +65,8 @@ Select.defaultProps = {
56
65
  icon: null,
57
66
  label: null,
58
67
  error: null,
68
+ isCreatable: false,
69
+ color: undefined,
59
70
  };
60
71
  Select.propTypes = {
61
72
  className: PropTypes.string,
@@ -64,4 +75,6 @@ Select.propTypes = {
64
75
  error: PropTypes.string,
65
76
  hideLabel: PropTypes.bool,
66
77
  icon: PropTypes.string,
78
+ isCreatable: PropTypes.bool,
79
+ color: PropTypes.oneOf(['bleu', 'mauve', 'green', 'grey', 'orange', 'red', 'white', undefined, '']),
67
80
  };
@@ -67,9 +67,20 @@ select
67
67
  background-color $primary15 !important
68
68
  color $secondary !important
69
69
  &__multi-value
70
+ display flex
71
+ align-items center
70
72
  background-color $primary !important
71
73
  border-radius .4rem !important
72
- flexMiddle()
74
+ .select__item--mauve &
75
+ background-color $labelMauve !important
76
+ .select__item--green &
77
+ background-color $labelGreen !important
78
+ .select__item--grey &
79
+ background-color $labelGrey !important
80
+ .select__item--orange &
81
+ background-color $labelOrange !important
82
+ .select__item--red &
83
+ background-color $labelRed !important
73
84
  &--is-disabled
74
85
  opacity .6
75
86
  &__label
@@ -105,6 +105,8 @@
105
105
  margin-bottom 2rem
106
106
  width 28.8rem
107
107
  height 20.6rem
108
+ img
109
+ fullwh()
108
110
  .title-container
109
111
  display flex !important
110
112
  align-items center
@@ -1,17 +0,0 @@
1
- import React from 'react';
2
- import DynamicTag from './DynamicTag';
3
-
4
- const Story = {
5
- title: 'base/DynamicTag',
6
- component: DynamicTag,
7
- };
8
-
9
- export default Story;
10
-
11
- const Template = () => (
12
- <div>
13
- <DynamicTag color="color1" defaultTags={['js', 'python']} />
14
- </div>
15
- );
16
- export const Default = Template.bind({});
17
- Default.args = {};