@comicrelief/component-library 5.6.0 → 5.7.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 (27) hide show
  1. package/.github/workflows/main.yml +131 -0
  2. package/.github/workflows/pr-checks.yml +17 -0
  3. package/README.md +6 -6
  4. package/dist/components/Atoms/ButtonWithStates/ButtonWithStates.js +91 -0
  5. package/dist/components/Atoms/ButtonWithStates/ButtonWithStates.md +13 -0
  6. package/dist/components/Atoms/Link/Link.js +17 -4
  7. package/dist/components/Molecules/Lookup/Lookup.js +201 -0
  8. package/dist/components/Molecules/SimpleSchoolLookup/SimpleSchoolLookup.js +112 -0
  9. package/dist/components/Molecules/SimpleSchoolLookup/SimpleSchoolLookup.md +7 -0
  10. package/dist/components/Molecules/SingleMessage/__snapshots__/SingleMessage.test.js.snap +8 -0
  11. package/dist/components/Organisms/Footer/Footer.js +4 -3
  12. package/dist/components/Organisms/Footer/Footer.md +8 -1
  13. package/dist/components/Organisms/Footer/Nav/Nav.js +10 -5
  14. package/dist/index.js +24 -0
  15. package/package.json +2 -1
  16. package/src/components/Atoms/ButtonWithStates/ButtonWithStates.js +64 -0
  17. package/src/components/Atoms/ButtonWithStates/ButtonWithStates.md +13 -0
  18. package/src/components/Atoms/Link/Link.js +12 -4
  19. package/src/components/Molecules/Lookup/Lookup.js +148 -0
  20. package/src/components/Molecules/SimpleSchoolLookup/SimpleSchoolLookup.js +63 -0
  21. package/src/components/Molecules/SimpleSchoolLookup/SimpleSchoolLookup.md +7 -0
  22. package/src/components/Molecules/SingleMessage/__snapshots__/SingleMessage.test.js.snap +8 -0
  23. package/src/components/Organisms/Footer/Footer.js +5 -3
  24. package/src/components/Organisms/Footer/Footer.md +8 -1
  25. package/src/components/Organisms/Footer/Nav/Nav.js +3 -2
  26. package/src/index.js +3 -0
  27. package/.circleci/config.yml +0 -54
@@ -896,6 +896,10 @@ exports[`renders Single Message with full width image and no text correctly 1`]
896
896
  z-index: 1;
897
897
  }
898
898
 
899
+ @media (min-width:740px) {
900
+
901
+ }
902
+
899
903
  @media (min-width:740px) {
900
904
  .c0 {
901
905
  -webkit-flex-direction: row;
@@ -1010,6 +1014,10 @@ exports[`renders Single Message with no Image correctly 1`] = `
1010
1014
  padding: 1rem;
1011
1015
  }
1012
1016
 
1017
+ @media (min-width:740px) {
1018
+
1019
+ }
1020
+
1013
1021
  @media (min-width:740px) {
1014
1022
  .c0 {
1015
1023
  -webkit-flex-direction: row;
@@ -42,9 +42,9 @@ var Footer = function Footer(_ref) {
42
42
  sizeMd: "72px",
43
43
  rotate: false,
44
44
  campaign: campaign
45
- }))), /*#__PURE__*/_react.default.createElement(_Nav.default, {
45
+ }))), /*#__PURE__*/_react.default.createElement(_Nav.default, Object.assign({
46
46
  navItems: navItems
47
- }), /*#__PURE__*/_react.default.createElement(_Footer.FooterCopyright, null, /*#__PURE__*/_react.default.createElement(_Text.default, {
47
+ }, rest)), /*#__PURE__*/_react.default.createElement(_Footer.FooterCopyright, null, /*#__PURE__*/_react.default.createElement(_Text.default, {
48
48
  tag: "p",
49
49
  color: "grey"
50
50
  }, footerCopy)))));
@@ -53,7 +53,8 @@ var Footer = function Footer(_ref) {
53
53
  Footer.defaultProps = {
54
54
  navItems: {},
55
55
  footerCopy: '',
56
- campaign: 'Comic Relief'
56
+ campaign: 'Comic Relief',
57
+ overrideWhiteList: false
57
58
  };
58
59
  var _default = Footer;
59
60
  exports.default = _default;
@@ -4,5 +4,12 @@
4
4
  import data from './data/data';
5
5
  import footerCopy from './data/footerCopy';
6
6
 
7
- <Footer navItems={data} footerCopy={footerCopy.copy} campaign="Comic Relief" />;
7
+ <>
8
+ <p>Standard footer</p>
9
+ <Footer navItems={data} footerCopy={footerCopy.copy} campaign="Comic Relief" />
10
+
11
+ <p>Overrides whitelist functionality for external usage</p>
12
+ <Footer navItems={data} footerCopy={footerCopy.copy} campaign="Comic Relief" overrideWhiteList />
13
+ </>
14
+
8
15
  ```
@@ -11,6 +11,8 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/es
11
11
 
12
12
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/slicedToArray"));
13
13
 
14
+ var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectWithoutProperties"));
15
+
14
16
  var _react = _interopRequireWildcard(require("react"));
15
17
 
16
18
  var _Text = _interopRequireDefault(require("../../../Atoms/Text/Text"));
@@ -23,12 +25,15 @@ var _internalLinkHelper = require("../../../../utils/internalLinkHelper");
23
25
 
24
26
  var _Nav = require("./Nav.style");
25
27
 
28
+ var _excluded = ["navItems"];
29
+
26
30
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
27
31
 
28
32
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
29
33
 
30
34
  var FooterNav = function FooterNav(_ref) {
31
- var navItems = _ref.navItems;
35
+ var navItems = _ref.navItems,
36
+ rest = (0, _objectWithoutProperties2.default)(_ref, _excluded);
32
37
  var menuGroups = navItems.menuGroups;
33
38
 
34
39
  var _useState = (0, _react.useState)(false),
@@ -96,14 +101,14 @@ var FooterNav = function FooterNav(_ref) {
96
101
  }, !isSmallBreakpoint ? /*#__PURE__*/_react.default.createElement(_Text.default, {
97
102
  color: "white",
98
103
  weight: "bold"
99
- }, group.title) : /*#__PURE__*/_react.default.createElement(_Nav.NavLink, {
104
+ }, group.title) : /*#__PURE__*/_react.default.createElement(_Nav.NavLink, Object.assign({
100
105
  href: "#",
101
106
  inline: true,
102
107
  "aria-expanded": !!isSubMenuOpen[group.id],
103
108
  "aria-haspopup": "true",
104
109
  role: "button",
105
110
  onClick: toggleSubMenu(group.id)
106
- }, /*#__PURE__*/_react.default.createElement(_Text.default, {
111
+ }, rest), /*#__PURE__*/_react.default.createElement(_Text.default, {
107
112
  color: "white"
108
113
  }, group.title)), group.links && group.links.length > 0 && /*#__PURE__*/_react.default.createElement(_Nav.SubNavMenu, {
109
114
  role: "list",
@@ -117,11 +122,11 @@ var FooterNav = function FooterNav(_ref) {
117
122
  return /*#__PURE__*/_react.default.createElement(_Nav.SubNavItem, {
118
123
  key: thisUrl,
119
124
  column: group.links.length % 2 === 0 && group.links.length > 2
120
- }, /*#__PURE__*/_react.default.createElement(_Nav.SubNavLink, {
125
+ }, /*#__PURE__*/_react.default.createElement(_Nav.SubNavLink, Object.assign({
121
126
  href: thisUrl,
122
127
  inline: true,
123
128
  role: "menuitem"
124
- }, /*#__PURE__*/_react.default.createElement(_Text.default, {
129
+ }, rest), /*#__PURE__*/_react.default.createElement(_Text.default, {
125
130
  color: "white"
126
131
  }, child.title)));
127
132
  })));
package/dist/index.js CHANGED
@@ -167,6 +167,12 @@ Object.defineProperty(exports, "Label", {
167
167
  return _Label.default;
168
168
  }
169
169
  });
170
+ Object.defineProperty(exports, "ButtonWithStates", {
171
+ enumerable: true,
172
+ get: function get() {
173
+ return _ButtonWithStates.default;
174
+ }
175
+ });
170
176
  Object.defineProperty(exports, "HeroBanner", {
171
177
  enumerable: true,
172
178
  get: function get() {
@@ -329,6 +335,18 @@ Object.defineProperty(exports, "Descriptor", {
329
335
  return _Descriptor.default;
330
336
  }
331
337
  });
338
+ Object.defineProperty(exports, "Lookup", {
339
+ enumerable: true,
340
+ get: function get() {
341
+ return _Lookup.default;
342
+ }
343
+ });
344
+ Object.defineProperty(exports, "SimpleSchoolLookup", {
345
+ enumerable: true,
346
+ get: function get() {
347
+ return _SimpleSchoolLookup.default;
348
+ }
349
+ });
332
350
  Object.defineProperty(exports, "EmailSignUp", {
333
351
  enumerable: true,
334
352
  get: function get() {
@@ -416,6 +434,8 @@ var _ErrorText = _interopRequireDefault(require("./components/Atoms/ErrorText/Er
416
434
 
417
435
  var _Label = _interopRequireDefault(require("./components/Atoms/Label/Label"));
418
436
 
437
+ var _ButtonWithStates = _interopRequireDefault(require("./components/Atoms/ButtonWithStates/ButtonWithStates"));
438
+
419
439
  var _HeroBanner = _interopRequireDefault(require("./components/Molecules/HeroBanner/HeroBanner"));
420
440
 
421
441
  var _InfoBanner = _interopRequireDefault(require("./components/Molecules/InfoBanner/InfoBanner"));
@@ -470,6 +490,10 @@ var _Chip = _interopRequireDefault(require("./components/Molecules/Chip/Chip"));
470
490
 
471
491
  var _Descriptor = _interopRequireDefault(require("./components/Molecules/Descriptor/Descriptor"));
472
492
 
493
+ var _Lookup = _interopRequireDefault(require("./components/Molecules/Lookup/Lookup"));
494
+
495
+ var _SimpleSchoolLookup = _interopRequireDefault(require("./components/Molecules/SimpleSchoolLookup/SimpleSchoolLookup"));
496
+
473
497
  var _EmailSignUp = _interopRequireDefault(require("./components/Organisms/EmailSignUp/EmailSignUp"));
474
498
 
475
499
  var _CookieBanner = _interopRequireDefault(require("./components/Organisms/CookieBanner/CookieBanner"));
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": "5.6.0",
4
+ "version": "5.7.1",
5
5
  "main": "dist/index.js",
6
6
  "license": "ISC",
7
7
  "jest": {
@@ -35,6 +35,7 @@
35
35
  "react-hook-form": "^6.3.0",
36
36
  "react-modal": "^3.14.3",
37
37
  "react-scripts": "4.0.3",
38
+ "react-spinners": "^0.11.0",
38
39
  "react-styleguidist": "^11.1.7",
39
40
  "react-test-renderer": "^17.0.2",
40
41
  "react-uid": "^2.2.0",
@@ -0,0 +1,64 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import ScaleLoader from 'react-spinners/ScaleLoader';
5
+ import spacing from '../../../theme/shared/spacing';
6
+ import Button from '../Button/Button';
7
+
8
+ const ButtonWithDisabledState = styled(Button)`
9
+ &:disabled {
10
+ cursor: not-allowed;
11
+ opacity: 0.75;
12
+ }
13
+ `;
14
+
15
+ const LoaderContainer = styled.div`${({ withMargin }) => (withMargin ? `
16
+ margin-top: ${spacing('xsm')};
17
+ margin-left: ${spacing('md')};
18
+ ` : '')}`;
19
+
20
+ // A button with loading and disabled states.
21
+ const ButtonWithStates = React.forwardRef(({
22
+ children, loadingText, loading, disabled, ...rest
23
+ }, ref) => {
24
+ const [loaderColour, setLoaderColour] = useState(null);
25
+
26
+ // Can't see a simpler way to get the button's text colour, without reading the value
27
+ // via JavaScript.
28
+ // (e.g. the `theme.buttonColours` helper returns a CSS string split into an array -
29
+ // don't really want to be parsing that.)
30
+ // (And can't use inherit because ScaleLoader's color prop is actually setting its
31
+ // background color.)
32
+ const getLoaderColour = useCallback(node => {
33
+ if (node && typeof window.getComputedStyle === 'function') {
34
+ const textColour = window.getComputedStyle(node).color;
35
+ if (textColour) {
36
+ setLoaderColour(textColour);
37
+ }
38
+ }
39
+ }, []);
40
+
41
+ return (
42
+ <ButtonWithDisabledState ref={ref} disabled={disabled} {...rest}>
43
+ {loading ? loadingText : children}
44
+ <LoaderContainer ref={getLoaderColour} withMargin={loading}>
45
+ <ScaleLoader height={16} width={2} loading={loading} color={loaderColour} />
46
+ </LoaderContainer>
47
+ </ButtonWithDisabledState>
48
+ );
49
+ });
50
+
51
+ ButtonWithStates.propTypes = {
52
+ children: PropTypes.node.isRequired,
53
+ loadingText: PropTypes.string,
54
+ loading: PropTypes.bool,
55
+ disabled: PropTypes.bool
56
+ };
57
+
58
+ ButtonWithStates.defaultProps = {
59
+ loading: false,
60
+ disabled: false,
61
+ loadingText: 'Loading'
62
+ };
63
+
64
+ export default ButtonWithStates;
@@ -0,0 +1,13 @@
1
+ ## Disabled and loading
2
+
3
+ ```js
4
+ import ButtonWithStates from './ButtonWithStates';
5
+
6
+ <ButtonWithStates
7
+ type="submit"
8
+ loading
9
+ disabled
10
+ >
11
+ Enter prize draw
12
+ </ButtonWithStates>
13
+ ```
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  import StyledLink, { HelperText, IconWrapper } from './Link.style';
@@ -19,29 +19,37 @@ const Link = ({
19
19
  iconFirst,
20
20
  ...rest
21
21
  }) => {
22
+ const [documentHost, setDocumentHost] = useState('');
22
23
  /**
23
24
  * If we haven't specifically set the target via props, check if
24
25
  * this is an internal link OR on our whitelist before making it a '_self' link
25
26
  */
26
27
  if (target === null) {
27
28
  // Use our helper function to determine the raw domains to compare
28
- const currentDomain = getDomain(document.location.host);
29
+ const currentDomain = getDomain(documentHost);
29
30
  const linkDomain = getDomain(href);
30
31
 
32
+ // Additional check for applications that need more control
33
+ const isWhiteListOverridden = rest.overrideWhiteList === true;
34
+
31
35
  /**
32
36
  * If the link has no domain supplied (likely '/' or '#')
33
37
  * OR has the same domain as the current page, don't open
34
38
  * in a new tab
35
39
  */
36
40
  const isExternalLink = linkDomain !== '' && (currentDomain !== linkDomain);
37
-
38
41
  const isWhiteListed = whiteListed(href);
39
- window = isExternalLink && !isWhiteListed ? '_blank' : '_self';
42
+
43
+ window = isExternalLink && (isWhiteListOverridden || !isWhiteListed) ? '_blank' : '_self';
40
44
  } else {
41
45
  window = target === 'blank' ? '_blank' : '_self';
42
46
  }
43
47
  const hasIcon = icon !== null;
44
48
 
49
+ useEffect(() => {
50
+ setDocumentHost(document.location.host);
51
+ }, []);
52
+
45
53
  return (
46
54
  <StyledLink
47
55
  {...rest}
@@ -0,0 +1,148 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled, { css } from 'styled-components';
4
+ import TextInputWithDropdown from '../../Atoms/TextInputWithDropdown/TextInputWithDropdown';
5
+ import spacing from '../../../theme/shared/spacing';
6
+ import ButtonWithStates from '../../Atoms/ButtonWithStates/ButtonWithStates';
7
+
8
+ const StyledButton = styled(ButtonWithStates)`${({ theme }) => css`
9
+ color: ${theme.color('grey_dark')};
10
+ border: 2px solid ${theme.color('grey_dark')};
11
+ background-color: ${theme.color('white')};
12
+ padding-left: ${spacing('lg')};
13
+ padding-right: ${spacing('lg')};
14
+
15
+ &:hover {
16
+ color: ${theme.color('grey_dark')};
17
+ background-color: ${theme.color('white')};
18
+ }
19
+ `}`;
20
+
21
+ const KEY_CODE_ENTER = 13;
22
+
23
+ /**
24
+ * A simple lookup component
25
+ *
26
+ * The `lookupHandler` should be an async function which is called when a lookup is triggered
27
+ * (either by hitting enter or clicking the button)
28
+ *
29
+ * It will receive the current search term and should:
30
+ * - take care of any validation on the search term
31
+ * - perform the actual lookup request
32
+ * - return an array of options (or an empty array if none were found)
33
+ * - only throw errors with user-friendly messages
34
+ *
35
+ * Any errors thrown will be caught and the message will be displayed to the user.
36
+ *
37
+ * The `onSelect` function will receive the chosen option.
38
+ *
39
+ * @param name
40
+ * @param label
41
+ * @param placeholder
42
+ * @param buttonText
43
+ * @param lookupHandler
44
+ * @param mapOptionToString
45
+ * @param onSelect
46
+ * @param noResultsMessage
47
+ * @param dropdownInstruction
48
+ * @param rest
49
+ * @returns {JSX.Element}
50
+ * @constructor
51
+ */
52
+ const Lookup = ({
53
+ name,
54
+ label,
55
+ placeholder,
56
+ buttonText,
57
+ lookupHandler,
58
+ mapOptionToString,
59
+ onSelect,
60
+ noResultsMessage,
61
+ dropdownInstruction,
62
+ ...rest
63
+ }) => {
64
+ const [query, setQuery] = useState('');
65
+ const [errorMessage, setErrorMessage] = useState('');
66
+ const [options, setOptions] = useState([]);
67
+ const [isSearching, setIsSearching] = useState(false);
68
+
69
+ const handler = useCallback(async () => {
70
+ setErrorMessage('');
71
+ setIsSearching(true);
72
+ try {
73
+ // If lookupHandler throws an error, the message will be displayed to the user
74
+ const results = await lookupHandler(query);
75
+ setOptions(results);
76
+ if (results.length === 0) {
77
+ setErrorMessage(noResultsMessage);
78
+ }
79
+ } catch (error) {
80
+ setErrorMessage(error.message);
81
+ }
82
+ setIsSearching(false);
83
+ }, [query, setOptions, setErrorMessage, noResultsMessage, lookupHandler]);
84
+
85
+ return (
86
+ <div {...rest}>
87
+ <TextInputWithDropdown
88
+ css={{ marginBottom: spacing('md') }}
89
+ name={name}
90
+ id={name}
91
+ value={query}
92
+ options={options.map(mapOptionToString)}
93
+ label={label}
94
+ placeholder={placeholder}
95
+ onChange={e => {
96
+ setQuery(e.target.value);
97
+ setErrorMessage('');
98
+ setOptions([]);
99
+ }}
100
+ onKeyPress={e => {
101
+ const keyCode = e.keyCode || e.which;
102
+ if (keyCode === KEY_CODE_ENTER) {
103
+ e.preventDefault();
104
+ return handler();
105
+ }
106
+ return null;
107
+ }}
108
+ onSelect={(text, index) => {
109
+ const selection = options[index];
110
+ onSelect(selection);
111
+ setQuery('');
112
+ setErrorMessage('');
113
+ setOptions([]);
114
+ }}
115
+ errorMsg={errorMessage}
116
+ dropdownInstruction={dropdownInstruction}
117
+ />
118
+ <StyledButton
119
+ type="button"
120
+ onClick={() => handler()}
121
+ loading={isSearching}
122
+ disabled={isSearching}
123
+ loadingText="Searching"
124
+ >
125
+ {buttonText}
126
+ </StyledButton>
127
+ </div>
128
+ );
129
+ };
130
+
131
+ Lookup.propTypes = {
132
+ name: PropTypes.string.isRequired,
133
+ label: PropTypes.string.isRequired,
134
+ placeholder: PropTypes.string.isRequired,
135
+ buttonText: PropTypes.string.isRequired,
136
+ lookupHandler: PropTypes.func.isRequired,
137
+ mapOptionToString: PropTypes.func.isRequired,
138
+ onSelect: PropTypes.func.isRequired,
139
+ noResultsMessage: PropTypes.string,
140
+ dropdownInstruction: PropTypes.string
141
+ };
142
+
143
+ Lookup.defaultProps = {
144
+ noResultsMessage: 'Sorry, could not find any results for your search',
145
+ dropdownInstruction: ''
146
+ };
147
+
148
+ export default Lookup;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import axios from 'axios';
4
+
5
+ import Lookup from '../Lookup/Lookup';
6
+
7
+ const schoolFetcher = async query => {
8
+ if (!query || !query.trim()) {
9
+ throw new Error('Please enter a search query');
10
+ }
11
+
12
+ if (query.length < 2) {
13
+ throw new Error('Please enter at least two characters');
14
+ }
15
+
16
+ try {
17
+ const res = await axios.get(
18
+ 'https://lookups.sls.comicrelief.com/schools/lookup',
19
+ { timeout: 10000, params: { query } }
20
+ );
21
+ return res.data.data.schools;
22
+ } catch (error) {
23
+ // if (typeof Sentry !== 'undefined') {
24
+ // Sentry.captureException(error);
25
+ // }
26
+ throw new Error('Sorry, something unexpected went wrong. Please try again or enter your school manually');
27
+ }
28
+ };
29
+
30
+ const schoolToString = school => `${school.name}, ${school.post_code}`;
31
+
32
+ /**
33
+ * The component library's school lookup component uses a typeahead/search-as-you-type approach.
34
+ *
35
+ * Given the API we use is v flaky and can be slow, this isn't really ideal.
36
+ *
37
+ * This version just has a simple input + a button (or you can hit enter) to trigger the search.
38
+ *
39
+ * @param onSelect
40
+ * @param rest
41
+ * @returns {JSX.Element}
42
+ * @constructor
43
+ */
44
+ const SimpleSchoolLookup = ({ onSelect, ...rest }) => (
45
+ <Lookup
46
+ name="school_lookup"
47
+ label="Enter the name or postcode of your organisation"
48
+ placeholder="Enter name or postcode..."
49
+ buttonText="Find school"
50
+ dropdownInstruction="Please select an organisation from the list below"
51
+ noResultsMessage="Sorry, could not find anything matching your search"
52
+ lookupHandler={schoolFetcher}
53
+ mapOptionToString={schoolToString}
54
+ onSelect={onSelect}
55
+ {...rest}
56
+ />
57
+ );
58
+
59
+ SimpleSchoolLookup.propTypes = {
60
+ onSelect: PropTypes.func.isRequired
61
+ };
62
+
63
+ export default SimpleSchoolLookup;
@@ -0,0 +1,7 @@
1
+ ```js
2
+
3
+ import SimpleSchoolLookup from './SimpleSchoolLookup';
4
+
5
+ <SimpleSchoolLookup onSelect={data => console.log(data)}/>
6
+
7
+ ```
@@ -896,6 +896,10 @@ exports[`renders Single Message with full width image and no text correctly 1`]
896
896
  z-index: 1;
897
897
  }
898
898
 
899
+ @media (min-width:740px) {
900
+
901
+ }
902
+
899
903
  @media (min-width:740px) {
900
904
  .c0 {
901
905
  -webkit-flex-direction: row;
@@ -1010,6 +1014,10 @@ exports[`renders Single Message with no Image correctly 1`] = `
1010
1014
  padding: 1rem;
1011
1015
  }
1012
1016
 
1017
+ @media (min-width:740px) {
1018
+
1019
+ }
1020
+
1013
1021
  @media (min-width:740px) {
1014
1022
  .c0 {
1015
1023
  -webkit-flex-direction: row;
@@ -31,7 +31,7 @@ const Footer = ({
31
31
  <Logo sizeSm="48px" sizeMd="72px" rotate={false} campaign={campaign} />
32
32
  </Brand>
33
33
  </FooterBranding>
34
- <FooterNav navItems={navItems} />
34
+ <FooterNav navItems={navItems} {...rest} />
35
35
  <FooterCopyright>
36
36
  <Text tag="p" color="grey">
37
37
  {footerCopy}
@@ -46,13 +46,15 @@ const Footer = ({
46
46
  Footer.propTypes = {
47
47
  navItems: PropTypes.objectOf(PropTypes.shape),
48
48
  footerCopy: PropTypes.string,
49
- campaign: PropTypes.string
49
+ campaign: PropTypes.string,
50
+ overrideWhiteList: PropTypes.bool
50
51
  };
51
52
 
52
53
  Footer.defaultProps = {
53
54
  navItems: {},
54
55
  footerCopy: '',
55
- campaign: 'Comic Relief'
56
+ campaign: 'Comic Relief',
57
+ overrideWhiteList: false
56
58
  };
57
59
 
58
60
  export default Footer;
@@ -4,5 +4,12 @@
4
4
  import data from './data/data';
5
5
  import footerCopy from './data/footerCopy';
6
6
 
7
- <Footer navItems={data} footerCopy={footerCopy.copy} campaign="Comic Relief" />;
7
+ <>
8
+ <p>Standard footer</p>
9
+ <Footer navItems={data} footerCopy={footerCopy.copy} campaign="Comic Relief" />
10
+
11
+ <p>Overrides whitelist functionality for external usage</p>
12
+ <Footer navItems={data} footerCopy={footerCopy.copy} campaign="Comic Relief" overrideWhiteList />
13
+ </>
14
+
8
15
  ```
@@ -16,7 +16,7 @@ import {
16
16
  SubNavLink
17
17
  } from './Nav.style';
18
18
 
19
- const FooterNav = ({ navItems }) => {
19
+ const FooterNav = ({ navItems, ...rest }) => {
20
20
  const { menuGroups } = navItems;
21
21
  const [isExpandable] = useState(false);
22
22
  const [isSubMenuOpen, setIsSubMenuOpen] = useState({});
@@ -78,6 +78,7 @@ const FooterNav = ({ navItems }) => {
78
78
  aria-haspopup="true"
79
79
  role="button"
80
80
  onClick={toggleSubMenu(group.id)}
81
+ {...rest}
81
82
  >
82
83
  <Text color="white">{group.title}</Text>
83
84
  </NavLink>
@@ -103,7 +104,7 @@ const FooterNav = ({ navItems }) => {
103
104
  group.links.length % 2 === 0 && group.links.length > 2
104
105
  }
105
106
  >
106
- <SubNavLink href={thisUrl} inline role="menuitem">
107
+ <SubNavLink href={thisUrl} inline role="menuitem" {...rest}>
107
108
  <Text color="white">{child.title}</Text>
108
109
  </SubNavLink>
109
110
  </SubNavItem>
package/src/index.js CHANGED
@@ -29,6 +29,7 @@ export { default as SocialIcons } from './components/Atoms/SocialIcons/SocialIco
29
29
  export { default as TextInputWithDropdown } from './components/Atoms/TextInputWithDropdown/TextInputWithDropdown';
30
30
  export { default as ErrorText } from './components/Atoms/ErrorText/ErrorText';
31
31
  export { default as Label } from './components/Atoms/Label/Label';
32
+ export { default as ButtonWithStates } from './components/Atoms/ButtonWithStates/ButtonWithStates';
32
33
 
33
34
  /* Molecules */
34
35
  export { default as HeroBanner } from './components/Molecules/HeroBanner/HeroBanner';
@@ -58,6 +59,8 @@ export { default as Countdown } from './components/Molecules/Countdown/Countdown
58
59
  export { default as Banner } from './components/Molecules/Banner/Banner';
59
60
  export { default as Chip } from './components/Molecules/Chip/Chip';
60
61
  export { default as Descriptor } from './components/Molecules/Descriptor/Descriptor';
62
+ export { default as Lookup } from './components/Molecules/Lookup/Lookup';
63
+ export { default as SimpleSchoolLookup } from './components/Molecules/SimpleSchoolLookup/SimpleSchoolLookup';
61
64
 
62
65
  /* Organisms */
63
66
  export { default as EmailSignUp } from './components/Organisms/EmailSignUp/EmailSignUp';