From 40c7b284b5f64c40e43481a943331bb34fdec5e5 Mon Sep 17 00:00:00 2001 From: Kaylee <65376239+KayleeWilliams@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:39:15 +0000 Subject: [PATCH 001/121] feat(react-email): added a theme switcher to the dev preview (#1749) Signed-off-by: dependabot[bot] Co-authored-by: gabriel miranda Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bu Kinoshita <6929565+bukinoshita@users.noreply.github.com> --- .changeset/dirty-needles-chew.md | 5 ++ .../src/app/preview/[...slug]/preview.tsx | 22 ++++- .../src/components/icons/icon-moon.tsx | 16 ++++ .../src/components/icons/icon-sun.tsx | 16 ++++ .../components/topbar/theme-toggle-group.tsx | 84 +++++++++++++++++++ .../src/hooks/use-iframe-color-scheme.ts | 35 ++++++++ 6 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 .changeset/dirty-needles-chew.md create mode 100644 packages/react-email/src/components/icons/icon-moon.tsx create mode 100644 packages/react-email/src/components/icons/icon-sun.tsx create mode 100644 packages/react-email/src/components/topbar/theme-toggle-group.tsx create mode 100644 packages/react-email/src/hooks/use-iframe-color-scheme.ts diff --git a/.changeset/dirty-needles-chew.md b/.changeset/dirty-needles-chew.md new file mode 100644 index 0000000000..acc7dd42e2 --- /dev/null +++ b/.changeset/dirty-needles-chew.md @@ -0,0 +1,5 @@ +--- +"react-email": minor +--- + +Theme switcher for email template diff --git a/packages/react-email/src/app/preview/[...slug]/preview.tsx b/packages/react-email/src/app/preview/[...slug]/preview.tsx index 1f38dad358..cdafca5dc4 100644 --- a/packages/react-email/src/app/preview/[...slug]/preview.tsx +++ b/packages/react-email/src/app/preview/[...slug]/preview.tsx @@ -1,7 +1,7 @@ 'use client'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { use, useState } from 'react'; +import { use, useState, useRef } from 'react'; import { flushSync } from 'react-dom'; import { Toaster } from 'sonner'; import { useDebouncedCallback } from 'use-debounce'; @@ -19,7 +19,9 @@ import { ViewSizeControls } from '../../../components/topbar/view-size-controls' import { PreviewContext } from '../../../contexts/preview'; import { useClampedState } from '../../../hooks/use-clamped-state'; import { cn } from '../../../utils'; +import { useIframeColorScheme } from '../../../hooks/use-iframe-color-scheme'; import { RenderingError } from './rendering-error'; +import { ThemeToggleGroup } from '../../../components/topbar/theme-toggle-group'; interface PreviewProps extends React.ComponentProps<'div'> { emailTitle: string; @@ -32,9 +34,17 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => { const pathname = usePathname(); const searchParams = useSearchParams(); - const activeView = searchParams.get('view') ?? 'preview'; + const activeTheme: 'dark' | 'light' = + searchParams.get('theme') === 'dark' ? 'dark' : 'light'; + const activeView = searchParams.get('view') ?? 'desktop'; const activeLang = searchParams.get('lang') ?? 'jsx'; + const handleThemeChange = (theme: 'dark' | 'light') => { + const params = new URLSearchParams(searchParams); + params.set('theme', theme); + router.push(`${pathname}?${params.toString()}`); + }; + const handleViewChange = (view: string) => { const params = new URLSearchParams(searchParams); params.set('view', view); @@ -51,6 +61,9 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => { ); }; + const iframeRef = useRef(null); + useIframeColorScheme(iframeRef, activeTheme); + const hasRenderingMetadata = typeof renderedEmailMetadata !== 'undefined'; const hasErrors = 'error' in renderingResult; @@ -99,6 +112,10 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => { viewHeight={height} viewWidth={width} /> + handleThemeChange(theme)} + /> {