@thecb/components 10.7.8 → 10.8.0-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "10.7.8",
3
+ "version": "10.8.0-beta.1",
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,122 @@
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 requires 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, your callbacks should trigger the adjustment of your table data's state in your
22
+ * application implementing Search.
23
+ */
24
+
25
+ const Search = ({
26
+ field,
27
+ fieldActions,
28
+ dataset,
29
+ valuesToSearchFor,
30
+ onSearchCallback,
31
+ onClearCallback,
32
+ disabled,
33
+ placeholder = "Search",
34
+ searchOnKeypress = false,
35
+ searchContainerWidth = null, // Defaults to full width
36
+ ariaControlsId,
37
+ themeValues
38
+ }) => {
39
+ const searchTerm = field.rawValue;
40
+
41
+ const getFilteredDataset = () => {
42
+ if (!searchTerm) return dataset;
43
+ return dataset.filter(item =>
44
+ valuesToSearchFor.some(key =>
45
+ item[key]
46
+ ?.toString()
47
+ .toLowerCase()
48
+ .includes(searchTerm.toLowerCase())
49
+ )
50
+ );
51
+ };
52
+
53
+ const handleSubmit = () => onSearchCallback(getFilteredDataset());
54
+
55
+ return (
56
+ <Cluster extraStyles={`overflow: visible;`}>
57
+ <Box
58
+ padding="0"
59
+ extraStyles={`
60
+ flex-grow: ${searchContainerWidth ? 0 : 1};
61
+ width: ${searchContainerWidth} : auto;`}
62
+ >
63
+ <FormInput
64
+ id="searchInput"
65
+ role="search"
66
+ aria-controls={ariaControlsId}
67
+ extraStyles={`border-radius: 2px 0 0 2px;`}
68
+ onKeyDown={e =>
69
+ searchOnKeypress || e.key === "Enter" ? handleSubmit() : noop
70
+ }
71
+ field={field}
72
+ fieldActions={fieldActions}
73
+ placeholder={placeholder}
74
+ errorMessages={{}}
75
+ disabled={disabled}
76
+ />
77
+ {searchTerm.length > 0 && (
78
+ <Box
79
+ padding="0"
80
+ extraStyles={`
81
+ position: absolute;
82
+ right: 8px;
83
+ top: 10px;
84
+ `}
85
+ >
86
+ <ButtonWithAction
87
+ extraStyles={`* > span { text-decoration: none; }`}
88
+ type="reset"
89
+ variant="smallGhost"
90
+ text="Clear Search"
91
+ aria-label="Clear Search"
92
+ action={() => {
93
+ fieldActions.set("");
94
+ onClearCallback();
95
+ }}
96
+ />
97
+ </Box>
98
+ )}
99
+ </Box>
100
+ <ButtonWithAction
101
+ type="submit"
102
+ aria-label="Submit search"
103
+ minWidth="0"
104
+ width="3rem"
105
+ padding=".5rem"
106
+ extraStyles={`
107
+ height: 48px;
108
+ margin: 4px 0 0;
109
+ border-radius: 0 2px 2px 0;
110
+ background: ${themeValues.searchIconBackgroundColor};
111
+ `}
112
+ variant="primary"
113
+ contentOverride={true}
114
+ action={handleSubmit}
115
+ >
116
+ <SearchIcon color={themeValues.searchIconColor} size={24} />
117
+ </ButtonWithAction>
118
+ </Cluster>
119
+ );
120
+ };
121
+
122
+ 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
+ field={props.fields.searchTerm}
32
+ fieldActions={props.actions.fields.searchTerm}
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,25 @@
1
+ import React from "react";
2
+ import Expand from "../../../util/expand";
3
+ import { Field, FieldActions } from "../../../types/common";
4
+
5
+ // Keys within this array will be referenced based on the
6
+ // `valuesToSearchFor` array.
7
+ type FlexibleObjectArray = Array<{ [key: string]: any }>;
8
+
9
+ export interface SearchProps {
10
+ field: Field;
11
+ fieldActions: FieldActions;
12
+ dataset: FlexibleObjectArray;
13
+ valuesToSearchFor: string[];
14
+ onSearchCallback: () => void;
15
+ onClearCallback: () => void;
16
+ disabled?: boolean;
17
+ placeholder?: string;
18
+ searchOnKeypress?: boolean;
19
+ formWidth?: string;
20
+ autocompleteValue?: string;
21
+ themeValues?: object;
22
+ }
23
+
24
+ export const Search: React.FC<Expand<SearchProps> &
25
+ React.HTMLAttributes<HTMLElement>>;
@@ -0,0 +1,3 @@
1
+ import Search from "./Search";
2
+
3
+ export default Search;