@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 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,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 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
+ // 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
- Search,
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
  };