@thecb/components 10.7.8-beta.0 → 10.8.0-beta.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": "@thecb/components",
3
- "version": "10.7.8-beta.0",
3
+ "version": "10.8.0-beta.0",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
Binary file
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
+ import { CHARADE_GREY } from "../../../constants/colors";
2
3
 
3
- const SearchIcon = () => (
4
- <svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1">
4
+ const SearchIcon = ({ color = CHARADE_GREY, size = "22px" }) => (
5
+ <svg width={size} height={size} viewBox="0 0 22 22" version="1.1">
5
6
  <g
6
7
  id="Debt-Search"
7
8
  stroke="none"
@@ -12,7 +13,7 @@ const SearchIcon = () => (
12
13
  <g
13
14
  id="Debt-Search---Expanded"
14
15
  transform="translate(-155.000000, -258.000000)"
15
- stroke="#292A33"
16
+ stroke={color}
16
17
  strokeWidth="2"
17
18
  >
18
19
  <g id="debt-search" transform="translate(132.000000, 189.000000)">
@@ -12,6 +12,7 @@ export * from "./nav-footer";
12
12
  export * from "./nav-header";
13
13
  export * from "./nav-tabs";
14
14
  export * from "./paragraph";
15
+ export * from "./search";
15
16
  export * from "./searchable-select";
16
17
  export * from "./spinner";
17
18
  export * from "./table";
@@ -37,6 +37,7 @@ export { default as Placeholder } from "./placeholder";
37
37
  export { default as ProcessingFee } from "./processing-fee";
38
38
  export { default as RadioButtonWithLabel } from "./radio-button-with-label";
39
39
  export { default as RadioButton } from "../molecules/radio-section/radio-button/RadioButton";
40
+ export { default as Search } from "./search";
40
41
  export { default as SearchableSelect } from "./searchable-select";
41
42
  export { default as SolidDivider } from "./solid-divider";
42
43
  export { default as Spinner } from "./spinner";
@@ -0,0 +1,125 @@
1
+ import React from "react";
2
+ import { Box, Cluster } from "../layouts";
3
+ import { ButtonWithAction } from "..";
4
+ import { FormInput } from "../form-layouts";
5
+ import { SearchIcon } from "../icons";
6
+ import { themeComponent } from "../../../util/themeUtils";
7
+ import { fallbackValues } from "./Search.theme";
8
+ import { noop } from "../../../util/general";
9
+
10
+ /**
11
+ * Search accepts a redux-freeform field and actions to use
12
+ * for its `FormInput` state, as well as a dataset and the keys you wish
13
+ * to include in your search.
14
+ *
15
+ * The `dataset` expected is a one-dimensional array of objects.
16
+ * Properties within the objects are included in the traversal
17
+ * when they are present in the ``valuesToSearchFor` prop.
18
+ *
19
+ * This component expects implementations of `onSearchCallback` and `onClearCallback`
20
+ * in the consuming application. For example, if you want to filter a table based on
21
+ * this search, this would trigger the adjustment of your table data's state in your
22
+ * consuming application.
23
+ */
24
+
25
+ const Search = ({
26
+ actions,
27
+ fields,
28
+ dataset,
29
+ valuesToSearchFor,
30
+ onSearchCallback,
31
+ onClearCallback,
32
+ disabled,
33
+ placeholder = "Search",
34
+ searchOnKeypress = false,
35
+ searchContainerWidth = null, // Defaults to full width via flex-grow: 1
36
+ searchFieldName = "searchTerm",
37
+ autocompleteValue,
38
+ ariaControlsId,
39
+ themeValues
40
+ }) => {
41
+ const searchTerm = fields[searchFieldName].rawValue;
42
+
43
+ const getFilteredDataset = () => {
44
+ if (!searchTerm) return dataset;
45
+ return dataset.filter(item =>
46
+ valuesToSearchFor.some(key =>
47
+ item[key]
48
+ ?.toString()
49
+ .toLowerCase()
50
+ .includes(searchTerm.toLowerCase())
51
+ )
52
+ );
53
+ };
54
+
55
+ const handleSubmit = () => onSearchCallback(getFilteredDataset());
56
+
57
+ return (
58
+ <Cluster extraStyles={`overflow: visible;`}>
59
+ <Box
60
+ padding="0"
61
+ extraStyles={`
62
+ flex-grow: ${searchContainerWidth ? 0 : 1};
63
+ width: ${searchContainerWidth} : auto;`}
64
+ >
65
+ <FormInput
66
+ id="searchInput"
67
+ role="search"
68
+ aria-controls={ariaControlsId}
69
+ autocompleteValue={autocompleteValue}
70
+ extraStyles={`border-radius: 2px 0 0 2px;`}
71
+ onKeyDown={e =>
72
+ searchOnKeypress || e.key === "Enter" ? handleSubmit() : noop
73
+ }
74
+ field={fields[searchFieldName]}
75
+ fieldActions={actions.fields[searchFieldName]}
76
+ placeholder={placeholder}
77
+ errorMessages={{}}
78
+ disabled={disabled}
79
+ />
80
+ {searchTerm.length > 0 && (
81
+ <Box
82
+ padding="0"
83
+ extraStyles={`
84
+ position: absolute;
85
+ right: 8px;
86
+ top: 10px;
87
+ `}
88
+ >
89
+ <ButtonWithAction
90
+ extraStyles={`* > span { text-decoration: none; }`}
91
+ type="reset"
92
+ variant="smallGhost"
93
+ text="Clear Search"
94
+ aria-label="Clear Search"
95
+ action={() => {
96
+ actions.fields[searchFieldName].set("");
97
+ onClearCallback();
98
+ }}
99
+ />
100
+ </Box>
101
+ )}
102
+ </Box>
103
+ <ButtonWithAction
104
+ type="submit"
105
+ aria-label="Submit search"
106
+ minWidth="0"
107
+ width="3rem"
108
+ padding=".5rem"
109
+ extraStyles={`
110
+ height: 48px;
111
+ margin: 4px 0 0;
112
+ border-radius: 0 2px 2px 0;
113
+ background: ${themeValues.searchIconBackgroundColor};
114
+ `}
115
+ variant="primary"
116
+ contentOverride={true}
117
+ action={handleSubmit}
118
+ >
119
+ <SearchIcon color={themeValues.searchIconColor} size={24} />
120
+ </ButtonWithAction>
121
+ </Cluster>
122
+ );
123
+ };
124
+
125
+ export default themeComponent(Search, "Search", fallbackValues);
@@ -0,0 +1,58 @@
1
+ import React, { useState } from "react";
2
+ import Search from "./Search";
3
+ import page from "../../../../.storybook/page";
4
+ import { createFormState } from "redux-freeform";
5
+ import { connect } from "react-redux";
6
+
7
+ const { mapStateToProps, mapDispatchToProps, reducer } = createFormState({
8
+ searchTerm: {
9
+ validators: []
10
+ }
11
+ });
12
+
13
+ const dataset = [
14
+ { id: 1, name: "Leslie Knope", email: "leslie.knope@email.com" },
15
+ { id: 2, name: "Ron Swanson", email: "ron.swanson@email.com" },
16
+ { id: 3, name: "April Ludgate", email: "april.ludgate@email.com" }
17
+ ];
18
+
19
+ const FormWrapper = props => {
20
+ const [filteredDataset, setFilteredDataset] = useState(dataset);
21
+ const onSearch = searchResults => setFilteredDataset(searchResults);
22
+ const onClear = () => setFilteredDataset(dataset);
23
+
24
+ return (
25
+ <>
26
+ <Search
27
+ placeholder="Search by Name or Email Address"
28
+ ariaControlsId="resultList"
29
+ searchOnKeypress={false}
30
+ valuesToSearchFor={["name", "email"]}
31
+ fields={props.fields}
32
+ actions={props.actions}
33
+ dataset={dataset}
34
+ onSearchCallback={onSearch}
35
+ onClearCallback={onClear}
36
+ />
37
+ <ul id="resultList">
38
+ {filteredDataset.map(item => (
39
+ <li key={item.id}>
40
+ {item.name} | {item.email}
41
+ </li>
42
+ ))}
43
+ </ul>
44
+ </>
45
+ );
46
+ };
47
+
48
+ const story = page({
49
+ title: "Components|Atoms/Search",
50
+ Component: Search,
51
+ reducer,
52
+ mapStateToProps,
53
+ mapDispatchToProps
54
+ });
55
+
56
+ export default story;
57
+ const ConnectedForm = connect(mapStateToProps, mapDispatchToProps)(FormWrapper);
58
+ export const search = () => <ConnectedForm />;
@@ -0,0 +1,9 @@
1
+ import { MATISSE_BLUE, WHITE } from "../../../constants/colors";
2
+
3
+ const searchIconColor = WHITE;
4
+ const searchIconBackgroundColor = MATISSE_BLUE;
5
+
6
+ export const fallbackValues = {
7
+ searchIconColor,
8
+ searchIconBackgroundColor
9
+ };
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import Expand from "../../../util/expand";
3
+ import { Field, FieldActions } from "../../../types/common";
4
+
5
+ /*
6
+ Keys within this array will be referenced based on the
7
+ valuesToSearchFor` array,.
8
+ */
9
+ type FlexibleObjectArray = Array<{ [key: string]: any }>;
10
+
11
+ export interface SearchProps {
12
+ actions: FieldActions;
13
+ fields: {
14
+ [key: string]: Field;
15
+ };
16
+ dataset: FlexibleObjectArray;
17
+ valuesToSearchFor: string[];
18
+ /*
19
+ The callbacks are on the consuming app to implement,
20
+ so they need to be flexible.
21
+ */
22
+ onSearchCallback: () => void;
23
+ onClearCallback: () => void;
24
+ disabled?: boolean;
25
+ placeholder?: string;
26
+ searchOnKeypress?: boolean;
27
+ formWidth?: string;
28
+ searchFieldName?: string;
29
+ autocompleteValue?: string;
30
+ themeValues?: object;
31
+ }
32
+
33
+ export const Search: React.FC<Expand<SearchProps> &
34
+ React.HTMLAttributes<HTMLElement>>;
@@ -0,0 +1,3 @@
1
+ import Search from "./Search";
2
+
3
+ export default Search;
@@ -42,7 +42,6 @@ const Modal = ({
42
42
  cancelButtonVariant = "secondary",
43
43
  children = [],
44
44
  closeButtonText = "Close",
45
- closeButtonVariant = "primary",
46
45
  continueAction = noop,
47
46
  continueButtonText = "Continue",
48
47
  continueButtonVariant = "primary",
@@ -203,7 +202,6 @@ const Modal = ({
203
202
  <CloseButton
204
203
  buttonExtraStyles={buttonExtraStyles}
205
204
  closeButtonText={closeButtonText}
206
- closeButtonVariant={closeButtonVariant}
207
205
  hideModal={hideModal}
208
206
  isMobile={isMobile}
209
207
  key="close"
@@ -9,7 +9,6 @@ import ButtonWithAction from "../../../atoms/button-with-action/ButtonWithAction
9
9
  export const CloseButton = ({
10
10
  buttonExtraStyles = "",
11
11
  closeButtonText = "",
12
- closeButtonVariant = "primary",
13
12
  hideModal = noop,
14
13
  isMobile = false
15
14
  }) => {
@@ -27,7 +26,7 @@ export const CloseButton = ({
27
26
  role="button"
28
27
  text={closeButtonText}
29
28
  textExtraStyles={`${fontSize}`}
30
- variant={closeButtonVariant}
29
+ variant="primary"
31
30
  />
32
31
  );
33
32
  };