@madie/madie-design-system 1.2.89 → 1.2.91
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/.babelrc +6 -2
- package/components/MadieTable/index.jsx +162 -0
- package/components/SearchAndFilter/index.jsx +123 -0
- package/components/SearchAndFilter/useFilterSearch.jsx +60 -0
- package/components/index.js +10 -2
- package/dist/browser.js +1 -1
- package/dist/index.js +1 -1
- package/dist/react/index.js +26 -26
- package/dist/react/index.js.LICENSE.txt +23 -1
- package/dist/react/index.js.map +1 -1
- package/jest.config.js +4 -0
- package/package.json +8 -1
- package/styles/components/_search-and-filter.scss +13 -0
- package/styles/qppds/components/_search-and-filter.scss +13 -0
- package/test/__mocks__/styled-components-macro.js +2 -0
- package/test/__mocks__/twin.macro.js +24 -0
- package/test/components/MadieTable.test.js +176 -0
- package/test/components/SearchAndFilter.test.js +78 -0
- package/test/components/UseFilterSearch.test.js +164 -0
- package/webpack.config.js +4 -1
- package/webpack.config.react.js +1 -1
- package/components/Search/Search.stories.js +0 -21
- package/components/Search/index.js +0 -86
package/.babelrc
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
"presets": ["@babel/preset-react", "@babel/preset-env"],
|
|
3
|
+
"plugins": [
|
|
4
|
+
"babel-plugin-macros",
|
|
5
|
+
"@babel/plugin-transform-modules-umd",
|
|
6
|
+
"@babel/plugin-transform-class-properties"
|
|
7
|
+
]
|
|
4
8
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { flexRender } from "@tanstack/react-table";
|
|
4
|
+
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
|
5
|
+
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
|
6
|
+
import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
|
|
7
|
+
import tw from "twin.macro";
|
|
8
|
+
import "styled-components/macro";
|
|
9
|
+
|
|
10
|
+
const MadieTable = ({
|
|
11
|
+
table,
|
|
12
|
+
currentSort,
|
|
13
|
+
currentDirection,
|
|
14
|
+
handleSort,
|
|
15
|
+
renderExpandedRow,
|
|
16
|
+
id="measureListTable",
|
|
17
|
+
dataTestId = "measure-list-tbl"
|
|
18
|
+
}) => {
|
|
19
|
+
const TH = tw.th`p-3 text-left text-sm font-bold capitalize`;
|
|
20
|
+
const [hoveredHeader, setHoveredHeader] = useState(null);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<table
|
|
24
|
+
/* eslint-disable-next-line react/no-unknown-property */
|
|
25
|
+
tw="min-w-full"
|
|
26
|
+
id={id}
|
|
27
|
+
data-testid={dataTestId}
|
|
28
|
+
className="tcl-table"
|
|
29
|
+
style={{
|
|
30
|
+
borderSpacing: "0 2em !important",
|
|
31
|
+
overflow: "visible",
|
|
32
|
+
backgroundColor: "#fff",
|
|
33
|
+
opacity: 1,
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<thead className="sticky-table" style={{ overflow: "visible" }}>
|
|
37
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
38
|
+
<tr key={headerGroup.id}>
|
|
39
|
+
{headerGroup.headers.map((header) => {
|
|
40
|
+
const isHovered = hoveredHeader?.includes(header.id);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<TH
|
|
44
|
+
key={header.id}
|
|
45
|
+
scope="col"
|
|
46
|
+
onMouseEnter={() => setHoveredHeader(header.id)}
|
|
47
|
+
onMouseLeave={() => setHoveredHeader(null)}
|
|
48
|
+
className="header-cell"
|
|
49
|
+
>
|
|
50
|
+
{header.isPlaceholder ? null : header.column.getCanSort() ? (
|
|
51
|
+
<button
|
|
52
|
+
className={
|
|
53
|
+
header.column.getCanSort()
|
|
54
|
+
? "cursor-pointer select-none header-button"
|
|
55
|
+
: "header-button"
|
|
56
|
+
}
|
|
57
|
+
disabled={!header.column.getCanSort()}
|
|
58
|
+
onClick={() =>
|
|
59
|
+
handleSort(header.id.replace("_", "."))
|
|
60
|
+
}
|
|
61
|
+
data-testid={`header-${header.id.replace("_", ".")}`}
|
|
62
|
+
title={
|
|
63
|
+
header.column.getCanSort()
|
|
64
|
+
? currentSort ===
|
|
65
|
+
header.column.id.replace("_", ".")
|
|
66
|
+
? currentDirection === "ASC"
|
|
67
|
+
? "Sort descending"
|
|
68
|
+
: currentDirection === "DESC"
|
|
69
|
+
? "Clear sort"
|
|
70
|
+
: "Sort ascending"
|
|
71
|
+
: "Sort ascending"
|
|
72
|
+
: undefined
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
{flexRender(
|
|
76
|
+
header.column.columnDef.header,
|
|
77
|
+
header.getContext()
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
<span className="arrowDisplay">
|
|
81
|
+
{header.column.getCanSort() ? (
|
|
82
|
+
currentSort ===
|
|
83
|
+
header.column.id.replace("_", ".") ? (
|
|
84
|
+
currentDirection === "ASC" ? (
|
|
85
|
+
<KeyboardArrowUpIcon />
|
|
86
|
+
) : (
|
|
87
|
+
<KeyboardArrowDownIcon />
|
|
88
|
+
)
|
|
89
|
+
) : isHovered ? (
|
|
90
|
+
<UnfoldMoreIcon data-testid="unfold-more-icon" />
|
|
91
|
+
) : null
|
|
92
|
+
) : null}
|
|
93
|
+
</span>
|
|
94
|
+
</button>
|
|
95
|
+
) : (
|
|
96
|
+
flexRender(
|
|
97
|
+
header.column.columnDef.header,
|
|
98
|
+
header.getContext()
|
|
99
|
+
)
|
|
100
|
+
)}
|
|
101
|
+
</TH>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</tr>
|
|
105
|
+
))}
|
|
106
|
+
</thead>
|
|
107
|
+
|
|
108
|
+
<tbody className="table-body measures-list" style={{ padding: 20 }}>
|
|
109
|
+
{table.getRowModel().rows.length === 0 && (
|
|
110
|
+
<tr>
|
|
111
|
+
<td
|
|
112
|
+
colSpan={table.getAllColumns().length}
|
|
113
|
+
style={{ padding: "40px 0", textAlign: "center" }}
|
|
114
|
+
>
|
|
115
|
+
<span>No results were found</span>
|
|
116
|
+
</td>
|
|
117
|
+
</tr>
|
|
118
|
+
)}
|
|
119
|
+
{table.getRowModel().rows.map((row) => (
|
|
120
|
+
<React.Fragment key={row.id}>
|
|
121
|
+
<tr
|
|
122
|
+
key={row.id}
|
|
123
|
+
className="ml-tr"
|
|
124
|
+
data-testid={`row-item`}
|
|
125
|
+
style={{
|
|
126
|
+
borderBottom: "solid 1px #8c8c8c",
|
|
127
|
+
borderSpacing: "0 2em !important",
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
{row.getVisibleCells().map((cell) => (
|
|
131
|
+
<td key={cell.id} data-testid={`measure-name-${cell.id}`}>
|
|
132
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
133
|
+
</td>
|
|
134
|
+
))}
|
|
135
|
+
</tr>
|
|
136
|
+
{/* Expanded row logic should be passed in as a fragment as we look for measureset, libraryset, and will also render a table in the composite view. */}
|
|
137
|
+
{renderExpandedRow?.(row)}
|
|
138
|
+
</React.Fragment>
|
|
139
|
+
))}
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
MadieTable.propTypes = {
|
|
146
|
+
table: PropTypes.shape({
|
|
147
|
+
getHeaderGroups: PropTypes.func.isRequired,
|
|
148
|
+
getRowModel: PropTypes.func.isRequired,
|
|
149
|
+
getAllColumns: PropTypes.func.isRequired,
|
|
150
|
+
}).isRequired,
|
|
151
|
+
|
|
152
|
+
currentSort: PropTypes.string,
|
|
153
|
+
currentDirection: PropTypes.oneOf(["ASC", "DESC"]),
|
|
154
|
+
handleSort: PropTypes.func.isRequired,
|
|
155
|
+
renderExpandedRow: PropTypes.func,
|
|
156
|
+
|
|
157
|
+
id: PropTypes.string,
|
|
158
|
+
dataTestId: PropTypes.string,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
export default MadieTable;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { IconButton, InputAdornment, MenuItem } from "@mui/material";
|
|
4
|
+
import Select from "../Select"
|
|
5
|
+
import TextField from "../TextField"
|
|
6
|
+
import SearchIcon from "@mui/icons-material/Search";
|
|
7
|
+
import ClearIcon from "@mui/icons-material/Clear";
|
|
8
|
+
import { filterByOptions } from "./useFilterSearch";
|
|
9
|
+
/**
|
|
10
|
+
* Reusable component for measure filter and search UI
|
|
11
|
+
* Provides filter dropdown and search input with trigger and clear actions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const SearchAndFilter = ({
|
|
15
|
+
filterBy,
|
|
16
|
+
searchField,
|
|
17
|
+
onFilterChange,
|
|
18
|
+
onSearchChange,
|
|
19
|
+
onSearchTrigger,
|
|
20
|
+
onSearchClear,
|
|
21
|
+
filterByOpts = filterByOptions,
|
|
22
|
+
textFieldID = "test-case"
|
|
23
|
+
}) => {
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="measure-search-filters">
|
|
27
|
+
<div>
|
|
28
|
+
<Select
|
|
29
|
+
label="Filter By"
|
|
30
|
+
id="filter-by-select"
|
|
31
|
+
data-testid="filter-by-select"
|
|
32
|
+
inputProps={{ "data-testid": "filter-by-select-input" }}
|
|
33
|
+
placeHolder={{ name: "Filter By", value: "" }}
|
|
34
|
+
SelectDisplayProps={{
|
|
35
|
+
"aria-required": "true",
|
|
36
|
+
}}
|
|
37
|
+
size="small"
|
|
38
|
+
name="filterBy"
|
|
39
|
+
value={filterBy}
|
|
40
|
+
onChange={onFilterChange}
|
|
41
|
+
options={[
|
|
42
|
+
<MenuItem key="-" value="" data-testid={`filter-by--`}>
|
|
43
|
+
-
|
|
44
|
+
</MenuItem>,
|
|
45
|
+
...filterByOpts?.map((option) => {
|
|
46
|
+
return (
|
|
47
|
+
<MenuItem
|
|
48
|
+
key={option}
|
|
49
|
+
value={option}
|
|
50
|
+
data-testid={`filter-by-${option}`}
|
|
51
|
+
>
|
|
52
|
+
{option}
|
|
53
|
+
</MenuItem>
|
|
54
|
+
);
|
|
55
|
+
}),
|
|
56
|
+
]}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
<div>
|
|
60
|
+
<TextField
|
|
61
|
+
id="search"
|
|
62
|
+
label="Search"
|
|
63
|
+
placeholder="Search"
|
|
64
|
+
inputProps={{
|
|
65
|
+
"data-testid": `${textFieldID}-list-search-input`,
|
|
66
|
+
}}
|
|
67
|
+
data-testid="test-case-list-search"
|
|
68
|
+
name="searchField"
|
|
69
|
+
value={searchField}
|
|
70
|
+
onChange={onSearchChange}
|
|
71
|
+
onKeyPress={(e) => {
|
|
72
|
+
if (e.key === "Enter") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
onSearchTrigger();
|
|
75
|
+
}
|
|
76
|
+
}}
|
|
77
|
+
slotProps={{
|
|
78
|
+
input: {
|
|
79
|
+
startAdornment: (
|
|
80
|
+
<InputAdornment
|
|
81
|
+
position="start"
|
|
82
|
+
data-testid={`${textFieldID}-trigger-search`}
|
|
83
|
+
onClick={onSearchTrigger}
|
|
84
|
+
style={{ cursor: "pointer" }}
|
|
85
|
+
>
|
|
86
|
+
<SearchIcon />
|
|
87
|
+
</InputAdornment>
|
|
88
|
+
),
|
|
89
|
+
endAdornment: (
|
|
90
|
+
<InputAdornment
|
|
91
|
+
data-testid={`${textFieldID}-clear-search`}
|
|
92
|
+
position="end"
|
|
93
|
+
style={{ cursor: "pointer" }}
|
|
94
|
+
onClick={onSearchClear}
|
|
95
|
+
>
|
|
96
|
+
<IconButton>
|
|
97
|
+
<ClearIcon />
|
|
98
|
+
</IconButton>
|
|
99
|
+
</InputAdornment>
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
SearchAndFilter.propTypes = {
|
|
110
|
+
filterBy: PropTypes.string.isRequired,
|
|
111
|
+
searchField: PropTypes.string.isRequired,
|
|
112
|
+
onFilterChange: PropTypes.func.isRequired,
|
|
113
|
+
onSearchChange: PropTypes.func.isRequired,
|
|
114
|
+
onSearchTrigger: PropTypes.func.isRequired,
|
|
115
|
+
onSearchClear: PropTypes.func.isRequired,
|
|
116
|
+
filterByOpts: PropTypes.arrayOf(PropTypes.string),
|
|
117
|
+
textFieldID: PropTypes.string,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
SearchAndFilter.defaultProps = {
|
|
121
|
+
filterByOpts: filterByOptions,
|
|
122
|
+
};
|
|
123
|
+
export default SearchAndFilter;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const filterByOptions = ["Measure", "Version", "CMS ID"];
|
|
4
|
+
|
|
5
|
+
export const filterMap = {
|
|
6
|
+
Measure: "measureName",
|
|
7
|
+
Version: "version",
|
|
8
|
+
"CMS ID": "cmsId",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Custom hook for managing measure or libraries filter and search functionality
|
|
13
|
+
* Provides state and handlers for filtering and searching measures by various criteria
|
|
14
|
+
*/
|
|
15
|
+
export const useFilterSearch = (
|
|
16
|
+
onPageReset
|
|
17
|
+
) => {
|
|
18
|
+
const [filterBy, setFilterBy] = useState("");
|
|
19
|
+
const [searchField, setSearchField] = useState("");
|
|
20
|
+
const [finalSearchAndFilterby, setFinalSearchAndFilterby] =
|
|
21
|
+
useState({
|
|
22
|
+
finalSearchField: "",
|
|
23
|
+
finalFilterBy: "",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const handleFilter = (e) => {
|
|
27
|
+
setFilterBy(e.target.value);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleSearch = (e) => {
|
|
31
|
+
setSearchField(e.target.value);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const finalizeSearchCriteria = () => {
|
|
35
|
+
const finalSearchAndFilter = {
|
|
36
|
+
finalSearchField: searchField,
|
|
37
|
+
finalFilterBy: filterBy,
|
|
38
|
+
};
|
|
39
|
+
setFinalSearchAndFilterby(finalSearchAndFilter);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const blankSearchCriteria = () => {
|
|
43
|
+
setSearchField("");
|
|
44
|
+
setFilterBy("");
|
|
45
|
+
setFinalSearchAndFilterby({ finalFilterBy: "", finalSearchField: "" });
|
|
46
|
+
if (onPageReset) {
|
|
47
|
+
onPageReset();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
filterBy,
|
|
53
|
+
searchField,
|
|
54
|
+
finalSearchAndFilterby,
|
|
55
|
+
handleFilter,
|
|
56
|
+
handleSearch,
|
|
57
|
+
finalizeSearchCriteria,
|
|
58
|
+
blankSearchCriteria,
|
|
59
|
+
};
|
|
60
|
+
};
|
package/components/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import MadieDialog from "./MadieDialog";
|
|
|
17
17
|
import MadieDiscardDialog from "./MadieDiscardDialog";
|
|
18
18
|
import MadieDeleteDialog from "./MadieDeleteDialog";
|
|
19
19
|
import MadieConfirmDialog from "./MadieConfirmDialog";
|
|
20
|
+
import MadieTable from "./MadieTable";
|
|
20
21
|
import MadieSpinner from "./MadieSpinner";
|
|
21
22
|
import MadieTooltip from "./MadieTooltip";
|
|
22
23
|
import MadieTooltipIcon from "./MadieTooltipIcon";
|
|
@@ -37,13 +38,15 @@ import TextArea from "./TextArea";
|
|
|
37
38
|
import Tooltip from "./Tooltip";
|
|
38
39
|
import Toast from "./Toast";
|
|
39
40
|
import Infotip from "./Infotip";
|
|
40
|
-
import
|
|
41
|
+
import SearchAndFilter from "./SearchAndFilter";
|
|
41
42
|
import TextInput from "./TextInput";
|
|
42
43
|
import Dropdown from "./Dropdown";
|
|
43
44
|
import DSLink from "./Link";
|
|
44
45
|
import NumberInput from "./NumberInput";
|
|
45
46
|
import TruncateText from "./TruncateText/index";
|
|
46
47
|
import theme from "../themes/actionCenterTheme.js";
|
|
48
|
+
// utilities
|
|
49
|
+
import { filterByOptions, filterMap, useFilterSearch } from "./SearchAndFilter/useFilterSearch.jsx";
|
|
47
50
|
import {
|
|
48
51
|
MyApplicationsIcon,
|
|
49
52
|
UserSignInIcon,
|
|
@@ -150,6 +153,7 @@ export {
|
|
|
150
153
|
MadieDeleteDialog,
|
|
151
154
|
MadieConfirmDialog,
|
|
152
155
|
MadieSpinner,
|
|
156
|
+
MadieTable,
|
|
153
157
|
MadieTooltipIcon,
|
|
154
158
|
MadieTooltip,
|
|
155
159
|
NumberInput,
|
|
@@ -158,7 +162,7 @@ export {
|
|
|
158
162
|
RadioButton,
|
|
159
163
|
ReadOnlyTextField,
|
|
160
164
|
RichTextEditor,
|
|
161
|
-
|
|
165
|
+
SearchAndFilter,
|
|
162
166
|
Select,
|
|
163
167
|
Spinner,
|
|
164
168
|
TabPanel,
|
|
@@ -175,4 +179,8 @@ export {
|
|
|
175
179
|
DSLink,
|
|
176
180
|
TruncateText,
|
|
177
181
|
theme,
|
|
182
|
+
// utilities
|
|
183
|
+
filterByOptions,
|
|
184
|
+
filterMap,
|
|
185
|
+
useFilterSearch,
|
|
178
186
|
};
|