[web] Add a hook for detecting mobile sized screens (#2490)
This commit is contained in:
@@ -5,7 +5,6 @@ import log from "@/base/log";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { CenteredFlex } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import EnteButton from "@ente/shared/components/EnteButton";
|
||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||
import InfoItem from "@ente/shared/components/Info/InfoItem";
|
||||
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
|
||||
@@ -16,14 +15,7 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import KeyIcon from "@mui/icons-material/Key";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Stack,
|
||||
Typography,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { Box, Stack, Typography, styled, useMediaQuery } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
@@ -265,8 +257,34 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
|
||||
passkey,
|
||||
onUpdateOrDeletePasskey,
|
||||
}) => {
|
||||
const { setDialogBoxAttributesV2 } = useAppContext();
|
||||
|
||||
const [showRenameDialog, setShowRenameDialog] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
|
||||
const showDeleteConfirmationDialog = useCallback(() => {
|
||||
const handleDelete = async (setLoading: (value: boolean) => void) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await deletePasskey(ensure(token), ensure(passkey).id);
|
||||
onUpdateOrDeletePasskey();
|
||||
} catch (e) {
|
||||
log.error("Failed to delete passkey", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
setDialogBoxAttributesV2({
|
||||
title: t("delete_passkey"),
|
||||
content: t("delete_passkey_confirmation"),
|
||||
proceed: {
|
||||
text: t("DELETE"),
|
||||
action: handleDelete,
|
||||
variant: "critical",
|
||||
},
|
||||
close: { text: t("CANCEL") },
|
||||
});
|
||||
}, [token, passkey, onUpdateOrDeletePasskey, setDialogBoxAttributesV2]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -297,9 +315,7 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
|
||||
/>
|
||||
<MenuItemDivider />
|
||||
<EnteMenuItem
|
||||
onClick={() => {
|
||||
setShowDeleteDialog(true);
|
||||
}}
|
||||
onClick={showDeleteConfirmationDialog}
|
||||
startIcon={<DeleteIcon />}
|
||||
label={t("delete_passkey")}
|
||||
color="critical"
|
||||
@@ -321,19 +337,6 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{token && passkey && (
|
||||
<DeletePasskeyDialog
|
||||
open={showDeleteDialog}
|
||||
onClose={() => setShowDeleteDialog(false)}
|
||||
token={token}
|
||||
passkey={passkey}
|
||||
onDeletePasskey={() => {
|
||||
setShowDeleteDialog(false);
|
||||
onUpdateOrDeletePasskey();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -387,63 +390,3 @@ const RenamePasskeyDialog: React.FC<RenamePasskeyDialogProps> = ({
|
||||
</DialogBoxV2>
|
||||
);
|
||||
};
|
||||
|
||||
interface DeletePasskeyDialogProps {
|
||||
/** If `true`, then the dialog is shown. */
|
||||
open: boolean;
|
||||
/** Callback to invoke when the dialog wants to be closed. */
|
||||
onClose: () => void;
|
||||
/** Auth token for API requests. */
|
||||
token: string;
|
||||
/** The {@link Passkey} to delete. */
|
||||
passkey: Passkey;
|
||||
/** Callback to invoke when the passkey is deleted. */
|
||||
onDeletePasskey: () => void;
|
||||
}
|
||||
|
||||
const DeletePasskeyDialog: React.FC<DeletePasskeyDialogProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
token,
|
||||
passkey,
|
||||
onDeletePasskey,
|
||||
}) => {
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const fullScreen = useMediaQuery("(max-width: 428px)");
|
||||
|
||||
const handleConfirm = async () => {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
await deletePasskey(token, passkey.id);
|
||||
onDeletePasskey();
|
||||
} catch (e) {
|
||||
log.error("Failed to delete passkey", e);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogBoxV2
|
||||
fullWidth
|
||||
{...{ open, onClose, fullScreen }}
|
||||
attributes={{ title: t("delete_passkey") }}
|
||||
>
|
||||
<Stack spacing={"8px"}>
|
||||
<Typography>{t("delete_passkey_confirmation")}</Typography>
|
||||
<EnteButton
|
||||
type="submit"
|
||||
size="large"
|
||||
color="critical"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
{t("DELETE")}
|
||||
</EnteButton>
|
||||
<Button size="large" color={"secondary"} onClick={onClose}>
|
||||
{t("CANCEL")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogBoxV2>
|
||||
);
|
||||
};
|
||||
|
||||
10
web/packages/base/hooks.ts
Normal file
10
web/packages/base/hooks.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* Return true if the screen width is classified as a "mobile" size.
|
||||
*
|
||||
* We use the MUI "sm" (small, 600px) breakpoint as the cutoff. This hook will
|
||||
* return true if the size of the window's width is less than 600px.
|
||||
*/
|
||||
export const useIsMobileWidth = () =>
|
||||
useMediaQuery(useTheme().breakpoints.down("sm"));
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import { ensureOk } from "@/base/http";
|
||||
import { getKV, removeKV, setKV } from "@/base/kv";
|
||||
import log from "@/base/log";
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
InputAdornment,
|
||||
Link,
|
||||
TextField,
|
||||
useMediaQuery,
|
||||
type ModalProps,
|
||||
} from "@mui/material";
|
||||
import { useFormik } from "formik";
|
||||
@@ -33,7 +33,7 @@ interface DevSettingsProps {
|
||||
* See: [Note: Configuring custom server].
|
||||
*/
|
||||
export const DevSettings: React.FC<DevSettingsProps> = ({ open, onClose }) => {
|
||||
const fullScreen = useMediaQuery("(max-width: 428px)");
|
||||
const fullScreen = useIsMobileWidth();
|
||||
|
||||
const handleDialogClose: ModalProps["onClose"] = (_, reason: string) => {
|
||||
// Don't close on backdrop clicks.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ensureElectron } from "@/base/electron";
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import { ut } from "@/base/i18n";
|
||||
import ArrowForward from "@mui/icons-material/ArrowForward";
|
||||
import {
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
Typography,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import React, { useEffect } from "react";
|
||||
import { didShowWhatsNew } from "../services/changelog";
|
||||
@@ -29,7 +29,7 @@ interface WhatsNewProps {
|
||||
* last time this dialog was shown.
|
||||
*/
|
||||
export const WhatsNew: React.FC<WhatsNewProps> = ({ open, onClose }) => {
|
||||
const fullScreen = useMediaQuery("(max-width: 428px)");
|
||||
const fullScreen = useIsMobileWidth();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) void didShowWhatsNew(ensureElectron());
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { ButtonProps } from "@mui/material";
|
||||
|
||||
/**
|
||||
* Customize the properties of the dialog box.
|
||||
*
|
||||
* Our custom dialog box helpers are meant for small message boxes, usually
|
||||
* meant to confirm some user action. If more customization is needed, it might
|
||||
* be a better idea to reach out for a bespoke MUI {@link DialogBox} instead.
|
||||
*/
|
||||
export interface DialogBoxAttributesV2 {
|
||||
icon?: React.ReactNode;
|
||||
/**
|
||||
@@ -13,15 +20,43 @@ export interface DialogBoxAttributesV2 {
|
||||
title?: React.ReactNode;
|
||||
staticBackdrop?: boolean;
|
||||
nonClosable?: boolean;
|
||||
/**
|
||||
* The dialog's content.
|
||||
*/
|
||||
content?: React.ReactNode;
|
||||
/**
|
||||
* Customize the cancel (dismiss) action button offered by the dialog box.
|
||||
*
|
||||
* Usually dialog boxes should have a cancel action, but this can be skipped
|
||||
* to only show one of the other types of buttons.
|
||||
*/
|
||||
close?: {
|
||||
/** The string to use as the label for the cancel button. */
|
||||
text?: string;
|
||||
/** The color of the button. */
|
||||
variant?: ButtonProps["color"];
|
||||
/**
|
||||
* The function to call when the user cancels.
|
||||
*
|
||||
* If provided, this callback is invoked before closing the dialog.
|
||||
*/
|
||||
action?: () => void;
|
||||
};
|
||||
/**
|
||||
* Customize the primary action button offered by the dialog box.
|
||||
*/
|
||||
proceed?: {
|
||||
/** The string to use as the label for the primary action. */
|
||||
text: string;
|
||||
action: (setLoading?: (value: boolean) => void) => void | Promise<void>;
|
||||
/**
|
||||
* The function to call when the user presses the primary action button.
|
||||
*
|
||||
* It is passed a {@link setLoading} function that can be used to show
|
||||
* or hide loading indicator or the primary action button.
|
||||
*/
|
||||
action:
|
||||
| (() => void | Promise<void>)
|
||||
| ((setLoading: (value: boolean) => void) => void | Promise<void>);
|
||||
variant?: ButtonProps["color"];
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user