@comicrelief/component-library 8.10.0 → 8.10.2

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.
@@ -24,7 +24,7 @@ const KEY_CODE_ESCAPE = 27;
24
24
  *
25
25
  * See the Typeahead and SchoolLookup molecules for the full implementation
26
26
  */
27
- const TextInputWithDropdown = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
27
+ const TextInputWithDropdown = /*#__PURE__*/_react.default.forwardRef((_ref, forwardedRef) => {
28
28
  let {
29
29
  options,
30
30
  onChange,
@@ -34,14 +34,37 @@ const TextInputWithDropdown = /*#__PURE__*/_react.default.forwardRef((_ref, ref)
34
34
  label,
35
35
  dropdownInstruction = null,
36
36
  className = '',
37
+ hideBorder = false,
37
38
  ...otherInputProps
38
39
  } = _ref;
39
40
  const [activeOption, setActiveOption] = (0, _react.useState)(-1);
40
41
  const [forceClosed, setForceClosed] = (0, _react.useState)(false);
42
+ const dropdownRef = (0, _react.useRef)(null);
43
+ const containerRef = (0, _react.useRef)(null);
41
44
  (0, _react.useEffect)(() => {
42
- // reset if options change
45
+ const handleClickOutside = event => {
46
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target) && !containerRef.current.contains(event.target)) {
47
+ setForceClosed(true);
48
+ }
49
+ };
50
+
51
+ // Only add the listeners if we have options showing
52
+ if (options.length > 0 && !forceClosed) {
53
+ ['mousedown', 'touchstart'].forEach(event => document.addEventListener(event, handleClickOutside));
54
+ }
55
+ return () => {
56
+ ['mousedown', 'touchstart'].forEach(event => document.removeEventListener(event, handleClickOutside));
57
+ };
58
+ }, [options.length, forceClosed, onChange]);
59
+ const closeDropdown = () => {
60
+ setForceClosed(true);
43
61
  setActiveOption(-1);
62
+ };
63
+
64
+ // Reset forceClosed when options change
65
+ (0, _react.useEffect)(() => {
44
66
  setForceClosed(false);
67
+ setActiveOption(-1);
45
68
  }, [options]);
46
69
  const down = () => activeOption < options.length - 1 ? setActiveOption(activeOption + 1) : setActiveOption(0);
47
70
  const up = () => activeOption < 1 ? setActiveOption(options.length - 1) : setActiveOption(activeOption - 1);
@@ -74,24 +97,29 @@ const TextInputWithDropdown = /*#__PURE__*/_react.default.forwardRef((_ref, ref)
74
97
  onSelect,
75
98
  dropdownInstruction,
76
99
  activeOption,
100
+ closeDropdown,
77
101
  resetActiveOption: () => setActiveOption(-1)
78
102
  };
79
103
  return /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.Container, {
80
104
  className: "TextInputWithDropdown ".concat(className).trim(),
81
- onKeyDown: navigateOptions
105
+ onKeyDown: navigateOptions,
106
+ ref: containerRef
82
107
  }, /*#__PURE__*/_react.default.createElement(_Input.default, Object.assign({}, inputProps, {
83
108
  className: "TextInputWithDropdown__input",
84
- ref: ref
85
- })), options.length > 0 && forceClosed === false && /*#__PURE__*/_react.default.createElement(Options, Object.assign({}, optionsProps, {
86
- className: "TextInputWithDropdown__options"
109
+ ref: forwardedRef
110
+ })), options.length > 0 && !forceClosed && /*#__PURE__*/_react.default.createElement(Options, Object.assign({}, optionsProps, {
111
+ className: "TextInputWithDropdown__options",
112
+ ref: dropdownRef,
113
+ hideBorder: hideBorder
87
114
  })));
88
115
  });
89
- const Options = _ref2 => {
116
+ const Options = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
90
117
  let {
91
118
  options,
92
119
  dropdownInstruction,
93
120
  onSelect,
94
121
  activeOption,
122
+ closeDropdown,
95
123
  resetActiveOption,
96
124
  ...rest
97
125
  } = _ref2;
@@ -109,11 +137,15 @@ const Options = _ref2 => {
109
137
  }, 100);
110
138
  };
111
139
  return /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.Dropdown, rest, /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.DropdownList, {
140
+ ref: ref,
112
141
  role: "listbox",
113
142
  onBlur: onBlur,
114
143
  "aria-activedescendant": activeOption > -1 ? "option-".concat(activeOption) : undefined
115
- }, dropdownInstruction && /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.DropdownItem, {
116
- role: "option"
144
+ }, dropdownInstruction && /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.DropdownItemSelectable, {
145
+ id: "dropdown-instruction",
146
+ role: "option",
147
+ key: "dropdown-instruction",
148
+ onClick: closeDropdown
117
149
  }, /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.TextItalic, null, dropdownInstruction)), options.map((option, index) => /*#__PURE__*/_react.default.createElement(_TextInputWithDropdown.DropdownItemSelectable, {
118
150
  id: "option-".concat(index),
119
151
  role: "option",
@@ -129,5 +161,6 @@ const Options = _ref2 => {
129
161
  "aria-selected": index === activeOption,
130
162
  ref: index === activeOption ? element => element && element.focus() : null
131
163
  }, /*#__PURE__*/_react.default.createElement(_Text.default, null, option)))));
132
- };
164
+ });
165
+ TextInputWithDropdown.displayName = 'TextInputWithDropdown';
133
166
  var _default = exports.default = TextInputWithDropdown;
@@ -16,7 +16,7 @@ const Container = exports.Container = _styledComponents.default.div.withConfig({
16
16
  const Dropdown = exports.Dropdown = _styledComponents.default.div.withConfig({
17
17
  displayName: "TextInputWithDropdownstyle__Dropdown",
18
18
  componentId: "sc-1s4bv7m-1"
19
- })(["", " font-family:", ";position:absolute;left:0;max-height:300px;overflow:auto;background-color:", ";border:1px solid;margin-top:-1px;width:100%;@media ", "{max-width:500px;}"], (0, _zIndex.default)('high'), _ref => {
19
+ })(["", " font-family:", ";position:absolute;left:0;max-height:300px;overflow:auto;background-color:", ";border:", ";margin-top:-1px;width:100%;@media ", "{max-width:500px;}"], (0, _zIndex.default)('high'), _ref => {
20
20
  let {
21
21
  theme
22
22
  } = _ref;
@@ -28,8 +28,13 @@ const Dropdown = exports.Dropdown = _styledComponents.default.div.withConfig({
28
28
  return theme.color('white');
29
29
  }, _ref3 => {
30
30
  let {
31
- theme
31
+ hideBorder
32
32
  } = _ref3;
33
+ return hideBorder ? 'none' : '1px solid';
34
+ }, _ref4 => {
35
+ let {
36
+ theme
37
+ } = _ref4;
33
38
  return theme.allBreakpoints('M');
34
39
  });
35
40
  const DropdownList = exports.DropdownList = _styledComponents.default.ul.withConfig({
@@ -43,15 +48,15 @@ const DropdownItem = exports.DropdownItem = _styledComponents.default.li.withCon
43
48
  const DropdownItemSelectable = exports.DropdownItemSelectable = (0, _styledComponents.default)(DropdownItem).withConfig({
44
49
  displayName: "TextInputWithDropdownstyle__DropdownItemSelectable",
45
50
  componentId: "sc-1s4bv7m-4"
46
- })(["cursor:pointer;border-top:1px solid ", ";&:hover,&:focus{background-color:", ";}"], _ref4 => {
51
+ })(["cursor:pointer;border-top:1px solid ", ";&:hover,&:focus{background-color:", ";}"], _ref5 => {
47
52
  let {
48
53
  theme
49
- } = _ref4;
54
+ } = _ref5;
50
55
  return theme.color('grey_light');
51
- }, _ref5 => {
56
+ }, _ref6 => {
52
57
  let {
53
58
  theme
54
- } = _ref5;
59
+ } = _ref6;
55
60
  return theme.color('grey_light');
56
61
  });
57
62
  const TextItalic = exports.TextItalic = (0, _styledComponents.default)(_Text.default).withConfig({
@@ -25,6 +25,7 @@ const SchoolLookup = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
25
25
  dropdownInstruction = 'Please select a school from the list below',
26
26
  notFoundMessage = "Sorry, we can't find this school",
27
27
  onSelect,
28
+ hideBorder = false,
28
29
  ...rest
29
30
  } = _ref;
30
31
  const props = {
@@ -37,10 +38,12 @@ const SchoolLookup = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
37
38
  placeholder,
38
39
  notFoundMessage,
39
40
  dropdownInstruction,
41
+ hideBorder,
40
42
  ...rest
41
43
  };
42
44
  return /*#__PURE__*/_react.default.createElement(_Typeahead.default, Object.assign({}, props, {
43
45
  ref: ref
44
46
  }));
45
47
  });
48
+ SchoolLookup.displayName = 'SchoolLookup';
46
49
  var _default = exports.default = SchoolLookup;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comicrelief/component-library",
3
3
  "author": "Comic Relief Engineering Team",
4
- "version": "8.10.0",
4
+ "version": "8.10.2",
5
5
  "main": "dist/index.js",
6
6
  "license": "ISC",
7
7
  "jest": {
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  import Input from '../Input/Input';
@@ -7,7 +7,6 @@ import {
7
7
  Container,
8
8
  Dropdown,
9
9
  DropdownList,
10
- DropdownItem,
11
10
  DropdownItemSelectable,
12
11
  TextItalic
13
12
  } from './TextInputWithDropdown.style';
@@ -37,16 +36,44 @@ const TextInputWithDropdown = React.forwardRef(
37
36
  label,
38
37
  dropdownInstruction = null,
39
38
  className = '',
39
+ hideBorder = false,
40
40
  ...otherInputProps
41
41
  },
42
- ref
42
+ forwardedRef
43
43
  ) => {
44
44
  const [activeOption, setActiveOption] = useState(-1);
45
45
  const [forceClosed, setForceClosed] = useState(false);
46
+ const dropdownRef = useRef(null);
47
+ const containerRef = useRef(null);
48
+
46
49
  useEffect(() => {
47
- // reset if options change
50
+ const handleClickOutside = event => {
51
+ if (dropdownRef.current
52
+ && !dropdownRef.current.contains(event.target)
53
+ && !containerRef.current.contains(event.target)) {
54
+ setForceClosed(true);
55
+ }
56
+ };
57
+
58
+ // Only add the listeners if we have options showing
59
+ if (options.length > 0 && !forceClosed) {
60
+ ['mousedown', 'touchstart'].forEach(event => document.addEventListener(event, handleClickOutside));
61
+ }
62
+
63
+ return () => {
64
+ ['mousedown', 'touchstart'].forEach(event => document.removeEventListener(event, handleClickOutside));
65
+ };
66
+ }, [options.length, forceClosed, onChange]);
67
+
68
+ const closeDropdown = () => {
69
+ setForceClosed(true);
48
70
  setActiveOption(-1);
71
+ };
72
+
73
+ // Reset forceClosed when options change
74
+ useEffect(() => {
49
75
  setForceClosed(false);
76
+ setActiveOption(-1);
50
77
  }, [options]);
51
78
 
52
79
  const down = () => (activeOption < options.length - 1
@@ -89,6 +116,7 @@ const TextInputWithDropdown = React.forwardRef(
89
116
  onSelect,
90
117
  dropdownInstruction,
91
118
  activeOption,
119
+ closeDropdown,
92
120
  resetActiveOption: () => setActiveOption(-1)
93
121
  };
94
122
 
@@ -96,16 +124,19 @@ const TextInputWithDropdown = React.forwardRef(
96
124
  <Container
97
125
  className={`TextInputWithDropdown ${className}`.trim()}
98
126
  onKeyDown={navigateOptions}
127
+ ref={containerRef}
99
128
  >
100
129
  <Input
101
130
  {...inputProps}
102
131
  className="TextInputWithDropdown__input"
103
- ref={ref}
132
+ ref={forwardedRef}
104
133
  />
105
- {options.length > 0 && forceClosed === false && (
134
+ {options.length > 0 && !forceClosed && (
106
135
  <Options
107
136
  {...optionsProps}
108
137
  className="TextInputWithDropdown__options"
138
+ ref={dropdownRef}
139
+ hideBorder={hideBorder}
109
140
  />
110
141
  )}
111
142
  </Container>
@@ -113,14 +144,15 @@ const TextInputWithDropdown = React.forwardRef(
113
144
  }
114
145
  );
115
146
 
116
- const Options = ({
147
+ const Options = React.forwardRef(({
117
148
  options,
118
149
  dropdownInstruction,
119
150
  onSelect,
120
151
  activeOption,
152
+ closeDropdown,
121
153
  resetActiveOption,
122
154
  ...rest
123
- }) => {
155
+ }, ref) => {
124
156
  // Reset 'activeOption' when the list is unfocused.
125
157
  const onBlur = e => {
126
158
  const { target } = e;
@@ -136,6 +168,7 @@ const Options = ({
136
168
  return (
137
169
  <Dropdown {...rest}>
138
170
  <DropdownList
171
+ ref={ref}
139
172
  role="listbox"
140
173
  onBlur={onBlur}
141
174
  aria-activedescendant={
@@ -143,9 +176,16 @@ const Options = ({
143
176
  }
144
177
  >
145
178
  {dropdownInstruction && (
146
- <DropdownItem role="option">
147
- <TextItalic>{dropdownInstruction}</TextItalic>
148
- </DropdownItem>
179
+ <DropdownItemSelectable
180
+ id="dropdown-instruction"
181
+ role="option"
182
+ key="dropdown-instruction"
183
+ onClick={closeDropdown}
184
+ >
185
+ <TextItalic>
186
+ {dropdownInstruction}
187
+ </TextItalic>
188
+ </DropdownItemSelectable>
149
189
  )}
150
190
  {options.map((option, index) => (
151
191
  <DropdownItemSelectable
@@ -173,7 +213,7 @@ const Options = ({
173
213
  </DropdownList>
174
214
  </Dropdown>
175
215
  );
176
- };
216
+ });
177
217
 
178
218
  TextInputWithDropdown.propTypes = {
179
219
  options: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -183,7 +223,8 @@ TextInputWithDropdown.propTypes = {
183
223
  name: PropTypes.string.isRequired,
184
224
  label: PropTypes.string.isRequired,
185
225
  className: PropTypes.string,
186
- dropdownInstruction: PropTypes.string
226
+ dropdownInstruction: PropTypes.string,
227
+ hideBorder: PropTypes.bool
187
228
  };
188
229
 
189
230
  Options.propTypes = {
@@ -191,7 +232,11 @@ Options.propTypes = {
191
232
  onSelect: PropTypes.func.isRequired,
192
233
  dropdownInstruction: PropTypes.string,
193
234
  activeOption: PropTypes.number.isRequired,
194
- resetActiveOption: PropTypes.func.isRequired
235
+ resetActiveOption: PropTypes.func.isRequired,
236
+ hideBorder: PropTypes.bool,
237
+ closeDropdown: PropTypes.func
195
238
  };
196
239
 
240
+ TextInputWithDropdown.displayName = 'TextInputWithDropdown';
241
+
197
242
  export default TextInputWithDropdown;
@@ -16,7 +16,7 @@ const Dropdown = styled.div`
16
16
  max-height: 300px;
17
17
  overflow: auto;
18
18
  background-color: ${({ theme }) => theme.color('white')};
19
- border: 1px solid;
19
+ border: ${({ hideBorder }) => (hideBorder ? 'none' : '1px solid')};
20
20
  margin-top: -1px;
21
21
  width: 100%;
22
22
 
@@ -25,6 +25,7 @@ const SchoolLookup = React.forwardRef(
25
25
  dropdownInstruction = 'Please select a school from the list below',
26
26
  notFoundMessage = "Sorry, we can't find this school",
27
27
  onSelect,
28
+ hideBorder = false,
28
29
  ...rest
29
30
  },
30
31
  ref
@@ -39,6 +40,7 @@ const SchoolLookup = React.forwardRef(
39
40
  placeholder,
40
41
  notFoundMessage,
41
42
  dropdownInstruction,
43
+ hideBorder,
42
44
  ...rest
43
45
  };
44
46
 
@@ -52,7 +54,10 @@ SchoolLookup.propTypes = {
52
54
  label: PropTypes.string,
53
55
  placeholder: PropTypes.string,
54
56
  dropdownInstruction: PropTypes.string,
55
- notFoundMessage: PropTypes.string
57
+ notFoundMessage: PropTypes.string,
58
+ hideBorder: PropTypes.bool
56
59
  };
57
60
 
61
+ SchoolLookup.displayName = 'SchoolLookup';
62
+
58
63
  export default SchoolLookup;