@madie/madie-design-system 1.2.89 → 1.2.90

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 CHANGED
@@ -1,4 +1,8 @@
1
1
  {
2
- "presets": ["@babel/preset-react", "@babel/preset-env"],
3
- "plugins": ["@babel/plugin-transform-modules-umd", "@babel/plugin-transform-class-properties"]
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
+ };
@@ -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,14 @@ import TextArea from "./TextArea";
37
38
  import Tooltip from "./Tooltip";
38
39
  import Toast from "./Toast";
39
40
  import Infotip from "./Infotip";
40
- import Search from "./Search";
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
+ import useFilterSearch from "./SearchAndFilter/useFilterSearch.jsx";
47
49
  import {
48
50
  MyApplicationsIcon,
49
51
  UserSignInIcon,
@@ -150,6 +152,7 @@ export {
150
152
  MadieDeleteDialog,
151
153
  MadieConfirmDialog,
152
154
  MadieSpinner,
155
+ MadieTable,
153
156
  MadieTooltipIcon,
154
157
  MadieTooltip,
155
158
  NumberInput,
@@ -158,7 +161,7 @@ export {
158
161
  RadioButton,
159
162
  ReadOnlyTextField,
160
163
  RichTextEditor,
161
- Search,
164
+ SearchAndFilter,
162
165
  Select,
163
166
  Spinner,
164
167
  TabPanel,
@@ -170,6 +173,7 @@ export {
170
173
  TextField,
171
174
  TextArea,
172
175
  Toast,
176
+ useFilterSearch,
173
177
  Dropdown,
174
178
  Tooltip,
175
179
  DSLink,