diff --git a/.changeset/tender-buckets-sin.md b/.changeset/tender-buckets-sin.md new file mode 100644 index 0000000000..c7cfcef2ce --- /dev/null +++ b/.changeset/tender-buckets-sin.md @@ -0,0 +1,5 @@ +--- +"@react-email/preview-server": patch +--- + +use tailwindcss v4 in the UI diff --git a/biome.json b/biome.json index 475d432414..63d6dc1725 100644 --- a/biome.json +++ b/biome.json @@ -7,6 +7,11 @@ } } }, + "css": { + "parser": { + "tailwindDirectives": true + } + }, "vcs": { "enabled": true, "clientKind": "git", diff --git a/packages/preview-server/package.json b/packages/preview-server/package.json index e708d52b46..72a431cdf7 100644 --- a/packages/preview-server/package.json +++ b/packages/preview-server/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-tooltip": "1.2.8", "@react-email/components": "workspace:*", "@react-email/tailwind": "workspace:2.0.1", + "@tailwindcss/postcss": "4.1.17", "@types/babel__core": "7.20.5", "@types/babel__traverse": "7.20.7", "@types/css-tree": "2.3.10", @@ -41,7 +42,6 @@ "@types/react": "19.0.10", "@types/react-dom": "19.0.4", "@types/webpack": "5.28.5", - "autoprefixer": "10.4.21", "clsx": "2.1.1", "colorjs.io": "0.5.2", "esbuild": "0.25.10", @@ -63,7 +63,7 @@ "source-map-js": "1.2.1", "stacktrace-parser": "0.1.11", "tailwind-merge": "3.2.0", - "tailwindcss": "3.4.0", + "tailwindcss": "4.1.17", "typescript": "5.8.3", "use-debounce": "10.0.4", "zod": "4.1.12" diff --git a/packages/preview-server/postcss.config.js b/packages/preview-server/postcss.config.js index b2d8946b3c..e5640725a9 100644 --- a/packages/preview-server/postcss.config.js +++ b/packages/preview-server/postcss.config.js @@ -1,8 +1,5 @@ -const path = require('node:path'); - module.exports = { plugins: { - tailwindcss: { config: path.resolve(__dirname, 'tailwind.config.ts') }, - autoprefixer: {}, + '@tailwindcss/postcss': {}, }, }; diff --git a/packages/preview-server/src/app/globals.css b/packages/preview-server/src/app/globals.css index 10304ba466..b058e518bc 100644 --- a/packages/preview-server/src/app/globals.css +++ b/packages/preview-server/src/app/globals.css @@ -1,6 +1,127 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; + +@theme { + --background-image-gradient: linear-gradient( + 145.37deg, + rgba(255, 255, 255, 0.09) -8.75%, + rgba(255, 255, 255, 0.027) 83.95% + ); + --background-image-gradient-hover: linear-gradient( + 145.37deg, + rgba(255, 255, 255, 0.1) -8.75%, + rgba(255, 255, 255, 0.057) 83.95% + ); + --background-image-shine: linear-gradient( + 45deg, + rgba(255, 255, 255, 0) 45%, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 0) 55%, + rgba(255, 255, 255, 0) 100% + ); + + --color-cyan-1: #0091f70a; + --color-cyan-2: #02a7f211; + --color-cyan-3: #00befd28; + --color-cyan-4: #00baff3b; + --color-cyan-5: #00befd4d; + --color-cyan-6: #00c7fd5e; + --color-cyan-7: #14cdff75; + --color-cyan-8: #11cfff95; + --color-cyan-9: #00cfffc3; + --color-cyan-10: #28d6ffcd; + --color-cyan-11: #52e1fee5; + --color-cyan-12: #bbf3fef7; + + --color-slate-1: #00000000; + --color-slate-2: #d8f4f609; + --color-slate-3: #ddeaf814; + --color-slate-4: #d3edf81d; + --color-slate-5: #d9edfe25; + --color-slate-6: #d6ebfd30; + --color-slate-7: #d9edff40; + --color-slate-8: #d9edff5d; + --color-slate-9: #dfebfd6d; + --color-slate-10: #e5edfd7b; + --color-slate-11: #f1f7feb5; + --color-slate-12: #fcfdffef; + + --font-sans: + var(--font-inter), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: + var(--font-sf-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + + --animate-spin-slow: spin 3s linear infinite; + --animate-spin-fast: spin 1.5s linear infinite; + + @keyframes shine { + 0% { + background-position: -100%; + } + + 100% { + background-position: 100%; + } + } + + @keyframes dash { + 0% { + stroke-dashoffset: 1000; + } + + 100% { + stroke-dashoffset: 0; + } + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } +} + +@utility arrow-hide { + appearance: textfield; + + &::-webkit-inner-spin-button { + appearance: none; + margin: 0px; + } + + &::-webkit-outer-spin-button { + appearance: none; + margin: 0px; + } +} + +/* + The default border color has changed to `currentcolor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } + + button:not(:disabled), + [role="button"]:not(:disabled) { + cursor: pointer; + } +} html { color-scheme: dark; diff --git a/packages/preview-server/src/app/layout.tsx b/packages/preview-server/src/app/layout.tsx index 0f4ea0dcc4..4a99619456 100644 --- a/packages/preview-server/src/app/layout.tsx +++ b/packages/preview-server/src/app/layout.tsx @@ -33,7 +33,7 @@ export default async function RootLayout({ lang="en" > -
+
diff --git a/packages/preview-server/src/app/preview/[...slug]/error-overlay.tsx b/packages/preview-server/src/app/preview/[...slug]/error-overlay.tsx index a7dd003cc3..e2304c2723 100644 --- a/packages/preview-server/src/app/preview/[...slug]/error-overlay.tsx +++ b/packages/preview-server/src/app/preview/[...slug]/error-overlay.tsx @@ -36,17 +36,17 @@ export const ErrorOverlay = ({ error }: ErrorOverlayProps) => { min-h-[50vh] w-full max-w-lg sm:rounded-lg md:max-w-[568px] lg:max-w-[920px] absolute left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%] rounded-t-sm overflow-hidden bg-white text-black shadow-lg duration-200 - flex flex-col selection:!text-black + flex flex-col selection:text-black! " >
-
-
+
+
{error.name}: {error.message}
{error.stack ? ( -
-
+            
+
                 {error.stack}
               
diff --git a/packages/preview-server/src/components/button.tsx b/packages/preview-server/src/components/button.tsx index 84c7453035..91c7545ee7 100644 --- a/packages/preview-server/src/components/button.tsx +++ b/packages/preview-server/src/components/button.tsx @@ -70,14 +70,14 @@ const getAppearance = (appearance: Appearance | undefined) => { return [ 'border-white bg-white text-black transition-colors duration-200 ease-in-out', 'hover:bg-white/90', - 'focus:bg-white/90 focus:outline-none focus:ring-2 focus:ring-white/20', + 'focus:bg-white/90 focus:outline-hidden focus:ring-2 focus:ring-white/20', 'mt-2 mb-2 aria-disabled:border-transparent aria-disabled:bg-slate-11', ]; case 'gradient': return [ 'bg-gradient border-[#34343A] backdrop-blur-[1.25rem]', - 'hover:bg-gradientHover', - 'focus:bg-gradientHover focus:outline-none focus:ring-2 focus:ring-white/20', + 'hover:bg-gradient-hover', + 'focus:bg-gradient-hover focus:outline-hidden focus:ring-2 focus:ring-white/20', ]; default: unreachable(appearance); diff --git a/packages/preview-server/src/components/code.tsx b/packages/preview-server/src/components/code.tsx index 7f311e22ee..d512a1571e 100644 --- a/packages/preview-server/src/components/code.tsx +++ b/packages/preview-server/src/components/code.tsx @@ -129,8 +129,8 @@ export const Code: React.FC> = ({ scroll={false} aria-selected={isHighlighted} className={cn( - 'text-[#49494f] relative text-[13px] font-light font-[MonoLisa,_Menlo,_monospace] align-middle scroll-mt-[325px] select-none pr-3 cursor-pointer hover:text-slate-12 transition-colors', - 'aria-selected:text-cyan-11 aria-selected:hover:text-cyan-11 aria-selected:bg-cyan-5 [&+*]:aria-selected:bg-cyan-5', + 'text-[#49494f] relative text-[13px] font-light font-[MonoLisa,Menlo,monospace] align-middle scroll-mt-[325px] select-none pr-3 cursor-pointer hover:text-slate-12 transition-colors', + 'aria-selected:text-cyan-11 aria-selected:hover:text-cyan-11 aria-selected:bg-cyan-5 aria-selected:[&+*]:bg-cyan-5', isHighlightStart && 'rounded-tl-sm', isHighlightEnd && 'rounded-bl-sm', )} diff --git a/packages/preview-server/src/components/icons/icon-button.tsx b/packages/preview-server/src/components/icons/icon-button.tsx index 52b7567b94..4ebe38f7a4 100644 --- a/packages/preview-server/src/components/icons/icon-button.tsx +++ b/packages/preview-server/src/components/icons/icon-button.tsx @@ -11,7 +11,7 @@ export const IconButton = React.forwardRef< type="button" {...props} className={cn( - 'focus:ring-gray-8 rounded text-slate-11 transition duration-200 ease-in-out hover:text-slate-12 focus:text-slate-12 focus:outline-none focus:ring-2', + 'focus:ring-gray-8 rounded-sm text-slate-11 transition duration-200 ease-in-out hover:text-slate-12 focus:text-slate-12 focus:outline-hidden focus:ring-2', className, )} ref={forwardedRef} diff --git a/packages/preview-server/src/components/send.tsx b/packages/preview-server/src/components/send.tsx index f4a642c03c..4dcfe98855 100644 --- a/packages/preview-server/src/components/send.tsx +++ b/packages/preview-server/src/components/send.tsx @@ -60,7 +60,7 @@ export const Send = ({ markup }: { markup: string }) => { >
-
+
{ className={cn( 'inline-block relative overflow-hidden will-change-[width]', 'w-full h-full', - '[transition:width_0.2s_ease-in-out,_transform_0.2s_ease-in-out]', + '[transition:width_0.2s_ease-in-out,transform_0.2s_ease-in-out]', { 'lg:w-[calc(100%-16rem)]': sidebarToggled, 'opacity-0 lg:opacity-100': !sidebarToggled, diff --git a/packages/preview-server/src/components/sidebar/file-tree-directory-children.tsx b/packages/preview-server/src/components/sidebar/file-tree-directory-children.tsx index 399f237611..2a42b58cf1 100644 --- a/packages/preview-server/src/components/sidebar/file-tree-directory-children.tsx +++ b/packages/preview-server/src/components/sidebar/file-tree-directory-children.tsx @@ -108,7 +108,7 @@ export const FileTreeDirectoryChildren = (props: { > {props.isRoot ? null : ( { return ( -
-