diff --git a/components/blocks/autofunction.js b/components/blocks/autofunction.js index afaff66d5..05062a554 100644 --- a/components/blocks/autofunction.js +++ b/components/blocks/autofunction.js @@ -18,6 +18,7 @@ const { publicRuntimeConfig } = getConfig(); import styles from "./autofunction.module.css"; import { looksLikeVersionAndPlatformString } from "../../lib/next/utils"; +import { getThemedUrl, getThemeFromDOM } from "../../lib/next/ThemeContext"; const LATEST_VERSION = publicRuntimeConfig.LATEST_VERSION; const DEFAULT_VERSION = publicRuntimeConfig.DEFAULT_VERSION; @@ -50,19 +51,22 @@ const Autofunction = ({ }, [streamlitFunction]); // Code to destroy and regenerate iframes on each new autofunction render. + // Also updates the theme in iframe URLs to match the current site theme. const regenerateIframes = () => { const iframes = Array.prototype.slice.call( blockRef.current.getElementsByTagName("iframe"), ); if (!iframes) return; + const currentTheme = getThemeFromDOM(); + iframes.forEach((iframe) => { const parent = iframe.parentElement; const newFrame = iframe.cloneNode(); newFrame.src = ""; newFrame.classList.add("new"); - newFrame.src = iframe.src; + newFrame.src = getThemedUrl(iframe.src, currentTheme); parent.replaceChild(newFrame, iframe); }); diff --git a/components/blocks/cloud.js b/components/blocks/cloud.js index 40ffcb57f..dabc04c75 100644 --- a/components/blocks/cloud.js +++ b/components/blocks/cloud.js @@ -1,5 +1,9 @@ -import React, { useEffect, useRef } from "react"; +import React from "react"; import classNames from "classnames"; +import { + useThemeContextSafe, + getThemeFromDOM, +} from "../../lib/next/ThemeContext"; // Arguments: // @@ -34,6 +38,12 @@ 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 }) => { + // Try to get theme from context, fall back to DOM reading + // Context may not be available when rendered via ReactDOMServer.renderToString in table.js + const themeContext = useThemeContextSafe(); + const theme = themeContext?.theme ?? getThemeFromDOM(); + const themeEmbedOption = `embed_options=${theme}_theme`; + if (!domain) domain = `${name}.streamlit.app`; if (domain.endsWith("/")) domain = domain.slice(0, -1); @@ -44,6 +54,8 @@ const Cloud = ({ name, path, query, height, domain, stylePlaceholder }) => { path = ""; } + // We'll process the query param using string processing rather than URLSearchParams because + // when the `query` is a placeholder $3 the "$" gets mangled by URLSearchParams. let normalQueryStr = ""; let embedQueryStr = ""; @@ -68,6 +80,9 @@ const Cloud = ({ name, path, query, height, domain, stylePlaceholder }) => { normalQueryStr = "&" + normalQueryParams.join("&"); } + // Add theme parameter to embed query string + embedQueryStr += `&${themeEmbedOption}`; + if (!height) height = "10rem"; const style = stylePlaceholder diff --git a/components/blocks/table.js b/components/blocks/table.js index 74032eeea..60487c7f3 100644 --- a/components/blocks/table.js +++ b/components/blocks/table.js @@ -122,23 +122,27 @@ const Table = ({ ); }; -// Regex capturing React components: +// Regex capturing React components in the RST docs. const CLOUD_RE = new RegExp( [ "", ].join(""), "g", ); +// Render component using placeholders "$1", etc. to be filled in later. const CLOUD_HTML = ReactDOMServer.renderToString( , ); +// Replace "" string with code for component, +// except the "$x" placeholders should be filled in with values +// captures by the RegEx. function insertCloud(htmlStr) { return htmlStr.replace(CLOUD_RE, CLOUD_HTML); } diff --git a/components/utilities/themeToggle.js b/components/utilities/themeToggle.js index 07d1e2240..9fc879ea9 100644 --- a/components/utilities/themeToggle.js +++ b/components/utilities/themeToggle.js @@ -1,27 +1,14 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import classNames from "classnames"; import styles from "./themeToggle.module.css"; +import { useThemeContext } from "../../lib/next/ThemeContext"; const ThemeToggle = () => { - const [activeTheme, setActiveTheme] = useState("light"); - let inactiveTheme; - inactiveTheme = activeTheme === "light" ? "dark" : "light"; + const { theme, setTheme } = useThemeContext(); + const inactiveTheme = theme === "light" ? "dark" : "light"; - const getUserPreference = () => { - if (window.localStorage.getItem("theme")) { - return window.localStorage.getItem("theme"); - } - return window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; - }; - - const changeTailwindTheme = (theme) => { - inactiveTheme = theme === "light" ? "dark" : "light"; - document.documentElement.classList.add(theme); - document.documentElement.classList.remove(inactiveTheme); - setActiveTheme(theme); - localStorage.setItem("theme", theme); + const toggleTheme = () => { + setTheme(inactiveTheme); }; const showTooltip = () => { @@ -32,23 +19,11 @@ const ThemeToggle = () => { document.getElementsByClassName(styles.Tooltip)[0].style.display = "none"; }; - useEffect(() => { - if (getUserPreference() === "dark") { - changeTailwindTheme("dark"); - } else { - changeTailwindTheme("light"); - } - }, [activeTheme]); - return (