@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/dist/index.cjs.js +176 -73
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.esm.js +176 -74
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/.DS_Store +0 -0
- package/src/components/atoms/icons/SearchIcon.js +4 -3
- package/src/components/atoms/index.d.ts +1 -0
- package/src/components/atoms/index.js +1 -0
- package/src/components/atoms/search/Search.js +125 -0
- package/src/components/atoms/search/Search.stories.js +58 -0
- package/src/components/atoms/search/Search.theme.js +9 -0
- package/src/components/atoms/search/index.d.ts +34 -0
- package/src/components/atoms/search/index.js +3 -0
- package/src/components/molecules/modal/ModalControlV2.js +0 -2
- package/src/components/molecules/modal/__private__/CloseButton.js +1 -2
package/package.json
CHANGED
|
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=
|
|
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=
|
|
16
|
+
stroke={color}
|
|
16
17
|
strokeWidth="2"
|
|
17
18
|
>
|
|
18
19
|
<g id="debt-search" transform="translate(132.000000, 189.000000)">
|
|
@@ -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,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>>;
|
|
@@ -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=
|
|
29
|
+
variant="primary"
|
|
31
30
|
/>
|
|
32
31
|
);
|
|
33
32
|
};
|