diff --git a/app/components/Button.tsx b/app/components/Button.tsx
index e8171c2f5..8f4a22527 100644
--- a/app/components/Button.tsx
+++ b/app/components/Button.tsx
@@ -113,6 +113,10 @@ const RealButton = styled.button<{
&:disabled {
background: none;
}
+
+ &.focus-visible {
+ outline-color: ${darken(0.2, props.theme.danger)} !important;
+ }
`};
`;
diff --git a/app/components/Dialogs.tsx b/app/components/Dialogs.tsx
index 57e42ddce..8cf15fe4e 100644
--- a/app/components/Dialogs.tsx
+++ b/app/components/Dialogs.tsx
@@ -22,6 +22,7 @@ function Dialogs() {
dialogs.closeModal(id)}
title={modal.title}
>
diff --git a/app/components/Modal.tsx b/app/components/Modal.tsx
index 3f81ecc16..d54ff4915 100644
--- a/app/components/Modal.tsx
+++ b/app/components/Modal.tsx
@@ -9,6 +9,8 @@ import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import Scrollable from "~/components/Scrollable";
+import Text from "~/components/Text";
+import useMobile from "~/hooks/useMobile";
import usePrevious from "~/hooks/usePrevious";
import useUnmount from "~/hooks/useUnmount";
import { fadeAndScaleIn } from "~/styles/animations";
@@ -16,6 +18,7 @@ import { fadeAndScaleIn } from "~/styles/animations";
let openModals = 0;
type Props = {
isOpen: boolean;
+ isCentered?: boolean;
title?: React.ReactNode;
onRequestClose: () => void;
};
@@ -23,6 +26,7 @@ type Props = {
const Modal: React.FC = ({
children,
isOpen,
+ isCentered,
title = "Untitled",
onRequestClose,
}) => {
@@ -31,6 +35,7 @@ const Modal: React.FC = ({
});
const [depth, setDepth] = React.useState(0);
const wasOpen = usePrevious(isOpen);
+ const isMobile = useMobile();
const { t } = useTranslation();
React.useEffect(() => {
@@ -58,37 +63,59 @@ const Modal: React.FC = ({
return (
{(props) => (
-
+
)}
@@ -96,14 +123,16 @@ const Modal: React.FC = ({
);
};
-const Backdrop = styled.div`
+const Backdrop = styled(Flex)<{ $isCentered?: boolean }>`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: ${(props) =>
- transparentize(0.25, props.theme.background)} !important;
+ props.$isCentered
+ ? props.theme.modalBackdrop
+ : transparentize(0.25, props.theme.background)} !important;
z-index: ${(props) => props.theme.depths.modalOverlay};
transition: opacity 50ms ease-in-out;
opacity: 0;
@@ -113,7 +142,7 @@ const Backdrop = styled.div`
}
`;
-const Scene = styled.div<{ $nested: boolean }>`
+const Fullscreen = styled.div<{ $nested: boolean }>`
animation: ${fadeAndScaleIn} 250ms ease;
position: absolute;
@@ -142,10 +171,10 @@ const Scene = styled.div<{ $nested: boolean }>`
const Content = styled(Scrollable)`
width: 100%;
- padding: 8vh 2rem 2rem;
+ padding: 8vh 32px;
${breakpoint("tablet")`
- padding-top: 13vh;
+ padding: 13vh 2rem 2rem;
`};
`;
@@ -156,13 +185,6 @@ const Centered = styled(Flex)`
margin: 0 auto;
`;
-const Text = styled.span`
- font-size: 16px;
- font-weight: 500;
- padding-right: 12px;
- user-select: none;
-`;
-
const Close = styled(NudeButton)`
position: absolute;
display: block;
@@ -191,6 +213,7 @@ const Back = styled(NudeButton)`
left: 2rem;
opacity: 0.75;
color: ${(props) => props.theme.text};
+ font-weight: 500;
width: auto;
height: auto;
@@ -203,4 +226,40 @@ const Back = styled(NudeButton)`
`};
`;
+const Header = styled(Flex)`
+ color: ${(props) => props.theme.textSecondary};
+ align-items: center;
+ justify-content: space-between;
+ font-weight: 600;
+ padding: 24px 24px 4px;
+`;
+
+const Small = styled.div`
+ animation: ${fadeAndScaleIn} 250ms ease;
+
+ margin: auto auto;
+ min-width: 350px;
+ max-width: 30vw;
+ z-index: ${(props) => props.theme.depths.modal};
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ background: ${(props) => props.theme.modalBackground};
+ transition: ${(props) => props.theme.backgroundTransition};
+ box-shadow: ${(props) => props.theme.modalShadow};
+ border-radius: 8px;
+ outline: none;
+
+ ${NudeButton} {
+ &:hover,
+ &[aria-expanded="true"] {
+ background: ${(props) => props.theme.sidebarControlHoverBackground};
+ }
+ }
+`;
+
+const SmallContent = styled(Scrollable)`
+ padding: 12px 24px 24px;
+`;
+
export default observer(Modal);
diff --git a/app/components/Sidebar/components/TrashLink.tsx b/app/components/Sidebar/components/TrashLink.tsx
index 5dbdf6ba7..5ba9bee61 100644
--- a/app/components/Sidebar/components/TrashLink.tsx
+++ b/app/components/Sidebar/components/TrashLink.tsx
@@ -50,6 +50,7 @@ function TrashLink() {
})}
onRequestClose={() => setDocument(undefined)}
isOpen
+ isCentered
>
{
@@ -139,6 +138,19 @@ function CollectionMenu({
[collection, menu]
);
+ const handleDelete = React.useCallback(() => {
+ dialogs.openModal({
+ isCentered: true,
+ title: t("Delete collection"),
+ content: (
+
+ ),
+ });
+ }, [dialogs, t, collection]);
+
const alphabeticalSort = collection.sort.field === "title";
const can = usePolicy(collection.id);
const canUserInTeam = usePolicy(team.id);
@@ -214,7 +226,7 @@ function CollectionMenu({
title: `${t("Delete")}…`,
dangerous: true,
visible: !!(collection && can.delete),
- onClick: () => setShowCollectionDelete(true),
+ onClick: handleDelete,
icon: ,
},
],
@@ -226,6 +238,7 @@ function CollectionMenu({
handleChangeSort,
handleNewDocument,
handleImportDocument,
+ handleDelete,
collection,
canUserInTeam.export,
]
@@ -282,16 +295,6 @@ function CollectionMenu({
collectionId={collection.id}
/>
- setShowCollectionDelete(false)}
- >
- setShowCollectionDelete(false)}
- collection={collection}
- />
-
setShowDeleteModal(false)}
isOpen={showDeleteModal}
+ isCentered
>
setShowPermanentDeleteModal(false)}
isOpen={showPermanentDeleteModal}
+ isCentered
>
setShowTemplateModal(false)}
isOpen={showTemplateModal}
+ isCentered
>
setDeleteModalOpen(false)}
isOpen={deleteModalOpen}
+ isCentered
>
setDeleteModalOpen(false)} />
diff --git a/app/stores/DialogsStore.ts b/app/stores/DialogsStore.ts
index de0483a10..a509a5ed6 100644
--- a/app/stores/DialogsStore.ts
+++ b/app/stores/DialogsStore.ts
@@ -2,23 +2,19 @@ import { observable, action } from "mobx";
import * as React from "react";
import { v4 as uuidv4 } from "uuid";
+type DialogDefinition = {
+ title: string;
+ content: React.ReactNode;
+ isOpen: boolean;
+ isCentered?: boolean;
+};
+
export default class DialogsStore {
@observable
- guide: {
- title: string;
- content: React.ReactNode;
- isOpen: boolean;
- };
+ guide: DialogDefinition;
@observable
- modalStack = new Map<
- string,
- {
- title: string;
- content: React.ReactNode;
- isOpen: boolean;
- }
- >();
+ modalStack = new Map();
openGuide = ({
title,
@@ -49,9 +45,11 @@ export default class DialogsStore {
openModal = ({
title,
content,
+ isCentered,
replace,
}: {
title: string;
+ isCentered?: boolean;
content: React.ReactNode;
replace?: boolean;
}) => {
@@ -67,6 +65,7 @@ export default class DialogsStore {
title,
content,
isOpen: true,
+ isCentered,
});
}),
0
diff --git a/app/typings/styled-components.d.ts b/app/typings/styled-components.d.ts
index 51fe0effe..042a141de 100644
--- a/app/typings/styled-components.d.ts
+++ b/app/typings/styled-components.d.ts
@@ -156,6 +156,9 @@ declare module "styled-components" {
sidebarText: string;
backdrop: string;
shadow: string;
+ modalBackdrop: string;
+ modalBackground: string;
+ modalShadow: string;
menuItemSelected: string;
menuBackground: string;
menuShadow: string;
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index f78d40d43..b2ee70dfe 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -234,6 +234,7 @@
"Path to document": "Path to document",
"Group member options": "Group member options",
"Remove": "Remove",
+ "Delete collection": "Delete collection",
"Sort in sidebar": "Sort in sidebar",
"Alphabetical sort": "Alphabetical sort",
"Manual sort": "Manual sort",
@@ -241,7 +242,6 @@
"Permissions": "Permissions",
"Delete": "Delete",
"Collection permissions": "Collection permissions",
- "Delete collection": "Delete collection",
"Export collection": "Export collection",
"Document restored": "Document restored",
"Document unpublished": "Document unpublished",
diff --git a/shared/theme.ts b/shared/theme.ts
index 3be9638a1..d55546b30 100644
--- a/shared/theme.ts
+++ b/shared/theme.ts
@@ -140,6 +140,12 @@ export const light = {
sidebarText: "rgb(78, 92, 110)",
backdrop: "rgba(0, 0, 0, 0.2)",
shadow: "rgba(0, 0, 0, 0.2)",
+
+ modalBackdrop: colors.black10,
+ modalBackground: colors.white,
+ modalShadow:
+ "0 4px 8px rgb(0 0 0 / 8%), 0 2px 4px rgb(0 0 0 / 0%), 0 30px 40px rgb(0 0 0 / 8%)",
+
menuItemSelected: colors.warmGrey,
menuBackground: colors.white,
menuShadow:
@@ -191,6 +197,12 @@ export const dark = {
sidebarText: colors.slate,
backdrop: "rgba(255, 255, 255, 0.3)",
shadow: "rgba(0, 0, 0, 0.6)",
+
+ modalBackdrop: colors.black50,
+ modalBackground: "#1f2128",
+ modalShadow:
+ "0 0 0 1px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.08)",
+
menuItemSelected: lighten(0.1, "#1f2128"),
menuBackground: "#1f2128",
menuShadow: