diff --git a/components/blocks/cloud.js b/components/blocks/cloud.js index 40ffcb57f..308e64192 100644 --- a/components/blocks/cloud.js +++ b/components/blocks/cloud.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import classNames from "classnames"; // Arguments: @@ -34,6 +34,19 @@ import classNames from "classnames"; // -> https://foo.streamlit.app/bar/?embed=true&embed_options=show_padding&embed_options=show_colored_line // const Cloud = ({ name, path, query, height, domain, stylePlaceholder }) => { + // State to track theme, starts with light theme for SSR + const [themeEmbedOption, setThemeEmbedOption] = useState( + "embed_options=light_theme", + ); + + // Update theme after component mounts (client-side only) + useEffect(() => { + const currentTheme = document.documentElement.classList.contains("dark") + ? "embed_options=dark_theme" + : "embed_options=light_theme"; + setThemeEmbedOption(currentTheme); + }, []); + if (!domain) domain = `${name}.streamlit.app`; if (domain.endsWith("/")) domain = domain.slice(0, -1); @@ -44,18 +57,15 @@ const Cloud = ({ name, path, query, height, domain, stylePlaceholder }) => { path = ""; } - let normalQueryStr = ""; - let embedQueryStr = ""; - // Separate "normal" query params from "embed-related" query params. // This way we can include only the "normal" query params in the Fullscreen link. // Note that this only applies to iframes rendered via the component // in React. For iframes rendered via the ".. output::" directive we **always** // include any provided query param in the Fullscreen link. - if (query) { - const embedQueryParams = []; - const normalQueryParams = []; + const embedQueryParams = [themeEmbedOption]; + const normalQueryParams = []; + if (query) { query.split("&").forEach((qStr) => { if (qStr.startsWith("embed=") || qStr.startsWith("embed_options=")) { embedQueryParams.push(qStr); @@ -63,11 +73,11 @@ const Cloud = ({ name, path, query, height, domain, stylePlaceholder }) => { normalQueryParams.push(qStr); } }); - - embedQueryStr = "&" + embedQueryParams.join("&"); - normalQueryStr = "&" + normalQueryParams.join("&"); } + const embedQueryStr = "&" + embedQueryParams.join("&"); + const normalQueryStr = "&" + normalQueryParams.join("&"); + if (!height) height = "10rem"; const style = stylePlaceholder diff --git a/components/utilities/themeToggle.js b/components/utilities/themeToggle.js index 07d1e2240..df6002da6 100644 --- a/components/utilities/themeToggle.js +++ b/components/utilities/themeToggle.js @@ -22,6 +22,33 @@ const ThemeToggle = () => { document.documentElement.classList.remove(inactiveTheme); setActiveTheme(theme); localStorage.setItem("theme", theme); + + // Force reload all Cloud iframes on the current page + const iframes = document.querySelectorAll('iframe[src*="streamlit.app"]'); + iframes.forEach((iframe) => { + const currentSrc = iframe.src; + const url = new URL(currentSrc); + + // Get all existing embed_options + const existingEmbedOptions = url.searchParams.getAll("embed_options"); + + // Remove only theme-related embed_options (light_theme or dark_theme) + const nonThemeOptions = existingEmbedOptions.filter( + (option) => option !== "light_theme" && option !== "dark_theme", + ); + + // Clear all embed_options and re-add the non-theme ones + url.searchParams.delete("embed_options"); + nonThemeOptions.forEach((option) => + url.searchParams.append("embed_options", option), + ); + + // Add new theme parameter + url.searchParams.append("embed_options", `${theme}_theme`); + + // Force reload iframe with new theme + iframe.src = url.toString(); + }); }; const showTooltip = () => {