diff --git a/src/components/ToastSubscribed/index.js b/src/components/ToastSubscribed/index.js
new file mode 100644
index 00000000000..a9e719572d3
--- /dev/null
+++ b/src/components/ToastSubscribed/index.js
@@ -0,0 +1,34 @@
+import React from "react";
+import Admonition from "@theme/Admonition";
+import IconCheck from "@site/static/img/icon-check.svg";
+
+const ToastSubscribed = () => {
+ return (
+ }
+ title="You're subscribed"
+ className="toast"
+ >
+ You’ll now receive our Changelog updates by email.
+
+
+ );
+};
+
+export default ToastSubscribed;
diff --git a/src/components/modal/index.js b/src/components/modal/index.js
new file mode 100644
index 00000000000..da4b0bb1cc8
--- /dev/null
+++ b/src/components/modal/index.js
@@ -0,0 +1,61 @@
+import React, { useEffect } from "react";
+import "./styles.css";
+
+const Modal = ({ isOpen, onClose, children }) => {
+ useEffect(() => {
+ if (isOpen) {
+ document.body.style.overflow = "hidden";
+ } else {
+ document.body.style.overflow = "unset";
+ }
+
+ return () => {
+ document.body.style.overflow = "unset";
+ };
+ }, [isOpen]);
+
+ useEffect(() => {
+ const handleEscape = (event) => {
+ if (event.key === "Escape" && isOpen) {
+ onClose();
+ }
+ };
+
+ document.addEventListener("keydown", handleEscape);
+ return () => document.removeEventListener("keydown", handleEscape);
+ }, [isOpen, onClose]);
+
+ if (!isOpen) return null;
+
+ return (
+
+
e.stopPropagation()}
+ role="dialog"
+ aria-modal="true"
+ >
+
+
{children}
+
+
+ );
+};
+
+export default Modal;
diff --git a/src/components/modal/styles.css b/src/components/modal/styles.css
new file mode 100644
index 00000000000..b01dea531a8
--- /dev/null
+++ b/src/components/modal/styles.css
@@ -0,0 +1,65 @@
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(38, 35, 47, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal-content {
+ background: #fff;
+ border-radius: 24px;
+ padding: 48px;
+ position: relative;
+ max-height: 95vh;
+ overflow-y: auto;
+ box-shadow: 0 2px 10px rgba(39, 26, 72, 0.18);
+ animation: modalFadeIn 0.3s ease-out;
+ width: 100%;
+ max-width: 600px;
+}
+
+.modal-close {
+ position: absolute;
+ top: 44px;
+ right: 44px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ color: #454348;
+ padding: 0;
+ width: 30px;
+ height: 30px;
+}
+
+.modal-body {
+ color: #454348;
+ font-size: 14px;
+ line-height: 24px;
+}
+
+.modal-body h3 {
+ margin: 8px 0 24px;
+}
+.modal-body p {
+ margin-bottom: 0;
+}
+.modal-body iframe {
+ border-radius: 8px;
+}
+
+@keyframes modalFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/src/css/custom.css b/src/css/custom.css
index 177a281e606..64de7ad430e 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -376,4 +376,48 @@ html[data-theme="dark"] .tabs__item--active {
width: 1px;
height: 20px;
background: #cbd5e1;
+}
+
+.toast {
+ position: fixed;
+ bottom: 72px;
+ right: 26px;
+ box-shadow: 0px 4px 10px 0px #271A482E;
+ opacity: 0;
+ animation: toastFadeIn 0.5s ease;
+ animation-fill-mode: forwards;
+ animation-delay: 1s;
+ transform: translateX(10px);
+
+}
+
+.toast--hidden {
+ animation: toastFadeOut 0.5s ease;
+ animation-fill-mode: forwards;
+}
+
+.toast-close {
+ position: absolute;
+ top: 12px;
+ right: 16px;
+ cursor: pointer;
+}
+
+
+@keyframes toastFadeIn {
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes toastFadeOut {
+ from {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateX(10px);
+ }
}
\ No newline at end of file
diff --git a/src/theme/BlogListPage/index.js b/src/theme/BlogListPage/index.js
index d7309460371..6266def4374 100644
--- a/src/theme/BlogListPage/index.js
+++ b/src/theme/BlogListPage/index.js
@@ -12,6 +12,7 @@ import SearchMetadata from "@theme/SearchMetadata";
import BlogPostItems from "@theme/BlogPostItems";
import MDXContent from "@theme/MDXContent";
import ComingUp from "../../../changelog/coming-up.mdx";
+import ToastSubscribed from "../../components/ToastSubscribed";
function BlogListPageMetadata(props) {
const { metadata } = props;
@@ -29,9 +30,11 @@ function BlogListPageMetadata(props) {
);
}
function BlogListPageContent(props) {
- const { metadata, items, sidebar } = props;
+ const { metadata, items, sidebar, history } = props;
+
return (
+ {history.location.hash === "#subscribed" && }
{metadata.page === 1 ? (
diff --git a/src/theme/BlogSidebar/Desktop/index.js b/src/theme/BlogSidebar/Desktop/index.js
index 0e580d30f6a..8d2f076528c 100644
--- a/src/theme/BlogSidebar/Desktop/index.js
+++ b/src/theme/BlogSidebar/Desktop/index.js
@@ -1,11 +1,14 @@
-import React from "react";
+import React, { useState } from "react";
import clsx from "clsx";
import Link from "@docusaurus/Link";
import { translate } from "@docusaurus/Translate";
import { useVisibleBlogSidebarItems } from "@docusaurus/theme-common/internal";
import styles from "./styles.module.css";
+import Modal from "@site/src/components/modal";
export default function BlogSidebarDesktop({ sidebar }) {
+ const [newsletterModalVisible, setNewsletterModal] = useState(false);
const items = useVisibleBlogSidebarItems(sidebar.items);
+
return (
);
}
diff --git a/static/img/icon-check.svg b/static/img/icon-check.svg
new file mode 100644
index 00000000000..4bc1ffdaf43
--- /dev/null
+++ b/static/img/icon-check.svg
@@ -0,0 +1,3 @@
+