alert dialog component
This commit is contained in:
72
components/AlertDialog/index.tsx
Normal file
72
components/AlertDialog/index.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { FC, ReactNode } from "react";
|
||||||
|
import { proxy, useSnapshot } from "valtio";
|
||||||
|
import Button from "../Button";
|
||||||
|
import Flex from "../Flex";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "./primitive";
|
||||||
|
|
||||||
|
export interface AlertState {
|
||||||
|
isOpen: boolean;
|
||||||
|
title?: string;
|
||||||
|
body?: ReactNode;
|
||||||
|
cancelText?: string;
|
||||||
|
confirmNode?: ReactNode;
|
||||||
|
onConfirm?: () => any;
|
||||||
|
onCancel?: () => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alertState = proxy<AlertState>({
|
||||||
|
isOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Alert: FC = () => {
|
||||||
|
const {
|
||||||
|
title = "Are you sure?",
|
||||||
|
isOpen,
|
||||||
|
body,
|
||||||
|
cancelText,
|
||||||
|
confirmNode = "Ok",
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
} = useSnapshot(alertState);
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={value => (alertState.isOpen = value)}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>{body}</AlertDialogDescription>
|
||||||
|
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
|
||||||
|
{cancelText && (
|
||||||
|
<AlertDialogCancel asChild>
|
||||||
|
<Button css={{ minWidth: "$16" }} outline onClick={onCancel}>
|
||||||
|
{cancelText}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
)}
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button
|
||||||
|
css={{ minWidth: "$16" }}
|
||||||
|
variant="primary"
|
||||||
|
onClick={async () => {
|
||||||
|
await onConfirm?.();
|
||||||
|
alertState.isOpen = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{confirmNode}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</Flex>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Alert;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { blackA } from "@radix-ui/colors";
|
import { blackA } from "@radix-ui/colors";
|
||||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
||||||
import { styled, keyframes } from "../stitches.config";
|
import { styled, keyframes } from "../../stitches.config";
|
||||||
|
|
||||||
const overlayShow = keyframes({
|
const overlayShow = keyframes({
|
||||||
"0%": { opacity: 0 },
|
"0%": { opacity: 0 },
|
||||||
@@ -50,15 +50,8 @@ import Stack from "./Stack";
|
|||||||
import { Input, Label } from "./Input";
|
import { Input, Label } from "./Input";
|
||||||
import Text from "./Text";
|
import Text from "./Text";
|
||||||
import Tooltip from "./Tooltip";
|
import Tooltip from "./Tooltip";
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogAction,
|
|
||||||
} from "./AlertDialog";
|
|
||||||
import { styled } from "../stitches.config";
|
import { styled } from "../stitches.config";
|
||||||
|
import { showAlert } from "../state/actions/showAlert";
|
||||||
|
|
||||||
const ErrorText = styled(Text, {
|
const ErrorText = styled(Text, {
|
||||||
color: "$error",
|
color: "$error",
|
||||||
@@ -68,7 +61,6 @@ const ErrorText = styled(Text, {
|
|||||||
|
|
||||||
const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
const [createNewAlertOpen, setCreateNewAlertOpen] = useState(false);
|
|
||||||
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false);
|
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false);
|
||||||
const [isNewfileDialogOpen, setIsNewfileDialogOpen] = useState(false);
|
const [isNewfileDialogOpen, setIsNewfileDialogOpen] = useState(false);
|
||||||
const [newfileError, setNewfileError] = useState<string | null>(null);
|
const [newfileError, setNewfileError] = useState<string | null>(null);
|
||||||
@@ -87,13 +79,32 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
setNewfileError(null);
|
setNewfileError(null);
|
||||||
}, [filename, setNewfileError]);
|
}, [filename, setNewfileError]);
|
||||||
|
|
||||||
|
const showNewGistAlert = () => {
|
||||||
|
showAlert("Are you sure?", {
|
||||||
|
body: (
|
||||||
|
<>
|
||||||
|
This action will create new <strong>public</strong> Github Gist from
|
||||||
|
your current saved files. You can delete gist anytime from your GitHub
|
||||||
|
Gists page.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
cancelText: "Cancel",
|
||||||
|
confirmNode: (
|
||||||
|
<>
|
||||||
|
<FilePlus size="15px" /> Create new Gist
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onConfirm: () => syncToGist(session, true),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const validateFilename = useCallback(
|
const validateFilename = useCallback(
|
||||||
(filename: string): { error: string | null } => {
|
(filename: string): { error: string | null } => {
|
||||||
// check if filename already exists
|
// check if filename already exists
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
return { error: "You need to add filename" };
|
return { error: "You need to add filename" };
|
||||||
}
|
}
|
||||||
if (snap.files.find((file) => file.name === filename)) {
|
if (snap.files.find(file => file.name === filename)) {
|
||||||
return { error: "Filename already exists." };
|
return { error: "Filename already exists." };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,8 +236,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
<Label>Filename</Label>
|
<Label>Filename</Label>
|
||||||
<Input
|
<Input
|
||||||
value={filename}
|
value={filename}
|
||||||
onChange={(e) => setFilename(e.target.value)}
|
onChange={e => setFilename(e.target.value)}
|
||||||
onKeyPress={(e) => {
|
onKeyPress={e => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
handleConfirm();
|
handleConfirm();
|
||||||
}
|
}
|
||||||
@@ -416,7 +427,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
if (snap.gistOwner === session?.user.username) {
|
if (snap.gistOwner === session?.user.username) {
|
||||||
syncToGist(session);
|
syncToGist(session);
|
||||||
} else {
|
} else {
|
||||||
setCreateNewAlertOpen(true);
|
showNewGistAlert();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -466,7 +477,7 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
disabled={status !== "authenticated"}
|
disabled={status !== "authenticated"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCreateNewAlertOpen(true);
|
showNewGistAlert();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilePlus size="16px" /> Create as a new Gist
|
<FilePlus size="16px" /> Create as a new Gist
|
||||||
@@ -486,34 +497,6 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
) : null}
|
) : null}
|
||||||
</Container>
|
</Container>
|
||||||
</Flex>
|
</Flex>
|
||||||
<AlertDialog
|
|
||||||
open={createNewAlertOpen}
|
|
||||||
onOpenChange={(value) => setCreateNewAlertOpen(value)}
|
|
||||||
>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
This action will create new <strong>public</strong> Github Gist from
|
|
||||||
your current saved files. You can delete gist anytime from your
|
|
||||||
GitHub Gists page.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
<Flex css={{ justifyContent: "flex-end", gap: "$3" }}>
|
|
||||||
<AlertDialogCancel asChild>
|
|
||||||
<Button outline>Cancel</Button>
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction asChild>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => {
|
|
||||||
syncToGist(session, true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FilePlus size="15px" /> Create new Gist
|
|
||||||
</Button>
|
|
||||||
</AlertDialogAction>
|
|
||||||
</Flex>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
|
|
||||||
<Dialog open={editorSettingsOpen} onOpenChange={setEditorSettingsOpen}>
|
<Dialog open={editorSettingsOpen} onOpenChange={setEditorSettingsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -529,8 +512,8 @@ const EditorNavigation = ({ showWat }: { showWat?: boolean }) => {
|
|||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
value={editorSettings.tabSize}
|
value={editorSettings.tabSize}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
setEditorSettings((curr) => ({
|
setEditorSettings(curr => ({
|
||||||
...curr,
|
...curr,
|
||||||
tabSize: Number(e.target.value),
|
tabSize: Number(e.target.value),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export { default as Text } from "./Text";
|
|||||||
export { default as Input, Label } from "./Input";
|
export { default as Input, Label } from "./Input";
|
||||||
export { default as Select } from "./Select";
|
export { default as Select } from "./Select";
|
||||||
export * from "./Tabs";
|
export * from "./Tabs";
|
||||||
export * from "./AlertDialog";
|
export * from "./AlertDialog/primitive";
|
||||||
export { default as Box } from "./Box";
|
export { default as Box } from "./Box";
|
||||||
export { default as Button } from "./Button";
|
export { default as Button } from "./Button";
|
||||||
export { default as Pre } from "./Pre";
|
export { default as Pre } from "./Pre";
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import state from "../state";
|
|||||||
import TimeAgo from "javascript-time-ago";
|
import TimeAgo from "javascript-time-ago";
|
||||||
import en from "javascript-time-ago/locale/en.json";
|
import en from "javascript-time-ago/locale/en.json";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
|
import Alert from '../components/AlertDialog';
|
||||||
|
|
||||||
TimeAgo.setDefaultLocale(en.locale);
|
TimeAgo.setDefaultLocale(en.locale);
|
||||||
TimeAgo.addLocale(en);
|
TimeAgo.addLocale(en);
|
||||||
@@ -140,6 +141,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
|
|||||||
})(),
|
})(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Alert />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</IdProvider>
|
</IdProvider>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import Transaction from "../../components/Transaction";
|
|||||||
import state from "../../state";
|
import state from "../../state";
|
||||||
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
import { getSplit, saveSplit } from "../../state/actions/persistSplits";
|
||||||
import { transactionsState, modifyTransaction } from "../../state";
|
import { transactionsState, modifyTransaction } from "../../state";
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { showAlert } from '../../state/actions/showAlert';
|
||||||
|
|
||||||
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|||||||
19
state/actions/showAlert.ts
Normal file
19
state/actions/showAlert.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ref } from 'valtio';
|
||||||
|
import { AlertState, alertState } from "../../components/AlertDialog";
|
||||||
|
|
||||||
|
export const showAlert = (title: string, opts: Partial<AlertState> = {}) => {
|
||||||
|
const { body: _body, confirmNode: _confirmNode, ...rest } = opts
|
||||||
|
const body = (_body && typeof _body === 'object') ? ref(_body) : _body
|
||||||
|
const confirmNode = (_confirmNode && typeof _confirmNode === 'object') ? ref(_confirmNode) : _confirmNode
|
||||||
|
|
||||||
|
const nwState = {
|
||||||
|
isOpen: true,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
confirmNode,
|
||||||
|
...rest
|
||||||
|
}
|
||||||
|
Object.entries(nwState).forEach(([key, value]) => {
|
||||||
|
(alertState as any)[key] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user