-
- Are you sure?
-
-
- Some extra information.
-
diff --git a/frontend/src/components/Dialog/Dialog.module.css b/frontend/src/components/Dialog/Dialog.module.css
new file mode 100644
index 000000000..6770cb54f
--- /dev/null
+++ b/frontend/src/components/Dialog/Dialog.module.css
@@ -0,0 +1,136 @@
+/* Copyright 2024 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+.overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(3 12 27 / 52.8%);
+}
+
+.dialog {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: calc(496px + var(--cpd-space-3x) * 2);
+ max-width: calc(100vw - var(--cpd-space-4x));
+}
+
+.body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--cpd-space-8x);
+ background: var(--cpd-color-bg-canvas-default);
+}
+
+.title {
+ font: var(--cpd-font-heading-md-semibold);
+ letter-spacing: var(--cpd-font-letter-spacing-heading-md);
+ color: var(--cpd-color-text-primary);
+ margin-block-start: var(--cpd-space-4x);
+}
+
+.actions {
+ display: flex;
+ flex-direction: column;
+ gap: var(--cpd-space-4x);
+}
+
+.dialog .body {
+ padding: var(--cpd-space-4x) var(--cpd-space-10x) var(--cpd-space-10x);
+}
+
+.dialog .title {
+ padding-inline-end: var(--cpd-space-7x);
+}
+
+.dialog .close {
+ position: absolute;
+ inset-block-start: var(--cpd-space-7x);
+ inset-inline-end: var(--cpd-space-7x);
+ padding: var(--cpd-space-1x);
+ border-radius: var(--cpd-radius-pill-effect);
+ background: var(--cpd-color-bg-subtle-secondary);
+ color: var(--cpd-color-icon-secondary);
+
+ &>svg {
+ inline-size: var(--cpd-space-5x);
+ block-size: var(--cpd-space-5x);
+ }
+
+ &:hover {
+ background: var(--cpd-color-bg-subtle-primary);
+ color: var(--cpd-color-icon-primary);
+ }
+}
+
+.drawer {
+ position: fixed;
+ background: var(--cpd-color-bg-canvas-default);
+ inset-block-end: 0;
+ inset-inline: 0;
+
+ /* Cap the inline content size at 520px, filling the rest of the space with
+ padding */
+ padding-inline: max(0px, calc((100% - 520px) / 2));
+ border-start-start-radius: var(--border-radius);
+ border-start-end-radius: var(--border-radius);
+ display: flex;
+ flex-direction: column;
+
+ /* Drawer comes in the Android style by default */
+ --border-radius: 28px;
+ --handle-block-size: 4px;
+ --handle-inline-size: 32px;
+ --handle-inset-block-start: var(--cpd-space-4x);
+ --handle-inset-block-end: 0px;
+ --content-inset-block-start: calc(var(--handle-inset-block-start) + var(--handle-block-size) + var(--handle-inset-block-end));
+}
+
+.drawer[data-platform="ios"] {
+ --border-radius: 10px;
+ --handle-block-size: 5px;
+ --handle-inline-size: 36px;
+ --handle-inset-block-start: var(--cpd-space-1-5x);
+ --handle-inset-block-end: 1px;
+}
+
+.drawer .body {
+ padding-inline: var(--cpd-space-4x);
+ padding-block-start: calc(var(--content-inset-block-start) + var(--cpd-space-6x));
+ padding-block-end: var(--cpd-space-12x);
+ border-start-start-radius: var(--border-radius);
+ border-start-end-radius: var(--border-radius);
+
+ /* Even with overflow: auto, the content can still overflow at the corners
+ where it meets with the curved border. A contain: paint fixes that. */
+ contain: paint;
+ overflow: auto;
+ scrollbar-width: none;
+
+ --cpd-separator-spacing: 0;
+ --cpd-separator-inset: var(--cpd-space-4x);
+}
+
+.drawer .body::before {
+ content: "";
+ position: absolute;
+ block-size: var(--handle-block-size);
+ inset-inline: calc((100% - var(--handle-inline-size)) / 2);
+ inset-block-start: var(--handle-inset-block-start);
+ background: var(--cpd-color-icon-secondary);
+ border-radius: var(--cpd-radius-pill-effect);
+}
diff --git a/frontend/src/components/Dialog/Dialog.stories.tsx b/frontend/src/components/Dialog/Dialog.stories.tsx
new file mode 100644
index 000000000..b4f32961a
--- /dev/null
+++ b/frontend/src/components/Dialog/Dialog.stories.tsx
@@ -0,0 +1,56 @@
+// Copyright 2024 The Matrix.org Foundation C.I.C.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { action } from "@storybook/addon-actions";
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { Dialog, Title, Description } from "./Dialog";
+
+const Template: React.FC<{
+ title: string;
+ description: string;
+ asDrawer: boolean;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}> = ({ title, description, asDrawer, open, onOpenChange }) => (
+