[web] UI color related improvements (#4759)

Continuation of https://github.com/ente-io/ente/pull/4751. Now that dark
theme colors have been (mostly) isolated, next up will be introducing
the light one.
This commit is contained in:
Manav Rathi
2025-01-17 14:43:59 +05:30
committed by GitHub
31 changed files with 285 additions and 268 deletions

View File

@@ -1,6 +1,6 @@
import { EnteLogo } from "@/base/components/EnteLogo";
import { decryptMetadataJSON_New } from "@/base/crypto";
import { Box, Button, Stack, Typography } from "@mui/material";
import { Box, Button, Stack, Typography, useTheme } from "@mui/material";
import React, { useEffect, useMemo, useState } from "react";
interface SharedCode {
@@ -19,6 +19,8 @@ const Page: React.FC = () => {
progress: 0,
});
const theme = useTheme();
const getTimeStatus = (
currentTime: number,
startTime: number,
@@ -65,7 +67,11 @@ const Page: React.FC = () => {
useEffect(() => {
if (!sharedCode) return;
let done = false;
const updateCode = () => {
if (done) return;
const currentTime = Date.now();
const codes = sharedCode.codes.split(",");
const status = getTimeStatus(
@@ -85,15 +91,23 @@ const Page: React.FC = () => {
),
);
}
requestAnimationFrame(updateCode);
};
const interval = setInterval(updateCode, 100);
return () => clearInterval(interval);
updateCode();
return () => {
done = true;
};
}, [sharedCode]);
const progressBarColor = useMemo(
() => (100 - codeDisplay.progress > 40 ? "#8E2DE2" : "#FFC107"),
[codeDisplay.progress],
() =>
100 - codeDisplay.progress > 40
? theme.vars.palette.accent.light
: theme.vars.palette.warning.main,
[theme, codeDisplay.progress],
);
return (
@@ -128,17 +142,17 @@ const Page: React.FC = () => {
{timeStatus === 0 && (
<Box
sx={{
backgroundColor: "#1C1C1E",
backgroundColor: "fixed.gray.A",
borderRadius: "10px",
paddingBottom: "20px",
position: "relative",
}}
>
<div
style={{
<Box
sx={{
width: "100%",
height: "4px",
backgroundColor: "#333333",
backgroundColor: "fixed.gray.B",
borderRadius: "2px",
}}
>
@@ -150,7 +164,7 @@ const Page: React.FC = () => {
borderRadius: "2px",
}}
/>
</div>
</Box>
<div
style={{
fontSize: "36px",
@@ -191,7 +205,7 @@ const Page: React.FC = () => {
<Button
color="accent"
sx={{
backgroundColor: "#8E2DE2",
backgroundColor: "accent.light",
borderRadius: "25px",
padding: "15px 30px",
marginBottom: "42px",

View File

@@ -1,5 +1,6 @@
import { TitledMiniDialog } from "@/base/components/MiniDialog";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { useSettingsSnapshot } from "@/new/photos/components/utils/use-snapshot";
@@ -13,7 +14,7 @@ import { loadCast } from "@/new/photos/utils/chromecast-sender";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";
import { Button, Link, Stack, Typography } from "@mui/material";
import { Link, Stack, Typography } from "@mui/material";
import { t } from "i18next";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
@@ -143,18 +144,18 @@ export const AlbumCastDialog: React.FC<AlbumCastDialogProps> = ({
{t("cast_auto_pair_description")}
</Typography>
<Button onClick={() => setView("auto")}>
<FocusVisibleButton onClick={() => setView("auto")}>
{t("cast_auto_pair")}
</Button>
</FocusVisibleButton>
</Stack>
)}
<Stack sx={{ gap: 2 }}>
<Typography sx={{ color: "text.muted" }}>
{t("pair_with_pin_description")}
</Typography>
<Button onClick={() => setView("pin")}>
<FocusVisibleButton onClick={() => setView("pin")}>
{t("pair_with_pin")}
</Button>
</FocusVisibleButton>
</Stack>
</Stack>
)}
@@ -164,17 +165,23 @@ export const AlbumCastDialog: React.FC<AlbumCastDialogProps> = ({
<ActivityIndicator />
</div>
<Typography>{t("choose_device_from_browser")}</Typography>
<Button color="secondary" onClick={() => setView("choose")}>
<FocusVisibleButton
color="secondary"
onClick={() => setView("choose")}
>
{t("go_back")}
</Button>
</FocusVisibleButton>
</Stack>
)}
{view == "auto-cast-error" && (
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
<Typography>{t("cast_auto_pair_failed")}</Typography>
<Button color="secondary" onClick={() => setView("choose")}>
<FocusVisibleButton
color="secondary"
onClick={() => setView("choose")}
>
{t("go_back")}
</Button>
</FocusVisibleButton>
</Stack>
)}
{view == "pin" && (
@@ -199,13 +206,13 @@ export const AlbumCastDialog: React.FC<AlbumCastDialogProps> = ({
buttonText={t("pair_device_to_tv")}
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
/>
<Button
<FocusVisibleButton
variant="text"
fullWidth
onClick={() => setView("choose")}
>
{t("go_back")}
</Button>
</FocusVisibleButton>
</>
)}
</TitledMiniDialog>

View File

@@ -1,9 +1,11 @@
import { isDesktop } from "@/base/app";
import { EnteSwitch } from "@/base/components/EnteSwitch";
import { LinkButton } from "@/base/components/LinkButton";
import {
OverflowMenu,
OverflowMenuOption,
} from "@/base/components/OverflowMenu";
import { EllipsizedTypography } from "@/base/components/Typography";
import type { ButtonishProps } from "@/base/components/mui";
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
import { ensureElectron } from "@/base/electron";
@@ -11,11 +13,7 @@ import log from "@/base/log";
import { EnteFile } from "@/media/file";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { useAppContext } from "@/new/photos/types/context";
import {
SpaceBetweenFlex,
VerticallyCenteredFlex,
} from "@ente/shared/components/Container";
import LinkButton from "@ente/shared/components/LinkButton";
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import { CustomError } from "@ente/shared/error";
import FolderIcon from "@mui/icons-material/Folder";
import {
@@ -25,9 +23,9 @@ import {
DialogContent,
DialogTitle,
Divider,
Stack,
Tooltip,
Typography,
styled,
} from "@mui/material";
import { t } from "i18next";
import { useEffect, useState } from "react";
@@ -183,15 +181,17 @@ export const Export: React.FC<ExportProps> = ({
</SpaceBetweenFlex>
<DialogContent>
<ExportDirectory
exportFolder={exportFolder}
changeExportDirectory={handleChangeExportDirectoryClick}
exportStage={exportStage}
/>
<ContinuousExport
continuousExport={continuousExport}
toggleContinuousExport={toggleContinuousExport}
/>
<Stack>
<ExportDirectory
exportFolder={exportFolder}
changeExportDirectory={handleChangeExportDirectoryClick}
exportStage={exportStage}
/>
<ContinuousExport
continuousExport={continuousExport}
toggleContinuousExport={toggleContinuousExport}
/>
</Stack>
</DialogContent>
<Divider />
<ExportDynamicContent
@@ -210,64 +210,56 @@ export const Export: React.FC<ExportProps> = ({
function ExportDirectory({ exportFolder, changeExportDirectory, exportStage }) {
return (
<SpaceBetweenFlex minHeight={"48px"}>
<Typography sx={{ color: "text.muted", mr: "16px" }}>
<Stack
direction="row"
sx={{
gap: 1,
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography sx={{ color: "text.muted", mr: 1 }}>
{t("destination")}
</Typography>
<>
{!exportFolder ? (
<Button color={"accent"} onClick={changeExportDirectory}>
{t("select_folder")}
</Button>
) : (
<VerticallyCenteredFlex>
<DirectoryPath width={262} path={exportFolder} />
{exportStage === ExportStage.FINISHED ||
exportStage === ExportStage.INIT ? (
<ChangeDirectoryOption
onClick={changeExportDirectory}
/>
) : (
<Box sx={{ width: "16px" }} />
)}
</VerticallyCenteredFlex>
)}
</>
</SpaceBetweenFlex>
{exportFolder ? (
<>
<DirectoryPath path={exportFolder} />
{exportStage === ExportStage.FINISHED ||
exportStage === ExportStage.INIT ? (
<ChangeDirectoryOption
onClick={changeExportDirectory}
/>
) : (
// Prevent layout shift.
<Box sx={{ width: "16px", height: "48px" }} />
)}
</>
) : (
<Button color={"accent"} onClick={changeExportDirectory}>
{t("select_folder")}
</Button>
)}
</Stack>
);
}
const DirectoryPath = ({ width, path }) => {
const handleClick = async () => {
try {
await ensureElectron().openDirectory(path);
} catch (e) {
log.error("openDirectory failed", e);
}
};
return (
<DirectoryPathContainer width={width} onClick={handleClick}>
<Tooltip title={path}>
<span>{path}</span>
</Tooltip>
</DirectoryPathContainer>
);
};
const DirectoryPathContainer = styled(LinkButton)(
({ width }) => `
width: ${width}px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
/* Beginning of string */
direction: rtl;
text-align: left;
`,
const DirectoryPath = ({ path }) => (
<LinkButton onClick={() => void ensureElectron().openDirectory(path)}>
<Tooltip title={path}>
<EllipsizedTypography
// Haven't found a way to get the path to ellipsize without
// providing a maxWidth. Luckily, this is the context of the
// desktop app where this width is should not be too off.
sx={{ maxWidth: "262px" }}
>
{path}
</EllipsizedTypography>
</Tooltip>
</LinkButton>
);
const ChangeDirectoryOption: React.FC<ButtonishProps> = ({ onClick }) => (
<OverflowMenu ariaID="export-option" triggerButtonSxProps={{ ml: 1 }}>
<OverflowMenu ariaID="export-option">
<OverflowMenuOption onClick={onClick} startIcon={<FolderIcon />}>
{t("change_folder")}
</OverflowMenuOption>

View File

@@ -1,3 +1,4 @@
import { LinkButton } from "@/base/components/LinkButton";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { formattedNumber } from "@/base/i18n";
import { EnteFile } from "@/media/file";
@@ -7,7 +8,6 @@ import { DialogActions, DialogContent, Stack, Typography } from "@mui/material";
import { t } from "i18next";
import { useState } from "react";
import ExportPendingList from "./ExportPendingList";
import LinkButton from "./pages/gallery/LinkButton";
interface Props {
pendingExports: EnteFile[];

View File

@@ -4,22 +4,15 @@ import {
VerticallyCentered,
} from "@ente/shared/components/Container";
import {
Box,
DialogActions,
DialogContent,
LinearProgress,
styled,
Typography,
} from "@mui/material";
import { t } from "i18next";
import { Trans } from "react-i18next";
import { ExportStage, type ExportProgress } from "services/export";
export const ComfySpan = styled("span")`
padding: 0 0.5rem;
word-spacing: 1rem;
color: #ddd;
`;
interface Props {
exportStage: ExportStage;
exportProgress: ExportProgress;
@@ -41,7 +34,7 @@ export default function ExportInProgress(props: Props) {
<>
<DialogContent>
<VerticallyCentered>
<Box sx={{ mb: 1.5 }}>
<Typography sx={{ mb: 1.5 }}>
{props.exportStage === ExportStage.STARTING ? (
t("EXPORT_STARTING")
) : props.exportStage === ExportStage.MIGRATION ? (
@@ -56,17 +49,31 @@ export default function ExportInProgress(props: Props) {
ExportStage.TRASHING_DELETED_COLLECTIONS ? (
t("TRASHING_DELETED_COLLECTIONS")
) : (
<Trans
i18nKey={"EXPORT_PROGRESS"}
components={{
a: <ComfySpan />,
}}
values={{
progress: props.exportProgress,
}}
/>
<Typography
component="span"
sx={{ color: "text.muted" }}
>
<Trans
i18nKey={"EXPORT_PROGRESS"}
components={{
a: (
<Typography
component="span"
sx={{
color: "text.base",
pr: "1rem",
wordSpacing: "1rem",
}}
/>
),
}}
values={{
progress: props.exportProgress,
}}
/>
</Typography>
)}
</Box>
</Typography>
<FlexWrapper px={1}>
{showIndeterminateProgress() ? (
<LinearProgress />

View File

@@ -1,3 +1,4 @@
import { LinkButtonUndecorated } from "@/base/components/LinkButton";
import { TitledMiniDialog } from "@/base/components/MiniDialog";
import { type ButtonishProps } from "@/base/components/mui";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
@@ -68,7 +69,6 @@ import {
TextField,
Typography,
} from "@mui/material";
import LinkButton from "components/pages/gallery/LinkButton";
import type { DisplayFile } from "components/PhotoFrame";
import { Formik } from "formik";
import { t } from "i18next";
@@ -254,18 +254,13 @@ export const FileInfo: React.FC<FileInfoProps> = ({
{t("view_on_map")}
</Link>
) : (
<LinkButton
<LinkButtonUndecorated
onClick={
openDisableMapConfirmationDialog
}
sx={{
textDecoration: "none",
color: "text.muted",
fontWeight: "medium",
}}
>
{t("disable_map")}
</LinkButton>
</LinkButtonUndecorated>
)
}
trailingButton={
@@ -296,16 +291,9 @@ export const FileInfo: React.FC<FileInfoProps> = ({
) : !exif.tags ? (
t("no_exif")
) : (
<LinkButton
onClick={showRawExif}
sx={{
textDecoration: "none",
color: "text.muted",
fontWeight: "medium",
}}
>
<LinkButtonUndecorated onClick={showRawExif}>
{t("view_exif")}
</LinkButton>
</LinkButtonUndecorated>
)
}
/>
@@ -913,13 +901,15 @@ const MapBoxContainer = styled("div")`
width: 100%;
`;
const MapBoxEnableContainer = styled(MapBoxContainer)`
const MapBoxEnableContainer = styled(MapBoxContainer)(
({ theme }) => `
position: relative;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(255, 255, 255, 0.09);
`;
background-color: ${theme.vars.palette.fill.fainter};
`,
);
interface RawExifProps {
open: boolean;

View File

@@ -304,12 +304,12 @@ const UsageBar: React.FC<UsageBarProps> = ({ used, total, sx }) => (
/>
);
const UsageBar_ = styled(LinearProgress)(() => ({
const UsageBar_ = styled(LinearProgress)(({ theme }) => ({
".MuiLinearProgress-bar": {
borderRadius: "2px",
},
borderRadius: "2px",
backgroundColor: "rgba(255, 255, 255, 0.2)",
backgroundColor: theme.vars.palette.fixed.storageCardUsageFill,
}));
interface LegendProps {

View File

@@ -2,6 +2,7 @@ import { RecoveryKey } from "@/accounts/components/RecoveryKey";
import { openAccountsManagePasskeysPage } from "@/accounts/services/passkey";
import { isDesktop } from "@/base/app";
import { EnteLogo } from "@/base/components/EnteLogo";
import { LinkButton } from "@/base/components/LinkButton";
import { SpaceBetweenFlex } from "@/base/components/containers";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { SidebarDrawer } from "@/base/components/mui/SidebarDrawer";
@@ -67,7 +68,6 @@ import {
import Typography from "@mui/material/Typography";
import DeleteAccountModal from "components/DeleteAccountModal";
import { WatchFolder } from "components/WatchFolder";
import LinkButton from "components/pages/gallery/LinkButton";
import { t } from "i18next";
import { useRouter } from "next/router";
import { GalleryContext } from "pages/gallery";

View File

@@ -4,10 +4,7 @@ import {
type UploadPhase,
} from "@/new/photos/services/upload/types";
import { useAppContext } from "@/new/photos/types/context";
import {
SpaceBetweenFlex,
VerticallyCenteredFlex,
} from "@ente/shared/components/Container";
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import CloseIcon from "@mui/icons-material/Close";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
@@ -433,8 +430,12 @@ const SectionAccordion = styled((props: AccordionProps) => (
"&:last-child": { borderBottom: `1px solid ${theme.vars.palette.divider}` },
}));
const SectionAccordionSummary = styled(AccordionSummary)(() => ({
backgroundColor: "rgba(255, 255, 255, .05)",
const SectionAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
backgroundColor: theme.vars.palette.fill.fainter,
// AccordionSummary is a button, and for a reasons to do with MUI internal
// that I didn't explore further, the user agent default font family is
// getting applied in this case.
fontFamily: "inherit",
}));
const SectionAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
@@ -534,7 +535,12 @@ interface TitleTextProps {
}
const TitleText: React.FC<TitleTextProps> = ({ title, count }) => (
<VerticallyCenteredFlex gap={"4px"}>
<Stack
direction="row"
// Need to reset the font weight since it gets reset by the
// AccordionSummary (see SectionAccordionSummary).
sx={{ gap: 1, fontWeight: "regular", alignItems: "baseline" }}
>
<Typography>{title}</Typography>
<Typography variant="small" sx={{ color: "text.faint" }}>
{"•"}
@@ -542,7 +548,7 @@ const TitleText: React.FC<TitleTextProps> = ({ title, count }) => (
<Typography variant="small" sx={{ color: "text.faint" }}>
{count ?? 0}
</Typography>
</VerticallyCenteredFlex>
</Stack>
);
const DoneFooter: React.FC = () => {

View File

@@ -1,34 +0,0 @@
import { Link, type ButtonProps, type LinkProps } from "@mui/material";
import React from "react";
export type LinkButtonProps = React.PropsWithChildren<{
onClick: () => void;
variant?: string;
style?: React.CSSProperties;
}>;
const LinkButton: React.FC<
LinkProps<"button", { color?: ButtonProps["color"] }>
> = ({ children, sx, color, ...props }) => {
return (
<Link
component="button"
sx={{
color: "text.base",
textDecoration: "underline rgba(255, 255, 255, 0.4)",
paddingBottom: "2px",
"&:hover": {
color: `${color}.main`,
textDecoration: `underline `,
textDecorationColor: `${color}.main`,
},
...sx,
}}
{...props}
>
{children}
</Link>
);
};
export default LinkButton;

View File

@@ -1,6 +1,6 @@
import { LoginContents } from "@/accounts/components/LoginContents";
import { SignUpContents } from "@/accounts/components/SignUpContents";
import { CenteredFill } from "@/base/components/containers";
import { CenteredFill, CenteredFlex } from "@/base/components/containers";
import { EnteLogo } from "@/base/components/EnteLogo";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
@@ -286,22 +286,21 @@ const MobileBoxFooter: React.FC<MobileBoxFooterProps> = ({ host }) => {
);
};
const DesktopBox = styled("div")`
const DesktopBox = styled(CenteredFlex)(
({ theme }) => `
flex-shrink: 0;
flex-grow: 2;
flex-basis: auto;
height: 100%;
padding-inline: 20px;
display: flex;
align-items: center;
justify-content: center;
background-color: #242424;
background-color: ${theme.vars.palette.fill.faint};
@media (width <= 1024px) {
display: none;
}
`;
`,
);
const Slideshow: React.FC = () => {
const [selectedIndex, setSelectedIndex] = useState(0);

View File

@@ -18,7 +18,7 @@ body {
height: 48px;
background: none !important;
background-image: none !important;
color: #fff;
color: var(--mui-palette-stroke-base);
}
.pswp__item video {
@@ -65,8 +65,8 @@ body {
.pswp__button--arrow--left,
.pswp__button--arrow--right {
color: #fff;
background-color: #333333 !important;
color: var(--mui-palette-stroke-base);
background-color: var(--mui-palette-fill-faint) !important;
border-radius: 50%;
width: 56px;
height: 56px;

View File

@@ -4,12 +4,12 @@ import {
passkeySessionExpiredErrorMessage,
saveCredentialsAndNavigateTo,
} from "@/accounts/services/passkey";
import { LinkButton } from "@/base/components/LinkButton";
import type { MiniDialogAttributes } from "@/base/components/MiniDialog";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { genericErrorDialogAttributes } from "@/base/components/utils/dialog";
import log from "@/base/log";
import { customAPIHost } from "@/base/origins";
import LinkButton from "@ente/shared/components/LinkButton";
import { CircularProgress, Stack, Typography, styled } from "@mui/material";
import { t } from "i18next";
import { useRouter } from "next/router";
@@ -54,7 +54,7 @@ export const AccountsPageFooterWithHost: React.FC<React.PropsWithChildren> = ({
useEffect(() => void customAPIHost().then(setHost), []);
return (
<Stack sx={{ gap: 2 }}>
<Stack sx={{ gap: 3 }}>
<AccountsPageFooter>{children}</AccountsPageFooter>
{host && (
<Typography

View File

@@ -5,9 +5,9 @@ import {
import { PAGES } from "@/accounts/constants/pages";
import { getSRPAttributes } from "@/accounts/services/srp-remote";
import { sendOTT } from "@/accounts/services/user";
import { LinkButton } from "@/base/components/LinkButton";
import { isMuseumHTTPError } from "@/base/http";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";
@@ -80,7 +80,7 @@ export const LoginContents: React.FC<LoginContentsProps> = ({
}
/>
<AccountsPageFooter>
<Stack sx={{ gap: 4, textAlign: "center" }}>
<Stack sx={{ gap: 3, textAlign: "center" }}>
<LinkButton onClick={onSignUp}>
{t("no_account")}
</LinkButton>

View File

@@ -85,7 +85,9 @@ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({
<Stack
sx={{
border: "1px dashed",
borderColor: "gray.A400",
borderColor: "stroke.muted",
// TODO(LM): Brighter?
// borderColor: "gray.A400",
borderRadius: 1,
}}
>

View File

@@ -2,13 +2,13 @@ import { PAGES } from "@/accounts/constants/pages";
import { generateKeyAndSRPAttributes } from "@/accounts/services/srp";
import { sendOTT } from "@/accounts/services/user";
import { isWeakPassword } from "@/accounts/utils/password";
import { LinkButton } from "@/base/components/LinkButton";
import { LoadingButton } from "@/base/components/mui/LoadingButton";
import { isMuseumHTTPError } from "@/base/http";
import log from "@/base/log";
import { LS_KEYS, setLSUser } from "@ente/shared//storage/localStorage";
import { VerticallyCentered } from "@ente/shared/components/Container";
import ShowHidePassword from "@ente/shared/components/Form/ShowHidePassword";
import LinkButton from "@ente/shared/components/LinkButton";
import {
generateAndSaveIntermediateKeyAttributes,
saveKeyInSessionStore,
@@ -339,16 +339,15 @@ export const SignUpContents: React.FC<SignUpContentsProps> = ({
<AccountsPageTitle>{t("sign_up")}</AccountsPageTitle>
{form}
<AccountsPageFooter>
<Stack sx={{ gap: 3, textAlign: "center" }}>
<Stack sx={{ gap: 5, textAlign: "center" }}>
<LinkButton onClick={onLogin}>
{t("existing_account")}
</LinkButton>
<Typography
variant="mini"
sx={{ color: "text.faint", minHeight: "16px" }}
>
{host ?? "" /* prevent layout shift with a minHeight */}
</Typography>
{host && (
<Typography variant="mini" sx={{ color: "text.faint" }}>
{host}
</Typography>
)}
</Stack>
</AccountsPageFooter>
</>

View File

@@ -1,7 +1,7 @@
import { CodeBlock } from "@/accounts/components/CodeBlock";
import { LinkButton } from "@/base/components/LinkButton";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { VerticallyCentered } from "@ente/shared/components/Container";
import LinkButton from "@ente/shared/components/LinkButton";
import { Stack, styled, Typography } from "@mui/material";
import { t } from "i18next";
import { useState } from "react";
@@ -99,7 +99,7 @@ const LoadingQRCode = styled(VerticallyCentered)(
({ theme }) => `
width: 200px;
aspect-ratio:1;
border: 1px solid ${theme.palette.grey.A200};
border: 1px solid ${theme.palette.stroke.muted};
margin: ${theme.spacing(2)};
`,
);

View File

@@ -5,11 +5,11 @@ import {
} from "@/accounts/components/layouts/centered-paper";
import { appHomeRoute } from "@/accounts/services/redirect";
import { changeEmail, sendOTT } from "@/accounts/services/user";
import { LinkButton } from "@/base/components/LinkButton";
import { LoadingButton } from "@/base/components/mui/LoadingButton";
import { isHTTPErrorWithStatus } from "@/base/http";
import log from "@/base/log";
import { VerticallyCentered } from "@ente/shared/components/Container";
import LinkButton from "@ente/shared/components/LinkButton";
import { LS_KEYS, getData, setLSUser } from "@ente/shared/storage/localStorage";
import { Alert, Box, TextField } from "@mui/material";
import { Formik, type FormikHelpers } from "formik";

View File

@@ -20,8 +20,8 @@ import {
updateSRPAndKeys,
} from "@/accounts/services/srp-remote";
import type { UpdatedKey } from "@/accounts/services/user";
import { LinkButton } from "@/base/components/LinkButton";
import { sharedCryptoWorker } from "@/base/crypto";
import LinkButton from "@ente/shared/components/LinkButton";
import {
generateAndSaveIntermediateKeyAttributes,
generateLoginSubKey,

View File

@@ -26,12 +26,12 @@ import {
import type { SRPAttributes } from "@/accounts/services/srp-remote";
import { getSRPAttributes } from "@/accounts/services/srp-remote";
import type { PageProps } from "@/accounts/types/page";
import { LinkButton } from "@/base/components/LinkButton";
import { LoadingIndicator } from "@/base/components/loaders";
import { sharedCryptoWorker } from "@/base/crypto";
import type { B64EncryptionResult } from "@/base/crypto/libsodium";
import { clearLocalStorage } from "@/base/local-storage";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import VerifyMasterPasswordForm, {
type VerifyMasterPasswordFormProps,
} from "@ente/shared/components/VerifyMasterPasswordForm";

View File

@@ -15,9 +15,9 @@ import {
} from "@/accounts/services/srp";
import { putAttributes } from "@/accounts/services/user";
import type { PageProps } from "@/accounts/types/page";
import { LinkButton } from "@/base/components/LinkButton";
import { LoadingIndicator } from "@/base/components/loaders";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import {
generateAndSaveIntermediateKeyAttributes,
saveKeyInSessionStore,

View File

@@ -7,9 +7,9 @@ import { PAGES } from "@/accounts/constants/pages";
import { appHomeRoute, stashRedirect } from "@/accounts/services/redirect";
import { sendOTT } from "@/accounts/services/user";
import type { PageProps } from "@/accounts/types/page";
import { LinkButton } from "@/base/components/LinkButton";
import { sharedCryptoWorker } from "@/base/crypto";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";

View File

@@ -10,11 +10,11 @@ import {
type TwoFactorType,
} from "@/accounts/services/user";
import type { AccountsContextT } from "@/accounts/types/context";
import { LinkButton } from "@/base/components/LinkButton";
import type { MiniDialogAttributes } from "@/base/components/MiniDialog";
import { sharedCryptoWorker } from "@/base/crypto";
import type { B64EncryptionResult } from "@/base/crypto/libsodium";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";

View File

@@ -6,8 +6,8 @@ import { appHomeRoute } from "@/accounts/services/redirect";
import type { TwoFactorSecret } from "@/accounts/services/user";
import { enableTwoFactor, setupTwoFactor } from "@/accounts/services/user";
import { CenteredFill } from "@/base/components/containers";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers";
import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage";
import { Paper, Stack, styled, Typography } from "@mui/material";
@@ -69,9 +69,14 @@ const Page: React.FC = () => {
onSubmit={onSubmit}
buttonText={t("enable")}
/>
<LinkButton sx={{ mt: 1 }} onClick={router.back}>
{t("go_back")}
</LinkButton>
<Stack sx={{ alignItems: "center" }}>
<FocusVisibleButton
variant="text"
onClick={router.back}
>
{t("go_back")}
</FocusVisibleButton>
</Stack>
</Stack>
</ContentsPaper>
</CenteredFill>

View File

@@ -1,6 +1,6 @@
import { PAGES } from "@/accounts/constants/pages";
import { verifyTwoFactor } from "@/accounts/services/user";
import LinkButton from "@ente/shared/components/LinkButton";
import { LinkButton } from "@/base/components/LinkButton";
import { ApiError } from "@ente/shared/error";
import {
LS_KEYS,

View File

@@ -20,9 +20,9 @@ import type {
import { getSRPAttributes } from "@/accounts/services/srp-remote";
import { putAttributes, sendOTT, verifyEmail } from "@/accounts/services/user";
import type { PageProps } from "@/accounts/types/page";
import { LinkButton } from "@/base/components/LinkButton";
import { LoadingIndicator } from "@/base/components/loaders";
import log from "@/base/log";
import LinkButton from "@ente/shared/components/LinkButton";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";

View File

@@ -0,0 +1,56 @@
import { Link, type ButtonProps } from "@mui/material";
import React from "react";
/**
* A button that looks like a link.
*
* The use of this component is not encouraged. It is only useful in uncommon
* cases where we do not have sufficient space to include a proper button.
*/
export const LinkButton: React.FC<
React.PropsWithChildren<Pick<ButtonProps, "onClick">>
> = ({ onClick, children }) => (
<Link
component="button"
sx={(theme) => ({
color: "text.base",
textDecoration: "underline",
// The shortcut "text.faint" does not work with textDecorationColor
// (as of MUI v6).
textDecorationColor: theme.vars.palette.text.faint,
"&:hover": {
color: "accent.main",
textDecoration: "underline",
textDecorationColor: "accent.main",
},
})}
{...{ onClick }}
>
{children}
</Link>
);
/**
* A variant of {@link LinkButton} that does not show an underline, and instead
* uses a bolder font weight to indicate clickability.
*
* Similar caveats as {@link LinkButton} apply.
*/
export const LinkButtonUndecorated: React.FC<
React.PropsWithChildren<Pick<ButtonProps, "onClick">>
> = ({ onClick, children }) => (
<Link
component="button"
sx={{
textDecoration: "none",
color: "text.muted",
fontWeight: "medium",
"&:hover": {
color: "accent.main",
},
}}
{...{ onClick }}
>
{children}
</Link>
);

View File

@@ -85,16 +85,18 @@ declare module "@mui/material/styles" {
* These change with the color scheme.
*
* They come in three strengths which are meant to play nicely with the
* corresponding strengths of "text.*" and "stroke.*".
* corresponding strengths of "text.*" and "stroke.*", plus extra ones.
*
* The strength comes with a hover variant, useful to indicate the hover
* state of buttons and menu items that use the corresponding fill.
* Some strengths also have a hover variant, useful to indicate hover on
* buttons and menu items that use the corresponding fill.
*/
fill: {
base: string;
baseHover: string;
muted: string;
faint: string;
faintHover: string;
fainter: string;
};
/**
* Transparent background fills that serve as the backdrop of modals,
@@ -148,6 +150,10 @@ declare module "@mui/material/styles" {
* "archived" indicator shown on top of albums.
*/
overlayIndicatorMuted: string;
/**
* Color of the total space in the usage bar on the storage card.
*/
storageCardUsageFill: string;
};
/**
* MUI as of v6 does not allow customizing shadows easily. This is due

View File

@@ -133,15 +133,16 @@ const _colors = {
light: "#01DE4D",
},
accentAuth: {
dark: "rgb(164, 0, 182)",
main: "rgb(150, 13, 214)",
light: "rgb(152, 77, 244)",
dark: "#8e0fcb",
main: "#9610d6",
light: "#8e2de2",
lighter: "#984df4" /* TODO(LM) */,
},
fixed: {
white: "#fff",
black: "#000",
success: "#1DB954",
warning: "#FFC247",
golden: "#FFC107",
danger: {
dark: "#F53434",
main: "#EA3F3F",
@@ -155,6 +156,7 @@ const _colors = {
switchOn: "#2ECA45",
croppedAreaOverlay: "rgba(0 0 0 / 0.5)",
overlayIndicatorMuted: "rgba(255 255 255 / 0.48)",
storageCardUsageFill: "rgba(255 255 255 / 0.2)",
},
light: {
background: {
@@ -197,31 +199,32 @@ const _colors = {
paper2: "#252525",
},
backdrop: {
base: "rgba(0, 0, 0, 0.90)",
muted: "rgba(0, 0, 0, 0.65)",
faint: "rgba(0, 0, 0,0.20)",
base: "rgba(0 0 0 / 0.90)",
muted: "rgba(0 0 0 / 0.65)",
faint: "rgba(0 0 0 / 0.20)",
},
text: {
base: "#fff",
muted: "rgba(255, 255, 255, 0.70)",
faint: "rgba(255, 255, 255, 0.50)",
muted: "rgba(255 255 255 / 0.70)",
faint: "rgba(255 255 255 / 0.50)",
},
fill: {
base: "#fff",
muted: "rgba(255, 255, 255, 0.16)",
faint: "rgba(255, 255, 255, 0.12)",
baseHover: "rgba(255, 255, 255, 0.90)",
faintHover: "rgba(255, 255, 255, 0.06)",
baseHover: "rgba(255 255 255 / 0.90)",
muted: "rgba(255 255 255 / 0.16)",
faint: "rgba(255 255 255 / 0.12)",
faintHover: "rgba(255 255 255 / 0.06)",
fainter: "rgba(255 255 255 / 0.05)",
},
stroke: {
base: "#fff",
muted: "rgba(255,255,255,0.24)",
faint: "rgba(255,255,255,0.16)",
muted: "rgba(255 255 255 / 0.24)",
faint: "rgba(255 255 255 / 0.16)",
},
boxShadow: {
float: "0px 2px 12px rgba(0, 0, 0, 0.75)",
menu: "0px 0px 6px rgba(0, 0, 0, 0.50), 0px 3px 6px rgba(0, 0, 0, 0.25)",
button: "0px 4px 4px rgba(0, 0, 0, 0.75)",
float: "0px 2px 12px rgba(0 0 0 / 0.75)",
menu: "0px 0px 6px rgba(0 0 0 / 0.50), 0px 3px 6px rgba(0 0 0 / 0.25)",
button: "0px 4px 4px rgba(0 0 0 / 0.75)",
},
},
};
@@ -251,7 +254,7 @@ const getColorSchemes = (colors: ReturnType<typeof getColors>) => ({
contrastText: colors.dark.text.base,
},
success: { main: colors.fixed.success },
warning: { main: colors.fixed.warning },
warning: { main: colors.fixed.golden },
accent: {
main: colors.accent.main,
dark: colors.accent.dark,
@@ -280,9 +283,11 @@ const getColorSchemes = (colors: ReturnType<typeof getColors>) => ({
},
fill: {
base: colors.dark.fill.base,
baseHover: colors.dark.fill.baseHover,
muted: colors.dark.fill.muted,
faint: colors.dark.fill.faint,
faintHover: colors.dark.fill.faintHover,
fainter: colors.dark.fill.fainter,
},
stroke: {
base: colors.dark.stroke.base,

View File

@@ -627,9 +627,6 @@ const FreePlanRow_ = styled(SpaceBetweenFlex)(({ theme }) => ({
gap: theme.spacing(1.5),
padding: theme.spacing(1.5, 1),
cursor: "pointer",
"&:hover .endIcon": {
backgroundColor: "rgba(255,255,255,0.08)",
},
}));
interface AddOnBonusRowsProps {

View File

@@ -1,34 +0,0 @@
import { Link, type ButtonProps, type LinkProps } from "@mui/material";
import React from "react";
export type LinkButtonProps = React.PropsWithChildren<{
onClick: () => void;
variant?: string;
style?: React.CSSProperties;
}>;
const LinkButton: React.FC<
LinkProps<"button", { color?: ButtonProps["color"] }>
> = ({ children, sx, color, ...props }) => {
return (
<Link
component="button"
sx={{
color: "text.base",
textDecoration: "underline rgba(255, 255, 255, 0.4)",
paddingBottom: 0.5,
"&:hover": {
color: `${color}.main`,
textDecoration: `underline `,
textDecorationColor: `${color}.main`,
},
...sx,
}}
{...props}
>
{children}
</Link>
);
};
export default LinkButton;