Repository: mapbox/mr-ui
Pull request #199 (2022-10-04 16:37:18)

Support disabled state in modal actions
To address downstream usage in Studio


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b67047b..8c8c00e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@ Changelog
 
 ## HEAD
 
+- Support `disabled` option in modal actions. [#199](https://github.com/mapbox/mr-ui/pull/199)
+
+
 ## 2.0.0
 
 - [fix] added new styling for ControlToggleGroup component that is consistent with actual usage.
diff --git a/src/components/modal/modal-actions.tsx b/src/components/modal/modal-actions.tsx
index c1d11c1..d7bcd62 100644
--- a/src/components/modal/modal-actions.tsx
+++ b/src/components/modal/modal-actions.tsx
@@ -7,14 +7,17 @@ interface Props {
     text: string;
     callback: () => void;
     destructive?: boolean;
+    disabled?: boolean;
   },
   secondaryAction?: {
     text: string;
     callback: () => void;
+    disabled?: boolean;
   },
   tertiaryAction?: {
     text: string;
     callback: () => void;
+    disabled?: boolean;
   }
 }
 
@@ -34,6 +37,7 @@ export default function ModalActions({
           variant="secondary"
           size="medium"
           onClick={secondaryAction.callback}
+          disabled={secondaryAction.disabled}
           data-test="secondary-modal-action"
           passthroughProps={{ 'aria-label': secondaryAction.text }}
         >
@@ -58,6 +62,7 @@ export default function ModalActions({
           aria-label={tertiaryAction.text}
           variant="tertiary"
           onClick={tertiaryAction.callback}
+          disabled={tertiaryAction.disabled}
           data-test="tertiary-modal-action"
         >
           {tertiaryAction.text}
@@ -77,6 +82,7 @@ export default function ModalActions({
           variant={primaryButtonVariant}
           size="medium"
           onClick={primaryAction.callback}
+          disabled={primaryAction.disabled}
           data-test="primary-modal-action"
           passthroughProps={{
             tabIndex: 0,
@@ -96,14 +102,17 @@ ModalActions.propTypes = {
   primaryAction: PropTypes.shape({
     text: PropTypes.string.isRequired,
     callback: PropTypes.func.isRequired,
-    destructive: PropTypes.bool
+    destructive: PropTypes.bool,
+    disabled: PropTypes.bool
   }).isRequired,
   secondaryAction: PropTypes.shape({
     text: PropTypes.string.isRequired,
-    callback: PropTypes.func.isRequired
+    callback: PropTypes.func.isRequired,
+    disabled: PropTypes.bool
   }),
   tertiaryAction: PropTypes.shape({
     text: PropTypes.string.isRequired,
-    callback: PropTypes.func.isRequired
+    callback: PropTypes.func.isRequired,
+    disabled: PropTypes.bool
   })
 };
diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx
index 74ed3b3..ae6cd9e 100644
--- a/src/components/modal/modal.tsx
+++ b/src/components/modal/modal.tsx
@@ -20,14 +20,17 @@ interface Props {
     text: string;
     callback: () => void;
     destructive?: boolean;
+    disabled?: boolean;
   },
   secondaryAction?: {
     text: string;
     callback: () => void;
+    disabled?: boolean;
   },
   tertiaryAction?: {
     text: string;
     callback: () => void;
+    disabled?: boolean;
   }
 }
 
@@ -181,9 +184,8 @@ Modal.propTypes = {
    */
   onExit: PropTypes.func,
   /**
-   * A function that should the primary application node, which should be
-   * `aria-hidden` when the modal is open. By default, returns
-   * `document.getElementById('app')`.
+   * Modal container size. Options are `small`, `large`, or `auto`. If `auto`
+   * is provided, a width is not specified.
    */
   size: PropTypes.oneOf(['small', 'large', 'auto']),
   /**


Test patch:
diff --git a/src/components/modal/modal-actions.test.tsx b/src/components/modal/modal-actions.test.tsx
index 07b3dfa..bc0060a 100644
--- a/src/components/modal/modal-actions.test.tsx
+++ b/src/components/modal/modal-actions.test.tsx
@@ -94,6 +94,30 @@ describe('ModalActions', () => {
       fireEvent.click(actions[2]);
       expect(mockedTertiaryCallback).toHaveBeenCalledTimes(1);
     });
+
+    test('disabled callbacks', () => {
+      const mockedPrimaryCallback = jest.fn();
+      const mockedSecondaryCallback = jest.fn();
+      const mockedTertiaryCallback = jest.fn();
+      render(
+        <ModalActions
+          primaryAction={{ text: 'Okay', disabled: true, callback: mockedPrimaryCallback }}
+          secondaryAction={{ text: 'Cancel', disabled: true, callback: mockedSecondaryCallback }}
+          tertiaryAction={{ text: 'Exit', disabled: true, callback: mockedTertiaryCallback }}
+        />
+      );
+
+      const actions = screen.queryAllByRole('button');
+
+      fireEvent.click(actions[0]);
+      expect(mockedPrimaryCallback).not.toHaveBeenCalled();
+
+      fireEvent.click(actions[1]);
+      expect(mockedSecondaryCallback).not.toHaveBeenCalled();
+
+      fireEvent.click(actions[2]);
+      expect(mockedTertiaryCallback).not.toHaveBeenCalled();
+    });
   });
 
   describe('primary and tertiary', () => {