@madie/madie-design-system 1.2.102 → 1.2.103

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.
Files changed (56) hide show
  1. package/.babelrc +0 -1
  2. package/.storybook/main.mjs +38 -0
  3. package/README.md +18 -0
  4. package/components/Breadcrumb/Breadcrumb.stories.js +9 -8
  5. package/components/Button/Button.stories.js +0 -2
  6. package/components/CalloutBox/CalloutBox.stories.js +0 -2
  7. package/components/Card/Card.stories.js +0 -2
  8. package/components/DateField/DateField.stories.jsx +0 -2
  9. package/components/DateTimeField/DateTimeField.stories.jsx +0 -2
  10. package/components/Dropdown/Dropdown.stories.js +0 -2
  11. package/components/Footer/Footer.stories.js +11 -7
  12. package/components/HowItWorks/HowItWorks.stories.js +0 -2
  13. package/components/Infotip/Infotip.stories.js +3 -4
  14. package/components/Instant/Instant.stories.js +0 -2
  15. package/components/Link/Link.stories.js +0 -2
  16. package/components/MadieAlert/MadieAlert.stories.js +0 -2
  17. package/components/MadieConfirmDialog/MadieConfirmDialog.stories.js +0 -2
  18. package/components/MadieDeleteDialog/MadieDeleteDialog.stories.js +0 -2
  19. package/components/MadieDialog/MadieDialog.stories.js +0 -2
  20. package/components/MadieDiscardDialog/MadieDiscardDialog.stories.js +0 -2
  21. package/components/MadieSpinner/MadieSpinner.stories.js +0 -2
  22. package/components/MadieTable/MadieTable.stories.js +93 -0
  23. package/components/MadieTable/index.jsx +34 -8
  24. package/components/Modal/Modal.stories.js +19 -25
  25. package/components/NotificationBanner/NotificationBanner.stories.js +5 -4
  26. package/components/NumberInput/NumberInput.stories.js +0 -2
  27. package/components/Popover/Popover.stories.js +0 -2
  28. package/components/ReadOnlyTextField/ReadOnlyTextField.stories.js +0 -2
  29. package/components/RichTextEditor/RichTextEditor.stories.js +0 -2
  30. package/components/SearchAndFilter/index.jsx +1 -1
  31. package/components/Select/Select.stories.js +0 -2
  32. package/components/Tabs/Tabs.stories.js +0 -2
  33. package/components/TextArea/TextArea.stories.js +0 -2
  34. package/components/TextField/TextField.stories.js +0 -2
  35. package/components/TextInput/TextInput.stories.js +0 -2
  36. package/components/TimeField/TimeField.jsx +1 -1
  37. package/components/TimeField/TimeField.stories.js +0 -2
  38. package/components/Toast/Toast.stories.js +0 -2
  39. package/components/TruncateText/TruncateText.stories.js +0 -2
  40. package/components/TruncateText/index.js +12 -1
  41. package/components/index.js +0 -2
  42. package/dist/browser.js +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/react/index.js +5 -5
  45. package/dist/react/index.js.LICENSE.txt +1 -1
  46. package/dist/react/index.js.map +1 -1
  47. package/jest.config.js +0 -4
  48. package/package.json +15 -25
  49. package/styles/_main.scss +0 -6
  50. package/test/components/MadieTable.test.js +38 -0
  51. package/test/components/TruncateText.test.js +38 -0
  52. package/.storybook/main.js +0 -61
  53. package/components/Spinner/index.js +0 -9
  54. package/components/UnwrappedSpinner/index.js +0 -8
  55. package/test/__mocks__/styled-components-macro.js +0 -2
  56. package/test/__mocks__/twin.macro.js +0 -24
package/.babelrc CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "presets": ["@babel/preset-react", "@babel/preset-env"],
3
3
  "plugins": [
4
- "babel-plugin-macros",
5
4
  "@babel/plugin-transform-modules-umd",
6
5
  "@babel/plugin-transform-class-properties"
7
6
  ]
@@ -0,0 +1,38 @@
1
+ export default {
2
+ stories: ["../components/**/*.@(mdx|stories.@(js|jsx))"],
3
+
4
+ addons: [
5
+ "@storybook/addon-links",
6
+ "@storybook/addon-a11y",
7
+ "@storybook/addon-docs"
8
+ ],
9
+
10
+ framework: {
11
+ name: "@storybook/react-vite",
12
+ options: {}
13
+ },
14
+
15
+ docs: {},
16
+
17
+ typescript: {
18
+ reactDocgen: "react-docgen"
19
+ },
20
+
21
+ // stories and components are .js files that contain JSX
22
+ viteFinal: async (config) => {
23
+ config.esbuild = {
24
+ ...config.esbuild,
25
+ loader: "jsx",
26
+ include: /(components|lib|\.storybook)\/.*\.jsx?$/,
27
+ exclude: [],
28
+ };
29
+ config.optimizeDeps = {
30
+ ...config.optimizeDeps,
31
+ esbuildOptions: {
32
+ ...(config.optimizeDeps && config.optimizeDeps.esbuildOptions),
33
+ loader: { ".js": "jsx" },
34
+ },
35
+ };
36
+ return config;
37
+ },
38
+ };
package/README.md CHANGED
@@ -1,3 +1,21 @@
1
1
  # Madie/madie-design-system React
2
2
 
3
3
  This directory contains react implementations of the components for Madie.
4
+
5
+ ## Testing
6
+
7
+ Use Node 20 to match CI. The test suite uses Jest 30, `babel-jest` 30, and
8
+ `jest-environment-jsdom` 30 (JSDOM 26).
9
+
10
+ ```sh
11
+ npm ci
12
+ npm test
13
+ npm run coverage
14
+ ```
15
+
16
+ To run selected suites, use `npm test -- --runTestsByPath test/components/Button.test.js`
17
+ or `npm test -- --testPathPatterns=Button`.
18
+
19
+ When updating tests, follow the [Jest 30 migration guide](https://jestjs.io/docs/upgrading-to-jest30).
20
+ Use canonical matcher names such as `toHaveBeenCalled` rather than the removed
21
+ `toBeCalled` aliases.
@@ -1,17 +1,18 @@
1
1
  import React from "react";
2
2
  import Breadcrumb from "./index";
3
- import { withKnobs, boolean } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "Breadcrumb",
7
6
  component: Breadcrumb,
8
- decorators: [withKnobs],
7
+ args: {
8
+ newBreadcrumb: true,
9
+ },
9
10
  };
10
11
 
11
- export const ExampleLightBreadcrumb = () => (
12
+ export const ExampleLightBreadcrumb = (args) => (
12
13
  <div className="qppds qpp-u-padding--16 qpp-u-fill--blue-80">
13
14
  <Breadcrumb
14
- newBreadcrumb={boolean("newBreadcrumb", true)}
15
+ newBreadcrumb={args.newBreadcrumb}
15
16
  crumbs={[
16
17
  {
17
18
  url: "/",
@@ -32,10 +33,10 @@ export const ExampleLightBreadcrumb = () => (
32
33
 
33
34
  ExampleLightBreadcrumb.storyName = "Light";
34
35
 
35
- export const ExampleScreenreaderOnlyBreadcrumb = () => (
36
+ export const ExampleScreenreaderOnlyBreadcrumb = (args) => (
36
37
  <div className="qppds qpp-u-padding--16 qpp-u-fill--blue-80">
37
38
  <Breadcrumb
38
- newBreadcrumb={boolean("newBreadcrumb", true)}
39
+ newBreadcrumb={args.newBreadcrumb}
39
40
  crumbs={[
40
41
  {
41
42
  url: "/",
@@ -50,10 +51,10 @@ export const ExampleScreenreaderOnlyBreadcrumb = () => (
50
51
  );
51
52
  ExampleScreenreaderOnlyBreadcrumb.storyName = "SR Only Breadcrumb";
52
53
 
53
- export const ExampleDarkBreadcrumb = () => (
54
+ export const ExampleDarkBreadcrumb = (args) => (
54
55
  <div className="qppds qpp-u-padding--16">
55
56
  <Breadcrumb
56
- newBreadcrumb={boolean("newBreadcrumb", true)}
57
+ newBreadcrumb={args.newBreadcrumb}
57
58
  dark
58
59
  crumbs={[
59
60
  {
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import FeatherIcon from "feather-icons-react";
5
4
 
6
5
  import Button, { TextButton } from "./index";
@@ -8,7 +7,6 @@ import Button, { TextButton } from "./index";
8
7
  export default {
9
8
  title: "Button",
10
9
  component: Button,
11
- decorators: [withKnobs],
12
10
  };
13
11
 
14
12
  const Container = ({ className = "", children }) => (
@@ -1,12 +1,10 @@
1
1
  import React, { useState } from "react";
2
- import { withKnobs } from "@storybook/addon-knobs";
3
2
 
4
3
  import CalloutBox from "./index";
5
4
 
6
5
  export default {
7
6
  title: "CalloutBox",
8
7
  component: CalloutBox,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  export const Default = () => {
@@ -1,12 +1,10 @@
1
1
  import React from "react";
2
- import { withKnobs } from "@storybook/addon-knobs";
3
2
 
4
3
  import Card from "./index";
5
4
 
6
5
  export default {
7
6
  title: "Card",
8
7
  component: Card,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  export const Default = () => {
@@ -1,11 +1,9 @@
1
1
  import React, { useState } from "react";
2
2
  import DateField from "./DateField";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "DateField",
7
6
  component: DateField,
8
- decorators: [withKnobs],
9
7
  };
10
8
  export const DateFieldComponent = (args) => {
11
9
  const [value, setValue] = useState("");
@@ -1,11 +1,9 @@
1
1
  import React, { useState } from "react";
2
2
  import DateTimeField from "./DateTimeField";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "DateTimeField",
7
6
  component: DateTimeField,
8
- decorators: [withKnobs],
9
7
  };
10
8
 
11
9
  export const TimeDateFieldComponent = (args) => {
@@ -1,11 +1,9 @@
1
1
  import React from "react";
2
2
  import Dropdown from "./index";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "Dropdown",
7
6
  component: Dropdown,
8
- decorators: [withKnobs],
9
7
  };
10
8
 
11
9
  export const DropdownField = () => (
@@ -1,19 +1,23 @@
1
1
  import React from "react";
2
2
  import Footer from "./FooterUI";
3
- import { withKnobs, boolean, text } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "Footer",
7
6
  component: Footer,
8
- decorators: [withKnobs],
7
+ args: {
8
+ isFullWidth: false,
9
+ buildVersion: "BUILD VERSION",
10
+ isNewFooter: false,
11
+ showHcdResearch: false,
12
+ },
9
13
  };
10
14
 
11
- export const ExampleFooter = () => (
15
+ export const ExampleFooter = (args) => (
12
16
  <Footer
13
- isFullWidth={boolean("isFullWidth", false)}
14
- buildVersion={text("buildVersion", "BUILD VERSION")}
15
- isNewFooter={boolean("isNewFooter", false)}
16
- showHcdResearch={boolean("showHcdResearch", false)}
17
+ isFullWidth={args.isFullWidth}
18
+ buildVersion={args.buildVersion}
19
+ isNewFooter={args.isNewFooter}
20
+ showHcdResearch={args.showHcdResearch}
17
21
  />
18
22
  );
19
23
 
@@ -1,12 +1,10 @@
1
1
  import React, { useState } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import HowItWorks from "./index";
5
4
 
6
5
  export default {
7
6
  title: "HowItWorks",
8
7
  component: HowItWorks,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  const Wrapper = ({ children }) => (
@@ -1,12 +1,11 @@
1
1
  import React from "react";
2
2
  import Infotip from "./index";
3
- import { withKnobs, boolean } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "Infotip",
7
6
  component: Infotip,
7
+ args: { lightIcon: false },
8
8
  decorators: [
9
- withKnobs,
10
9
  (storyFn) => (
11
10
  <div style={{ marginTop: "120px", marginLeft: "100px" }}>
12
11
  {storyFn()}
@@ -15,8 +14,8 @@ export default {
15
14
  ],
16
15
  };
17
16
 
18
- export const ExampleInfoTip = () => (
19
- <Infotip label="Tooltip Content!" lightIcon={boolean("lightIcon", false)} />
17
+ export const ExampleInfoTip = (args) => (
18
+ <Infotip label="Tooltip Content!" lightIcon={args.lightIcon} />
20
19
  );
21
20
 
22
21
  ExampleInfoTip.storyName = "qpp themed Infotip";
@@ -1,11 +1,9 @@
1
1
  import React, { useState } from "react";
2
2
  import Instant from "./index";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "Instant",
7
6
  component: Instant,
8
- decorators: [withKnobs],
9
7
  };
10
8
 
11
9
  export const InstantComponent = (args) => {
@@ -1,12 +1,10 @@
1
1
  import React from "react";
2
2
  import DSLink from "./index";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import OpenInNewIcon from "@mui/icons-material/OpenInNew";
5
4
 
6
5
  export default {
7
6
  title: "Link",
8
7
  component: DSLink,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  export const LinkLabel = () => <DSLink href="#">Link Label</DSLink>;
@@ -1,12 +1,10 @@
1
1
  import React from "react";
2
2
  import MadieAlert from "./index";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
 
5
4
  export default {
6
5
  title: "Madie Alert",
7
6
  component: MadieAlert,
8
7
  decorators: [
9
- withKnobs,
10
8
  (storyFn) => (
11
9
  <div style={{ padding: "120px", backgroundColor: "gray" }}>
12
10
  {storyFn()}
@@ -1,12 +1,10 @@
1
1
  import React, { useState } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import MadieConfirmDialog from "./index";
5
4
  import Button from "../Button";
6
5
  export default {
7
6
  title: "MadieConfirmDialog",
8
7
  component: MadieConfirmDialog,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  const Wrapper = ({ children }) => (
@@ -1,12 +1,10 @@
1
1
  import React, { useState } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import MadieDeleteDialog from "./index";
5
4
  import Button from "../Button";
6
5
  export default {
7
6
  title: "MadieDeleteDialog",
8
7
  component: MadieDeleteDialog,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  const Wrapper = ({ children }) => (
@@ -1,6 +1,5 @@
1
1
  import React, { useState } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import MadieDialog from "./index";
5
4
  import TextArea from "../TextArea/index";
6
5
  import Select from "../Select/index";
@@ -11,7 +10,6 @@ import { DialogContent, Typography } from "@mui/material";
11
10
  export default {
12
11
  title: "MadieDialog",
13
12
  component: MadieDialog,
14
- decorators: [withKnobs],
15
13
  };
16
14
 
17
15
  const Wrapper = ({ children }) => (
@@ -1,12 +1,10 @@
1
1
  import React, { useState } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import MadieDiscardDialog from "./index";
5
4
  import Button from "../Button";
6
5
  export default {
7
6
  title: "MadieDiscardDialog",
8
7
  component: MadieDiscardDialog,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  const Wrapper = ({ children }) => (
@@ -1,12 +1,10 @@
1
1
  import React from "react";
2
2
  import PropTypes from "prop-types";
3
- import { withKnobs } from "@storybook/addon-knobs";
4
3
  import MadieSpinner from "./index";
5
4
 
6
5
  export default {
7
6
  title: "MadieSpinner",
8
7
  component: MadieSpinner,
9
- decorators: [withKnobs],
10
8
  };
11
9
 
12
10
  const Wrapper = ({ children }) => (
@@ -0,0 +1,93 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import {
3
+ createColumnHelper,
4
+ getCoreRowModel,
5
+ getSortedRowModel,
6
+ useReactTable,
7
+ } from "@tanstack/react-table";
8
+ import MadieTable from "./index";
9
+ import TruncateText from "../TruncateText";
10
+
11
+ export default {
12
+ title: "Madie Table",
13
+ component: MadieTable,
14
+ };
15
+
16
+ const longDescription =
17
+ "Addressing Social Needs in the Inpatient Setting Part 4 of 4 Housing Domain. This description is intentionally long so that it is truncated and exposes a keyboard focusable Show more button.";
18
+
19
+ const rows = [
20
+ { id: "1", name: "Measure A", version: "1.0.000", description: longDescription },
21
+ { id: "2", name: "Measure B", version: "0.0.001", description: "Short description" },
22
+ { id: "3", name: "Measure C", version: "2.1.000", description: longDescription },
23
+ ];
24
+
25
+ const columnHelper = createColumnHelper();
26
+
27
+ const useStoryTable = (data) => {
28
+ const [sorting, setSorting] = useState([]);
29
+ const columns = useMemo(
30
+ () => [
31
+ columnHelper.accessor("name", { header: "Name" }),
32
+ columnHelper.accessor("version", { header: "Version" }),
33
+ columnHelper.accessor("description", {
34
+ header: "Description",
35
+ enableSorting: false,
36
+ cell: (info) => (
37
+ <TruncateText
38
+ text={info.getValue()}
39
+ maxLength={60}
40
+ dataTestId={`description-${info.row.original.id}`}
41
+ />
42
+ ),
43
+ }),
44
+ ],
45
+ []
46
+ );
47
+ const table = useReactTable({
48
+ data,
49
+ columns,
50
+ state: { sorting },
51
+ onSortingChange: setSorting,
52
+ getCoreRowModel: getCoreRowModel(),
53
+ getSortedRowModel: getSortedRowModel(),
54
+ });
55
+
56
+ // asc -> desc -> cleared, matching how the consuming apps drive MadieTable
57
+ const handleSort = (columnId) => {
58
+ const current = sorting[0];
59
+ if (!current || current.id !== columnId) {
60
+ setSorting([{ id: columnId, desc: false }]);
61
+ } else if (!current.desc) {
62
+ setSorting([{ id: columnId, desc: true }]);
63
+ } else {
64
+ setSorting([]);
65
+ }
66
+ };
67
+
68
+ return {
69
+ table,
70
+ handleSort,
71
+ currentSort: sorting[0]?.id,
72
+ currentDirection: sorting[0] ? (sorting[0].desc ? "DESC" : "ASC") : undefined,
73
+ };
74
+ };
75
+
76
+ export const WithTruncatedText = () => {
77
+ const tableProps = useStoryTable(rows);
78
+ return (
79
+ <div className="measure-table" style={{ padding: 24 }}>
80
+ <MadieTable {...tableProps} ariaLabel="Measures" />
81
+ </div>
82
+ );
83
+ };
84
+ WithTruncatedText.storyName = "With TruncateText cells";
85
+
86
+ export const Empty = () => {
87
+ const tableProps = useStoryTable([]);
88
+ return (
89
+ <div className="measure-table" style={{ padding: 24 }}>
90
+ <MadieTable {...tableProps} ariaLabel="Measures" />
91
+ </div>
92
+ );
93
+ };
@@ -4,8 +4,20 @@ import { flexRender } from "@tanstack/react-table";
4
4
  import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
5
5
  import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
6
6
  import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
7
- import tw from "twin.macro";
8
- import "styled-components/macro";
7
+ import styled from "@emotion/styled";
8
+
9
+ const StyledTable = styled.table`
10
+ min-width: 100%;
11
+ `;
12
+
13
+ const TH = styled.th`
14
+ padding: 0.75rem;
15
+ text-align: left;
16
+ font-size: 0.875rem;
17
+ line-height: 1.25rem;
18
+ font-weight: 700;
19
+ text-transform: capitalize;
20
+ `;
9
21
 
10
22
  const MadieTable = ({
11
23
  table,
@@ -15,16 +27,15 @@ const MadieTable = ({
15
27
  renderExpandedRow,
16
28
  emptyMessage = "No results were found",
17
29
  id="measureListTable",
18
- dataTestId = "measure-list-tbl"
30
+ dataTestId = "measure-list-tbl",
31
+ ariaLabel,
19
32
  }) => {
20
- const TH = tw.th`p-3 text-left text-sm font-bold capitalize`;
21
33
  const [hoveredHeader, setHoveredHeader] = useState(null);
22
34
 
23
35
  return (
24
- <table
25
- /* eslint-disable-next-line react/no-unknown-property */
26
- tw="min-w-full"
36
+ <StyledTable
27
37
  id={id}
38
+ aria-label={ariaLabel}
28
39
  data-testid={dataTestId}
29
40
  className="tcl-table"
30
41
  style={{
@@ -39,11 +50,23 @@ const MadieTable = ({
39
50
  <tr key={headerGroup.id}>
40
51
  {headerGroup.headers.map((header) => {
41
52
  const isHovered = hoveredHeader?.includes(header.id);
53
+ const isSorted =
54
+ currentSort === header.column.id.replace("_", ".");
55
+ const ariaSort = header.column.getCanSort()
56
+ ? isSorted
57
+ ? currentDirection === "ASC"
58
+ ? "ascending"
59
+ : currentDirection === "DESC"
60
+ ? "descending"
61
+ : "none"
62
+ : "none"
63
+ : undefined;
42
64
 
43
65
  return (
44
66
  <TH
45
67
  key={header.id}
46
68
  scope="col"
69
+ aria-sort={ariaSort}
47
70
  onMouseEnter={() => setHoveredHeader(header.id)}
48
71
  onMouseLeave={() => setHoveredHeader(null)}
49
72
  className="header-cell"
@@ -59,6 +82,8 @@ const MadieTable = ({
59
82
  onClick={() =>
60
83
  handleSort(header.id.replace("_", "."))
61
84
  }
85
+ onFocus={() => setHoveredHeader(header.id)}
86
+ onBlur={() => setHoveredHeader(null)}
62
87
  data-testid={`header-${header.id.replace("_", ".")}`}
63
88
  title={
64
89
  header.column.getCanSort()
@@ -139,7 +164,7 @@ const MadieTable = ({
139
164
  </React.Fragment>
140
165
  ))}
141
166
  </tbody>
142
- </table>
167
+ </StyledTable>
143
168
  );
144
169
  };
145
170
 
@@ -157,6 +182,7 @@ MadieTable.propTypes = {
157
182
  emptyMessage: PropTypes.node,
158
183
 
159
184
  id: PropTypes.string,
185
+ ariaLabel: PropTypes.string,
160
186
  dataTestId: PropTypes.string,
161
187
  };
162
188