Skip to content

Commit 4fc2004

Browse files
committed
Use toast instead of alert
1 parent a476e6c commit 4fc2004

7 files changed

Lines changed: 182 additions & 118 deletions

File tree

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"lodash": "^4.17.23",
2020
"react": "^19.2.0",
2121
"react-dom": "^19.2.0",
22+
"sonner": "^2.0.7",
2223
"stringify-object": "^6.0.0"
2324
},
2425
"devDependencies": {

src/App.tsx

Lines changed: 4 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,21 @@
1-
import { GetApp, Refresh, Share } from "@mui/icons-material";
2-
import {
3-
Box,
4-
Button,
5-
createTheme,
6-
IconButton,
7-
Link,
8-
Stack,
9-
ThemeProvider,
10-
Tooltip,
11-
Typography,
12-
type IconButtonProps,
13-
type ThemeOptions,
14-
} from "@mui/material";
1+
import { Box, createTheme, Stack, ThemeProvider, type ThemeOptions } from "@mui/material";
152
import { merge } from "lodash";
163
import { useEffect, useState } from "react";
4+
import { BottomControls } from "./components/BottomControls/BottomControls";
175
import { InfoPanel } from "./components/InfoPanel/InfoPanel";
186
import { MockApp } from "./components/MockApp/MockApp";
197
import { ToolsPanel } from "./components/ToolsPanel/ToolsPanel";
20-
import { generateTheme } from "./generateTheme";
218
import { InnerThemeContext } from "./hooks/useInnerTheme";
22-
import { deleteKeys, saveObjectToClipboard } from "./utils";
9+
import { deleteKeys } from "./utils";
2310

2411
const LOCAL_STORAGE_KEY = "themeOptions";
2512

26-
const SHARED_ICONBUTTON_PROPS: IconButtonProps = {
27-
size: "large",
28-
sx: {
29-
bgcolor: "primary.main",
30-
"&:hover": { bgcolor: "primary.dark" },
31-
"&:disabled": { bgcolor: "primary.main", opacity: 0.5, color: "primary.contrastText" },
32-
color: "primary.contrastText",
33-
},
34-
};
35-
36-
const shareSite = async () => {
37-
try {
38-
await navigator.share({
39-
title: "MUI Theme Builder",
40-
text: "Modern MUI theme builder",
41-
url: "https://google.com",
42-
});
43-
} catch (err: any) {
44-
if (err.name === "AbortError") return;
45-
try {
46-
await navigator.clipboard.writeText("https://google.com");
47-
alert("Site URL copied to clipboard");
48-
} catch (error) {
49-
alert("Failed to share URL");
50-
console.error(error);
51-
throw error;
52-
}
53-
}
54-
};
55-
5613
function App() {
5714
const [themeOptions, setThemeOptions] = useState<ThemeOptions>(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? "{}"));
5815
const innerTheme = createTheme(themeOptions);
5916
const mergeThemeOptions = (partialThemeOptions: ThemeOptions) =>
6017
setThemeOptions(merge(structuredClone(themeOptions), partialThemeOptions));
6118
const deleteThemeOptionKey = (keyPath: Array<string>) => setThemeOptions(deleteKeys(themeOptions, keyPath));
62-
const hasEditedTheme = Object.keys(themeOptions).length !== 0;
6319

6420
useEffect(() => {
6521
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(themeOptions));
@@ -107,72 +63,7 @@ function App() {
10763
<MockApp />
10864
</ThemeProvider>
10965
</Box>
110-
<Stack gap={2} sx={{ p: { xs: 2, sm: 3, md: 4 }, pt: { xs: 0, sm: 0, md: 0 } }} alignItems="center">
111-
<Stack gap={1.5} direction="row">
112-
<Button
113-
size="large"
114-
variant="contained"
115-
onClick={() => setThemeOptions(generateTheme())}
116-
sx={{
117-
border: "2px solid transparent",
118-
borderImage: "conic-gradient(from 0deg, red, yellow, lime, aqua, blue, magenta, red) 1",
119-
borderImageSlice: 1,
120-
transition: "all 0.3s ease",
121-
fontWeight: "bold",
122-
}}
123-
>
124-
Randomize!
125-
</Button>
126-
<Tooltip title="Export theme">
127-
<IconButton disabled={!hasEditedTheme} onClick={() => saveObjectToClipboard(themeOptions)} {...SHARED_ICONBUTTON_PROPS}>
128-
<GetApp />
129-
</IconButton>
130-
</Tooltip>
131-
<Tooltip title="Reset">
132-
<IconButton
133-
disabled={!hasEditedTheme}
134-
onClick={() => {
135-
if (!confirm("Reset all settings to default?")) return;
136-
setThemeOptions({});
137-
}}
138-
{...SHARED_ICONBUTTON_PROPS}
139-
>
140-
<Refresh />
141-
</IconButton>
142-
</Tooltip>
143-
<Tooltip title="Share">
144-
<IconButton onClick={shareSite} {...SHARED_ICONBUTTON_PROPS}>
145-
<Share />
146-
</IconButton>
147-
</Tooltip>
148-
{/* <Tooltip title={hasEditedTheme ? "Share..." : "Share"}>
149-
<IconButton
150-
{...(hasEditedTheme ? { popoverTarget: "share-popover" } : { onClick: shareSite })}
151-
{...SHARED_ICONBUTTON_PROPS}
152-
>
153-
<Share />
154-
</IconButton>
155-
</Tooltip>
156-
{hasEditedTheme && (
157-
<Paper
158-
popover="auto"
159-
id="share-popover"
160-
sx={{ bottom: "anchor(top)", insetInlineEnd: "anchor(end)", bgcolor: "primary.dark", p: 0 }}
161-
>
162-
<MenuList>
163-
<MenuItem onClick={shareSite}>Share site</MenuItem>
164-
<MenuItem onClick={() => {}}>Share this theme</MenuItem>
165-
</MenuList>
166-
</Paper>
167-
)} */}
168-
</Stack>
169-
<Typography color="#ddd" sx={{ display: { sm: "none" } }}>
170-
View on a larger screen to edit&nbsp;&nbsp;|&nbsp;&nbsp;
171-
<Link href="https://github.com/PeterTYLiu/mui-theme-builder" target="_blank">
172-
Github
173-
</Link>
174-
</Typography>
175-
</Stack>
66+
<BottomControls />
17667
</Stack>
17768
<ToolsPanel />
17869
</Box>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { GetApp, Refresh, Share } from "@mui/icons-material";
2+
import {
3+
Button,
4+
Dialog,
5+
DialogActions,
6+
DialogContent,
7+
DialogContentText,
8+
DialogTitle,
9+
IconButton,
10+
Link,
11+
Stack,
12+
Tooltip,
13+
Typography,
14+
type IconButtonProps,
15+
} from "@mui/material";
16+
import { useState } from "react";
17+
import { toast } from "sonner";
18+
import { generateTheme } from "../../generateTheme";
19+
import { useInnerTheme } from "../../hooks/useInnerTheme";
20+
import { saveObjectToClipboard } from "../../utils";
21+
22+
const SHARED_ICONBUTTON_PROPS: IconButtonProps = {
23+
size: "large",
24+
sx: {
25+
bgcolor: "primary.main",
26+
"&:hover": { bgcolor: "primary.dark" },
27+
"&:disabled": { bgcolor: "primary.main", opacity: 0.5, color: "primary.contrastText" },
28+
color: "primary.contrastText",
29+
},
30+
};
31+
32+
const shareSite = async () => {
33+
try {
34+
await navigator.share({
35+
title: "MUI Theme Builder",
36+
text: "Modern MUI theme builder",
37+
url: "https://google.com",
38+
});
39+
} catch (err: any) {
40+
if (err.name === "AbortError") return;
41+
try {
42+
await navigator.clipboard.writeText("https://google.com");
43+
toast.success("Site URL copied to clipboard");
44+
} catch (error) {
45+
toast.error("Failed to share URL");
46+
console.error(error);
47+
throw error;
48+
}
49+
}
50+
};
51+
52+
export const BottomControls = () => {
53+
const { setThemeOptions, themeOptions } = useInnerTheme();
54+
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
55+
const hasEditedTheme = Object.keys(themeOptions).length !== 0;
56+
57+
return (
58+
<Stack gap={2} sx={{ p: { xs: 2, sm: 3, md: 4 }, pt: { xs: 0, sm: 0, md: 0 } }} alignItems="center">
59+
<Stack gap={1.5} direction="row">
60+
<Button
61+
size="large"
62+
variant="contained"
63+
onClick={() => setThemeOptions(generateTheme())}
64+
sx={{
65+
border: "2px solid transparent",
66+
borderImage: "conic-gradient(from 0deg, red, yellow, lime, aqua, blue, magenta, red) 1",
67+
borderImageSlice: 1,
68+
transition: "all 0.3s ease",
69+
fontWeight: "bold",
70+
}}
71+
>
72+
Randomize!
73+
</Button>
74+
<Tooltip title="Export theme">
75+
<IconButton disabled={!hasEditedTheme} onClick={() => saveObjectToClipboard(themeOptions)} {...SHARED_ICONBUTTON_PROPS}>
76+
<GetApp />
77+
</IconButton>
78+
</Tooltip>
79+
<Tooltip title="Reset">
80+
<IconButton disabled={!hasEditedTheme} onClick={() => setIsResetDialogOpen(true)} {...SHARED_ICONBUTTON_PROPS}>
81+
<Refresh />
82+
</IconButton>
83+
</Tooltip>
84+
<Dialog
85+
open={isResetDialogOpen}
86+
onClose={() => setIsResetDialogOpen(false)}
87+
aria-labelledby="reset-dialog-title"
88+
aria-describedby="reset-dialog-description"
89+
>
90+
<DialogTitle id="reset-dialog-title">Reset to default theme?</DialogTitle>
91+
<DialogContent>
92+
<DialogContentText id="reset-dialog-description">You will permanently lose these settings</DialogContentText>
93+
</DialogContent>
94+
<DialogActions>
95+
<Button onClick={() => setIsResetDialogOpen(false)}>Cancel</Button>
96+
<Button
97+
onClick={() => {
98+
setIsResetDialogOpen(false);
99+
setThemeOptions({});
100+
}}
101+
autoFocus
102+
>
103+
Reset
104+
</Button>
105+
</DialogActions>
106+
</Dialog>
107+
<Tooltip title="Share">
108+
<IconButton onClick={shareSite} {...SHARED_ICONBUTTON_PROPS}>
109+
<Share />
110+
</IconButton>
111+
</Tooltip>
112+
{/* <Tooltip title={hasEditedTheme ? "Share..." : "Share"}>
113+
<IconButton
114+
{...(hasEditedTheme ? { popoverTarget: "share-popover" } : { onClick: shareSite })}
115+
{...SHARED_ICONBUTTON_PROPS}
116+
>
117+
<Share />
118+
</IconButton>
119+
</Tooltip>
120+
{hasEditedTheme && (
121+
<Paper
122+
popover="auto"
123+
id="share-popover"
124+
sx={{ bottom: "anchor(top)", insetInlineEnd: "anchor(end)", bgcolor: "primary.dark", p: 0 }}
125+
>
126+
<MenuList>
127+
<MenuItem onClick={shareSite}>Share site</MenuItem>
128+
<MenuItem onClick={() => {}}>Share this theme</MenuItem>
129+
</MenuList>
130+
</Paper>
131+
)} */}
132+
</Stack>
133+
<Typography color="#ddd" sx={{ display: { sm: "none" } }}>
134+
View on a larger screen to edit&nbsp;&nbsp;|&nbsp;&nbsp;
135+
<Link href="https://github.com/PeterTYLiu/mui-theme-builder" target="_blank">
136+
Github
137+
</Link>
138+
</Typography>
139+
</Stack>
140+
);
141+
};

src/components/TextEditor.tsx/TextEditor.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
type TypographyVariants,
1414
} from "@mui/material";
1515
import { useRef } from "react";
16+
import { toast } from "sonner";
1617
import { DEFAULT_THEME } from "../../constants";
1718
import { useInnerTheme } from "../../hooks/useInnerTheme";
1819
import { deleteKeys } from "../../utils";
@@ -94,9 +95,9 @@ export const TextEditor = () => {
9495
try {
9596
const text = codeblockRef.current.innerText;
9697
await navigator.clipboard.writeText(text);
97-
alert("Snippet copied to clipboard");
98+
toast.success("Snippet copied to clipboard");
9899
} catch (error) {
99-
alert("Failed to copy snippet to clipboard");
100+
toast.error("Failed to copy snippet to clipboard");
100101
console.error(error);
101102
throw error;
102103
}

src/main.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { Check } from "@mui/icons-material";
12
import { ThemeProvider, createTheme, type ThemeOptions } from "@mui/material";
23
import { StrictMode } from "react";
34
import { createRoot } from "react-dom/client";
5+
import { Toaster } from "sonner";
46
import App from "./App.tsx";
57
import "./index.css";
68

@@ -33,10 +35,26 @@ const appThemeOptions: ThemeOptions = {
3335
},
3436
};
3537

38+
const appTheme = createTheme(appThemeOptions);
39+
3640
createRoot(document.getElementById("root")!).render(
3741
<StrictMode>
38-
<ThemeProvider theme={createTheme(appThemeOptions)}>
42+
<ThemeProvider theme={appTheme}>
3943
<App />
44+
<Toaster
45+
position="top-center"
46+
duration={2100}
47+
icons={{ success: <Check color="success" /> }}
48+
toastOptions={{
49+
style: {
50+
backgroundColor: "var(--mui-palette-background-paper)",
51+
color: "var(--mui-palette-text-primary)",
52+
backgroundImage: "var(--mui-overlays-24)",
53+
border: 0,
54+
borderRadius: 0,
55+
},
56+
}}
57+
/>
4058
</ThemeProvider>
4159
</StrictMode>,
4260
);

src/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { rgbToHex, type ThemeOptions } from "@mui/material";
2+
import { toast } from "sonner";
23

34
export const toStandardHex = (input: string, fallback: string = "#000000"): string => {
45
if (input.startsWith("rgb")) return toStandardHex(rgbToHex(input));
@@ -86,9 +87,9 @@ export async function saveObjectToClipboard(data: any): Promise<void> {
8687
try {
8788
const jsonString = JSON.stringify(data, null, 2);
8889
await navigator.clipboard.writeText(jsonString);
89-
alert("Theme options copied to clipboard");
90+
toast.success("Theme options copied to clipboard");
9091
} catch (error) {
91-
alert("Failed to copy theme to clipboard");
92+
toast.error("Failed to copy theme to clipboard");
9293
console.error(error);
9394
throw error;
9495
}

0 commit comments

Comments
 (0)