Adaptive Dialog

Adaptiveness

  • Android & iOS: native styling
  • Stretches it's actions on the extra-small breakpoint (customizable using the AdaptiveDialogActions.stretchBreakpoint prop)
  • variant="tall" defaults to full screen mode on the extra-small breakpoint (customizable using the fullScreenBreakpoint prop)
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import AdaptiveModeDemo from "@/shared/adaptiveModeDemo";
import Stack from "@mui/material/Stack";
import { useAdaptiveMode } from "adaptive-material-ui/adaptiveMode";
import { AdaptiveButton } from "adaptive-material-ui/components/button";
import {
  AdaptiveDialog,
  AdaptiveDialogActions,
} from "adaptive-material-ui/components/dialog";
import { useState } from "react";

function Content() {
  const [isShortOpen, setIsShortOpen] = useState(false);
  const [isTallOpen, setIsTallOpen] = useState(false);
  const adaptiveMode = useAdaptiveMode();

  return (
    <>
      <Stack direction="row" spacing={2}>
        <AdaptiveButton
          onClick={() => setIsShortOpen(true)}
          variant="contained"
        >
          Open Short
        </AdaptiveButton>
        <AdaptiveButton onClick={() => setIsTallOpen(true)} variant="contained">
          Open Tall
        </AdaptiveButton>
      </Stack>
      <AdaptiveDialog
        open={isShortOpen}
        onClose={() => setIsShortOpen(false)}
        variant="short"
      >
        <DialogTitle>Use Google's location service?</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Let Google help apps determine location. This means sending
            anonymous location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <AdaptiveDialogActions>
          <AdaptiveButton
            color={adaptiveMode === "ios" ? "inherit" : undefined}
            onClick={() => setIsShortOpen(false)}
          >
            Disagree
          </AdaptiveButton>
          <AdaptiveButton onClick={() => setIsShortOpen(false)}>
            Agree
          </AdaptiveButton>
        </AdaptiveDialogActions>
      </AdaptiveDialog>
      <AdaptiveDialog
        open={isTallOpen}
        onClose={() => setIsTallOpen(false)}
        variant="tall"
      >
        <DialogTitle>Subscribe</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Cras mattis
            {Array.from(Array(10).keys())
              .map(() => `metus felis, varius ut elit eu, convallis`)
              .join(" ")}
          </DialogContentText>
        </DialogContent>
        <AdaptiveDialogActions>
          <AdaptiveButton
            color={adaptiveMode === "ios" ? "inherit" : undefined}
            onClick={() => setIsTallOpen(false)}
          >
            Cancel
          </AdaptiveButton>
          <AdaptiveButton onClick={() => setIsTallOpen(false)}>
            Subscribe
          </AdaptiveButton>
        </AdaptiveDialogActions>
      </AdaptiveDialog>
    </>
  );
}

export default function () {
  return (
    <AdaptiveModeDemo>
      <Content />
    </AdaptiveModeDemo>
  );
}

Actions

Stretching

Actions have the same stretching as the Adaptive Button Stack using the AdaptiveDialogActions.stretchBreakpoint prop

import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import DemoSelector from "@/shared/demoSelector";
import Stack from "@mui/material/Stack";
import { AdaptiveButton } from "adaptive-material-ui/components/button";
import {
  AdaptiveDialog,
  AdaptiveDialogActions,
} from "adaptive-material-ui/components/dialog";
import { useState } from "react";

export default function () {
  const [open, setOpen] = useState(false);
  const [numberOfButtons, setNumberOfButtons] = useState("1");

  return (
    <Stack spacing={3} sx={{ alignItems: "flex-start" }}>
      <DemoSelector
        onChange={setNumberOfButtons}
        options={[
          { label: "One Action", value: "1" },
          { label: "Two Actions", value: "2" },
          { label: "Three Actions", value: "3" },
        ]}
        value={numberOfButtons}
      />
      <AdaptiveButton onClick={() => setOpen(true)} variant="contained">
        Open {numberOfButtons} Actions
      </AdaptiveButton>

      <AdaptiveDialog
        open={open}
        onClose={() => setOpen(false)}
        variant="short"
      >
        <DialogTitle>Use Google's location service?</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Let Google help apps determine location. This means sending
            anonymous location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <AdaptiveDialogActions stretchBreakpoint={true}>
          {["Submit", "Cancel", "Delete"]
            .slice(0, parseInt(numberOfButtons))
            .reverse()
            .map((b) => (
              <AdaptiveButton key={b} onClick={() => setOpen(false)}>
                {b}
              </AdaptiveButton>
            ))}
        </AdaptiveDialogActions>
      </AdaptiveDialog>
    </Stack>
  );
}

Split Alignment

adaptiveDialogActionsClasses.alignStart can be used to align actions at both ends of the dialog when stacked horizontally

import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import { AdaptiveButton } from "adaptive-material-ui/components/button";
import {
  AdaptiveDialog,
  AdaptiveDialogActions,
  adaptiveDialogActionsClasses,
} from "adaptive-material-ui/components/dialog";
import { useState } from "react";

export default function () {
  const [open, setOpen] = useState(false);

  return (
    <div>
      <AdaptiveButton onClick={() => setOpen(true)} variant="contained">
        Open
      </AdaptiveButton>

      <AdaptiveDialog
        open={open}
        onClose={() => setOpen(false)}
        variant="short"
      >
        <DialogTitle>Use Google's location service?</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Let Google help apps determine location. This means sending
            anonymous location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <AdaptiveDialogActions stretchBreakpoint={false}>
          <AdaptiveButton
            className={adaptiveDialogActionsClasses.alignStart}
            onClick={() => setOpen(false)}
          >
            Delete
          </AdaptiveButton>
          <AdaptiveButton onClick={() => setOpen(false)}>Cancel</AdaptiveButton>
          <AdaptiveButton onClick={() => setOpen(false)}>Submit</AdaptiveButton>
        </AdaptiveDialogActions>
      </AdaptiveDialog>
    </div>
  );
}

buttonDefaultProps

AdaptiveDialogActions.buttonDefaultProps can be used to set props on all the child buttons on all devices. To apply them to only specific devices you can use the useAdaptiveMode hook.

import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import AdaptiveModeDemo from "@/shared/adaptiveModeDemo";
import { AdaptiveButton } from "adaptive-material-ui/components/button";
import {
  AdaptiveDialog,
  AdaptiveDialogActions,
} from "adaptive-material-ui/components/dialog";
import { useState } from "react";

export default function () {
  const [open, setOpen] = useState(false);

  return (
    <AdaptiveModeDemo adaptiveMode="desktop">
      <AdaptiveButton onClick={() => setOpen(true)} variant="contained">
        Open
      </AdaptiveButton>

      <AdaptiveDialog
        open={open}
        onClose={() => setOpen(false)}
        variant="short"
      >
        <DialogTitle>Use Google's location service?</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Let Google help apps determine location. This means sending
            anonymous location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <AdaptiveDialogActions buttonDefaultProps={{ variant: "outlined" }}>
          <AdaptiveButton onClick={() => setOpen(false)}>Cancel</AdaptiveButton>
          <AdaptiveButton onClick={() => setOpen(false)}>Submit</AdaptiveButton>
        </AdaptiveDialogActions>
      </AdaptiveDialog>
    </AdaptiveModeDemo>
  );
}

Background Limitations

The iOS dialog background uses backdrop-filter which has limitations based on it's parent elements. Typically most of these limitations will be avoided as long as disablePortal={true} isn't set.

API

MUI Dialog API with the following changes:

fullScreenBreakpoint

Breakpoint or screen width in px and below at which the dialog starts rendering full screen. This behavior can be disabled by setting the fullScreen or variant property or setting it to false

  • Type: "xs" | "sm" | "md" | "lg" | number | boolean
  • Default: xs

variant

Short dialogs ignore the fullScreenBreakpoint logic

  • Type: "short" | "tall"
  • Default: tall

AdaptiveDialogActions

MUI DialogActions API with the following changes:

buttonDefaultProps

Props passed to child AdaptiveButton components

  • Type: AdaptiveButtonProps
  • Default: adaptiveMode === "ios" ? { disableElevation: true, round: true, size: "large", variant: "contained" } : undefined

stretchBreakpoint

Breakpoint or screen width in px and below at which the children will be stretched. This behavior can be enabled/disabled always by setting it to true/false

  • Type: "xs" | "sm" | "md" | "lg" | number | boolean
  • Default: xs

Browser Compatibility