@react-email/ui 6.8.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +10 -9
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/page/server-reference-manifest.json +1 -1
  16. package/.next/server/app/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/preview/[...slug]/page/server-reference-manifest.json +8 -8
  18. package/.next/server/app/preview/[...slug]/page.js +5 -4
  19. package/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  20. package/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  21. package/.next/server/chunks/ssr/{0kci_next_dist_esm_build_templates_app-page_0o.jt0x.js → 0kci_next_dist_esm_build_templates_app-page_13jevt-.js} +3 -3
  22. package/.next/server/chunks/ssr/0kci_next_dist_esm_build_templates_app-page_13jevt-.js.map +1 -0
  23. package/.next/server/chunks/ssr/[root-of-the-server]__0jx5d~g._.js +2 -2
  24. package/.next/server/chunks/ssr/[root-of-the-server]__0k6754k._.js +1 -1
  25. package/.next/server/chunks/ssr/[root-of-the-server]__0k6754k._.js.map +1 -1
  26. package/.next/server/chunks/ssr/{[root-of-the-server]__0d.h7dp._.js → [root-of-the-server]__13sdo3r._.js} +2 -2
  27. package/.next/server/chunks/ssr/_03liear._.js +1 -1
  28. package/.next/server/chunks/ssr/_03liear._.js.map +1 -1
  29. package/.next/server/chunks/ssr/_06p~r0t._.js +1 -1
  30. package/.next/server/chunks/ssr/_06p~r0t._.js.map +1 -1
  31. package/.next/server/chunks/ssr/_0au~b4w._.js +1 -1
  32. package/.next/server/chunks/ssr/_0au~b4w._.js.map +1 -1
  33. package/.next/server/chunks/ssr/_0bb37hq._.js +1 -1
  34. package/.next/server/chunks/ssr/_0bb37hq._.js.map +1 -1
  35. package/.next/server/chunks/ssr/_0jzzir7._.js +1 -1
  36. package/.next/server/chunks/ssr/_0jzzir7._.js.map +1 -1
  37. package/.next/server/chunks/ssr/_0o5084b._.js +5 -5
  38. package/.next/server/chunks/ssr/_0o5084b._.js.map +1 -1
  39. package/.next/server/chunks/ssr/_0z2_2b7._.js +1 -1
  40. package/.next/server/chunks/ssr/_0z2_2b7._.js.map +1 -1
  41. package/.next/server/chunks/ssr/packages_ui_src_0.ov~_x._.js +3 -0
  42. package/.next/server/chunks/ssr/packages_ui_src_0.ov~_x._.js.map +1 -0
  43. package/.next/server/chunks/ssr/packages_ui_src_11mz0xt._.js +3 -0
  44. package/.next/server/chunks/ssr/packages_ui_src_11mz0xt._.js.map +1 -0
  45. package/.next/server/middleware-build-manifest.js +3 -3
  46. package/.next/server/pages/500.html +1 -1
  47. package/.next/server/server-reference-manifest.js +1 -1
  48. package/.next/server/server-reference-manifest.json +9 -9
  49. package/.next/static/chunks/{0xfhv_nejv2yt.js → 0.__jzgiil6q7.js} +1 -1
  50. package/.next/static/chunks/00ecsosfoo~be.css +3 -0
  51. package/.next/static/chunks/{15~kc67eqdve8.js → 0cr8lx05fcstc.js} +1 -1
  52. package/.next/static/chunks/0nhnu1wkjtfwe.js +1 -0
  53. package/.next/static/chunks/{0m-l5t15j5bnl.js → 0ntsvdyzr-1u3.js} +1 -1
  54. package/.next/static/chunks/0owottqfmsjwp.js +42 -0
  55. package/.next/trace +1 -1
  56. package/.next/trace-build +1 -1
  57. package/.next/types/routes.d.ts +3 -1
  58. package/.next/types/validator.ts +9 -0
  59. package/package.json +2 -2
  60. package/src/app/preview/[...slug]/preview.tsx +126 -104
  61. package/src/app/preview/layout.tsx +9 -0
  62. package/src/components/code.tsx +2 -2
  63. package/src/components/icons/icon-panel-right.tsx +19 -0
  64. package/src/components/json-editor.tsx +71 -0
  65. package/src/components/preview-props-editor.tsx +101 -0
  66. package/src/components/preview-props-panel.tsx +31 -0
  67. package/src/components/toolbar/copy-for-ai.tsx +3 -3
  68. package/src/components/toolbar/toolbar-button.tsx +2 -2
  69. package/src/components/toolbar.tsx +231 -65
  70. package/src/components/topbar/props-panel-toggle.tsx +31 -0
  71. package/src/contexts/props-panel.tsx +61 -0
  72. package/src/hooks/use-hot-reload.ts +11 -10
  73. package/.next/server/chunks/ssr/0kci_next_dist_esm_build_templates_app-page_0o.jt0x.js.map +0 -1
  74. package/.next/static/chunks/0l15-vshddb6t.css +0 -3
  75. package/.next/static/chunks/0o7gbl8p~22d1.js +0 -42
  76. package/src/components/toolbar/preview-props-editor.tsx +0 -102
  77. /package/.next/server/chunks/ssr/{[root-of-the-server]__0d.h7dp._.js.map → [root-of-the-server]__13sdo3r._.js.map} +0 -0
  78. /package/.next/static/{RcXkYVVpqLeGe0HK28j4o → c8jFxxcwBv319fqx4lN5X}/_buildManifest.js +0 -0
  79. /package/.next/static/{RcXkYVVpqLeGe0HK28j4o → c8jFxxcwBv319fqx4lN5X}/_clientMiddlewareManifest.js +0 -0
  80. /package/.next/static/{RcXkYVVpqLeGe0HK28j4o → c8jFxxcwBv319fqx4lN5X}/_ssgManifest.js +0 -0
package/.next/trace CHANGED
@@ -1 +1 @@
1
- [{"name":"generate-buildid","duration":221,"timestamp":139953394,"id":4,"parentId":1,"tags":{},"startTime":1783950783859,"traceId":"bec6b1bcdae73aeb"},{"name":"load-custom-routes","duration":401,"timestamp":139953727,"id":5,"parentId":1,"tags":{},"startTime":1783950783859,"traceId":"bec6b1bcdae73aeb"},{"name":"create-dist-dir","duration":847,"timestamp":139954157,"id":6,"parentId":1,"tags":{},"startTime":1783950783859,"traceId":"bec6b1bcdae73aeb"},{"name":"clean","duration":268,"timestamp":139955612,"id":7,"parentId":1,"tags":{},"startTime":1783950783861,"traceId":"bec6b1bcdae73aeb"},{"name":"discover-routes","duration":4599,"timestamp":139975246,"id":8,"parentId":1,"tags":{},"startTime":1783950783881,"traceId":"bec6b1bcdae73aeb"},{"name":"create-root-mapping","duration":81,"timestamp":139979893,"id":9,"parentId":1,"tags":{},"startTime":1783950783885,"traceId":"bec6b1bcdae73aeb"},{"name":"generate-route-types","duration":16467,"timestamp":139981793,"id":10,"parentId":1,"tags":{},"startTime":1783950783887,"traceId":"bec6b1bcdae73aeb"},{"name":"public-dir-conflict-check","duration":79,"timestamp":139998299,"id":11,"parentId":1,"tags":{},"startTime":1783950783904,"traceId":"bec6b1bcdae73aeb"},{"name":"generate-routes-manifest","duration":1910,"timestamp":139998455,"id":12,"parentId":1,"tags":{},"startTime":1783950783904,"traceId":"bec6b1bcdae73aeb"},{"name":"run-turbopack","duration":9858274,"timestamp":140004045,"id":14,"parentId":1,"tags":{},"startTime":1783950783909,"traceId":"bec6b1bcdae73aeb"},{"name":"turbopack-build-events","duration":58,"timestamp":140256679,"id":15,"parentId":1,"tags":{},"startTime":1783950784162,"traceId":"bec6b1bcdae73aeb"},{"name":"run-typescript","duration":7112640,"timestamp":149870742,"id":16,"parentId":1,"tags":{},"startTime":1783950793776,"traceId":"bec6b1bcdae73aeb"},{"name":"generate-required-server-files","duration":871,"timestamp":156983867,"id":18,"parentId":1,"tags":{},"startTime":1783950800889,"traceId":"bec6b1bcdae73aeb"},{"name":"check-static-error-page","duration":9070,"timestamp":157010537,"id":20,"parentId":19,"tags":{},"startTime":1783950800916,"traceId":"bec6b1bcdae73aeb"},{"name":"is-page-static","duration":256111,"timestamp":157020578,"id":27,"parentId":22,"tags":{},"startTime":1783950800926,"traceId":"bec6b1bcdae73aeb"},{"name":"check-page","duration":261206,"timestamp":157015576,"id":22,"parentId":19,"tags":{"page":"/_global-error"},"startTime":1783950800921,"traceId":"bec6b1bcdae73aeb"},{"name":"is-page-static","duration":326466,"timestamp":157026201,"id":29,"parentId":24,"tags":{},"startTime":1783950800931,"traceId":"bec6b1bcdae73aeb"},{"name":"check-page","duration":336880,"timestamp":157015838,"id":24,"parentId":19,"tags":{"page":"/"},"startTime":1783950800921,"traceId":"bec6b1bcdae73aeb"},{"name":"is-page-static","duration":378507,"timestamp":157020657,"id":28,"parentId":23,"tags":{},"startTime":1783950800926,"traceId":"bec6b1bcdae73aeb"},{"name":"check-page","duration":383487,"timestamp":157015741,"id":23,"parentId":19,"tags":{"page":"/favicon.ico"},"startTime":1783950800921,"traceId":"bec6b1bcdae73aeb"},{"name":"is-page-static","duration":388987,"timestamp":157020001,"id":26,"parentId":21,"tags":{},"startTime":1783950800925,"traceId":"bec6b1bcdae73aeb"},{"name":"check-page","duration":397612,"timestamp":157011454,"id":21,"parentId":19,"tags":{"page":"/_not-found"},"startTime":1783950800917,"traceId":"bec6b1bcdae73aeb"},{"name":"is-page-static","duration":614995,"timestamp":157028166,"id":30,"parentId":25,"tags":{},"startTime":1783950800933,"traceId":"bec6b1bcdae73aeb"},{"name":"check-page","duration":627325,"timestamp":157015905,"id":25,"parentId":19,"tags":{"page":"/preview/[...slug]"},"startTime":1783950800921,"traceId":"bec6b1bcdae73aeb"},{"name":"static-check","duration":634533,"timestamp":157008727,"id":19,"parentId":1,"tags":{},"startTime":1783950800914,"traceId":"bec6b1bcdae73aeb"},{"name":"write-routes-manifest","duration":290,"timestamp":157643884,"id":32,"parentId":1,"tags":{},"startTime":1783950801549,"traceId":"bec6b1bcdae73aeb"},{"name":"load-dotenv","duration":27,"timestamp":157650840,"id":35,"parentId":34,"tags":{},"startTime":1783950801556,"traceId":"bec6b1bcdae73aeb"},{"name":"run-export-path-map","duration":278,"timestamp":157652516,"id":36,"parentId":34,"tags":{},"startTime":1783950801558,"traceId":"bec6b1bcdae73aeb"},{"name":"next-export","duration":115233,"timestamp":157649987,"id":34,"parentId":1,"tags":{},"startTime":1783950801555,"traceId":"bec6b1bcdae73aeb"},{"name":"move-exported-app-global-error-","duration":397,"timestamp":157766261,"id":37,"parentId":33,"tags":{},"startTime":1783950801672,"traceId":"bec6b1bcdae73aeb"},{"name":"static-generation","duration":122084,"timestamp":157645868,"id":33,"parentId":1,"tags":{},"startTime":1783950801551,"traceId":"bec6b1bcdae73aeb"},{"name":"write-routes-manifest","duration":280,"timestamp":157768097,"id":38,"parentId":1,"tags":{},"startTime":1783950801673,"traceId":"bec6b1bcdae73aeb"},{"name":"print-tree-view","duration":2407,"timestamp":157774373,"id":39,"parentId":1,"tags":{},"startTime":1783950801680,"traceId":"bec6b1bcdae73aeb"},{"name":"write-route-bundle-stats","duration":4395,"timestamp":157776799,"id":40,"parentId":1,"tags":{},"startTime":1783950801682,"traceId":"bec6b1bcdae73aeb"},{"name":"telemetry-flush","duration":489125,"timestamp":157781207,"id":41,"parentId":1,"tags":{},"startTime":1783950801686,"traceId":"bec6b1bcdae73aeb"},{"name":"next-build","duration":18375952,"timestamp":139894407,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1783950783800,"traceId":"bec6b1bcdae73aeb"}]
1
+ [{"name":"generate-buildid","duration":270,"timestamp":258208200,"id":4,"parentId":1,"tags":{},"startTime":1784148191305,"traceId":"eaf7a751603db501"},{"name":"load-custom-routes","duration":353,"timestamp":258208592,"id":5,"parentId":1,"tags":{},"startTime":1784148191305,"traceId":"eaf7a751603db501"},{"name":"create-dist-dir","duration":859,"timestamp":258208975,"id":6,"parentId":1,"tags":{},"startTime":1784148191305,"traceId":"eaf7a751603db501"},{"name":"clean","duration":286,"timestamp":258210389,"id":7,"parentId":1,"tags":{},"startTime":1784148191307,"traceId":"eaf7a751603db501"},{"name":"discover-routes","duration":4422,"timestamp":258229762,"id":8,"parentId":1,"tags":{},"startTime":1784148191326,"traceId":"eaf7a751603db501"},{"name":"create-root-mapping","duration":49,"timestamp":258234223,"id":9,"parentId":1,"tags":{},"startTime":1784148191331,"traceId":"eaf7a751603db501"},{"name":"generate-route-types","duration":11878,"timestamp":258235806,"id":10,"parentId":1,"tags":{},"startTime":1784148191332,"traceId":"eaf7a751603db501"},{"name":"public-dir-conflict-check","duration":86,"timestamp":258247732,"id":11,"parentId":1,"tags":{},"startTime":1784148191344,"traceId":"eaf7a751603db501"},{"name":"generate-routes-manifest","duration":1932,"timestamp":258247906,"id":12,"parentId":1,"tags":{},"startTime":1784148191344,"traceId":"eaf7a751603db501"},{"name":"run-turbopack","duration":10172916,"timestamp":258253358,"id":14,"parentId":1,"tags":{},"startTime":1784148191350,"traceId":"eaf7a751603db501"},{"name":"turbopack-build-events","duration":66,"timestamp":258503061,"id":15,"parentId":1,"tags":{},"startTime":1784148191599,"traceId":"eaf7a751603db501"},{"name":"run-typescript","duration":7161506,"timestamp":268429324,"id":16,"parentId":1,"tags":{},"startTime":1784148201526,"traceId":"eaf7a751603db501"},{"name":"generate-required-server-files","duration":1008,"timestamp":275591420,"id":18,"parentId":1,"tags":{},"startTime":1784148208688,"traceId":"eaf7a751603db501"},{"name":"check-static-error-page","duration":7164,"timestamp":275618464,"id":20,"parentId":19,"tags":{},"startTime":1784148208715,"traceId":"eaf7a751603db501"},{"name":"is-page-static","duration":242700,"timestamp":275629594,"id":27,"parentId":22,"tags":{},"startTime":1784148208726,"traceId":"eaf7a751603db501"},{"name":"check-page","duration":248962,"timestamp":275623414,"id":22,"parentId":19,"tags":{"page":"/_global-error"},"startTime":1784148208720,"traceId":"eaf7a751603db501"},{"name":"is-page-static","duration":305982,"timestamp":275636131,"id":29,"parentId":24,"tags":{},"startTime":1784148208733,"traceId":"eaf7a751603db501"},{"name":"check-page","duration":318530,"timestamp":275623650,"id":24,"parentId":19,"tags":{"page":"/"},"startTime":1784148208720,"traceId":"eaf7a751603db501"},{"name":"is-page-static","duration":340523,"timestamp":275629333,"id":26,"parentId":21,"tags":{},"startTime":1784148208726,"traceId":"eaf7a751603db501"},{"name":"check-page","duration":350588,"timestamp":275619331,"id":21,"parentId":19,"tags":{"page":"/_not-found"},"startTime":1784148208716,"traceId":"eaf7a751603db501"},{"name":"is-page-static","duration":364896,"timestamp":275629652,"id":28,"parentId":23,"tags":{},"startTime":1784148208726,"traceId":"eaf7a751603db501"},{"name":"check-page","duration":375497,"timestamp":275623568,"id":23,"parentId":19,"tags":{"page":"/favicon.ico"},"startTime":1784148208720,"traceId":"eaf7a751603db501"},{"name":"is-page-static","duration":609740,"timestamp":275636190,"id":30,"parentId":25,"tags":{},"startTime":1784148208733,"traceId":"eaf7a751603db501"},{"name":"check-page","duration":622294,"timestamp":275623700,"id":25,"parentId":19,"tags":{"page":"/preview/[...slug]"},"startTime":1784148208720,"traceId":"eaf7a751603db501"},{"name":"static-check","duration":628364,"timestamp":275617670,"id":19,"parentId":1,"tags":{},"startTime":1784148208714,"traceId":"eaf7a751603db501"},{"name":"write-routes-manifest","duration":233,"timestamp":276246587,"id":32,"parentId":1,"tags":{},"startTime":1784148209343,"traceId":"eaf7a751603db501"},{"name":"load-dotenv","duration":27,"timestamp":276253142,"id":35,"parentId":34,"tags":{},"startTime":1784148209350,"traceId":"eaf7a751603db501"},{"name":"run-export-path-map","duration":261,"timestamp":276254701,"id":36,"parentId":34,"tags":{},"startTime":1784148209351,"traceId":"eaf7a751603db501"},{"name":"next-export","duration":108669,"timestamp":276252290,"id":34,"parentId":1,"tags":{},"startTime":1784148209349,"traceId":"eaf7a751603db501"},{"name":"move-exported-app-global-error-","duration":398,"timestamp":276361940,"id":37,"parentId":33,"tags":{},"startTime":1784148209458,"traceId":"eaf7a751603db501"},{"name":"static-generation","duration":115151,"timestamp":276248292,"id":33,"parentId":1,"tags":{},"startTime":1784148209345,"traceId":"eaf7a751603db501"},{"name":"write-routes-manifest","duration":201,"timestamp":276363554,"id":38,"parentId":1,"tags":{},"startTime":1784148209460,"traceId":"eaf7a751603db501"},{"name":"print-tree-view","duration":2251,"timestamp":276367689,"id":39,"parentId":1,"tags":{},"startTime":1784148209464,"traceId":"eaf7a751603db501"},{"name":"write-route-bundle-stats","duration":5341,"timestamp":276369954,"id":40,"parentId":1,"tags":{},"startTime":1784148209466,"traceId":"eaf7a751603db501"},{"name":"telemetry-flush","duration":490023,"timestamp":276375313,"id":41,"parentId":1,"tags":{},"startTime":1784148209472,"traceId":"eaf7a751603db501"},{"name":"next-build","duration":18713954,"timestamp":258151412,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1784148191248,"traceId":"eaf7a751603db501"}]
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-turbopack","duration":9858274,"timestamp":140004045,"id":14,"parentId":1,"tags":{},"startTime":1783950783909,"traceId":"bec6b1bcdae73aeb"},{"name":"turbopack-build-events","duration":58,"timestamp":140256679,"id":15,"parentId":1,"tags":{},"startTime":1783950784162,"traceId":"bec6b1bcdae73aeb"},{"name":"run-typescript","duration":7112640,"timestamp":149870742,"id":16,"parentId":1,"tags":{},"startTime":1783950793776,"traceId":"bec6b1bcdae73aeb"},{"name":"static-check","duration":634533,"timestamp":157008727,"id":19,"parentId":1,"tags":{},"startTime":1783950800914,"traceId":"bec6b1bcdae73aeb"},{"name":"static-generation","duration":122084,"timestamp":157645868,"id":33,"parentId":1,"tags":{},"startTime":1783950801551,"traceId":"bec6b1bcdae73aeb"},{"name":"telemetry-flush","duration":489125,"timestamp":157781207,"id":41,"parentId":1,"tags":{},"startTime":1783950801686,"traceId":"bec6b1bcdae73aeb"},{"name":"next-build","duration":18375952,"timestamp":139894407,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1783950783800,"traceId":"bec6b1bcdae73aeb"}]
1
+ [{"name":"run-turbopack","duration":10172916,"timestamp":258253358,"id":14,"parentId":1,"tags":{},"startTime":1784148191350,"traceId":"eaf7a751603db501"},{"name":"turbopack-build-events","duration":66,"timestamp":258503061,"id":15,"parentId":1,"tags":{},"startTime":1784148191599,"traceId":"eaf7a751603db501"},{"name":"run-typescript","duration":7161506,"timestamp":268429324,"id":16,"parentId":1,"tags":{},"startTime":1784148201526,"traceId":"eaf7a751603db501"},{"name":"static-check","duration":628364,"timestamp":275617670,"id":19,"parentId":1,"tags":{},"startTime":1784148208714,"traceId":"eaf7a751603db501"},{"name":"static-generation","duration":115151,"timestamp":276248292,"id":33,"parentId":1,"tags":{},"startTime":1784148209345,"traceId":"eaf7a751603db501"},{"name":"telemetry-flush","duration":490023,"timestamp":276375313,"id":41,"parentId":1,"tags":{},"startTime":1784148209472,"traceId":"eaf7a751603db501"},{"name":"next-build","duration":18713954,"timestamp":258151412,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1784148191248,"traceId":"eaf7a751603db501"}]
@@ -3,7 +3,7 @@
3
3
 
4
4
  type AppRoutes = "/" | "/preview/[...slug]"
5
5
  type PageRoutes = never
6
- type LayoutRoutes = "/"
6
+ type LayoutRoutes = "/" | "/preview"
7
7
  type RedirectRoutes = never
8
8
  type RewriteRoutes = never
9
9
  type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRoutes
@@ -11,6 +11,7 @@ type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRo
11
11
 
12
12
  interface ParamMap {
13
13
  "/": {}
14
+ "/preview": {}
14
15
  "/preview/[...slug]": { "slug": string[]; }
15
16
  }
16
17
 
@@ -19,6 +20,7 @@ export type ParamsOf<Route extends Routes> = ParamMap[Route]
19
20
 
20
21
  interface LayoutSlotMap {
21
22
  "/": never
23
+ "/preview": never
22
24
  }
23
25
 
24
26
 
@@ -68,3 +68,12 @@ type LayoutConfig<Route extends LayoutRoutes = LayoutRoutes> = {
68
68
  // @ts-ignore
69
69
  type __Unused = __Check
70
70
  }
71
+
72
+ // Validate ../../src/app/preview/layout.tsx
73
+ {
74
+ type __IsExpected<Specific extends LayoutConfig<"/preview">> = Specific
75
+ const handler = {} as typeof import("../../src/app/preview/layout.js")
76
+ type __Check = __IsExpected<typeof handler>
77
+ // @ts-ignore
78
+ type __Unused = __Check
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-email/ui",
3
- "version": "6.8.0",
3
+ "version": "6.9.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "main": "./index.mjs",
6
6
  "files": [
@@ -73,7 +73,7 @@
73
73
  "typescript": "5.9.3",
74
74
  "use-debounce": "10.1.1",
75
75
  "zod": "4.3.6",
76
- "react-email": "6.8.0"
76
+ "react-email": "6.9.0"
77
77
  },
78
78
  "license": "MIT",
79
79
  "repository": {
@@ -7,6 +7,7 @@ import { Toaster } from 'sonner';
7
7
  import { useDebouncedCallback } from 'use-debounce';
8
8
  import { Topbar } from '../../../components';
9
9
  import { CodeContainer } from '../../../components/code-container';
10
+ import { PreviewPropsPanel } from '../../../components/preview-props-panel';
10
11
  import {
11
12
  makeIframeDocumentBubbleEvents,
12
13
  ResizableWrapper,
@@ -16,8 +17,10 @@ import { useToolbarState } from '../../../components/toolbar';
16
17
  import { Tooltip } from '../../../components/tooltip';
17
18
  import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
18
19
  import { EmulatedDarkModeToggle } from '../../../components/topbar/emulated-dark-mode-toggle';
20
+ import { PropsPanelToggle } from '../../../components/topbar/props-panel-toggle';
19
21
  import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
20
22
  import { usePreviewContext } from '../../../contexts/preview';
23
+ import { usePropsPanel } from '../../../contexts/props-panel';
21
24
  import { useClampedState } from '../../../hooks/use-clamped-state';
22
25
  import { cn } from '../../../utils';
23
26
  import { inferEmailTitle } from '../../../utils/infer-email-title';
@@ -104,6 +107,9 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
104
107
 
105
108
  const { toggled: toolbarToggled } = useToolbarState();
106
109
 
110
+ const { open: propsPanelOpen, setOpen: handlePropsPanelChange } =
111
+ usePropsPanel();
112
+
107
113
  return (
108
114
  <>
109
115
  <Topbar emailTitle={emailTitle}>
@@ -137,6 +143,12 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
137
143
  activeView={activeView}
138
144
  setActiveView={handleViewChange}
139
145
  />
146
+ {hasRenderingMetadata && !isRawHtmlEmail ? (
147
+ <PropsPanelToggle
148
+ onChange={handlePropsPanelChange}
149
+ open={propsPanelOpen}
150
+ />
151
+ ) : null}
140
152
  {hasRenderingMetadata ? (
141
153
  <div className="flex justify-end">
142
154
  <Send
@@ -150,121 +162,131 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
150
162
  </Topbar>
151
163
 
152
164
  <div
153
- {...props}
154
165
  className={cn(
155
- 'h-[calc(100%-3.5rem-2.375rem)] will-change-[height] flex p-4 transition-[height] duration-300 relative',
156
- activeView === 'preview' && 'bg-gray-200',
157
- activeView === 'preview' && isDarkModeEnabled && 'bg-gray-400',
166
+ 'flex h-[calc(100%-3.5rem-2.375rem)] will-change-[height] transition-[height] duration-300',
158
167
  toolbarToggled && 'h-[calc(100%-3.5rem-13rem)]',
159
- className,
160
168
  )}
161
- ref={(element) => {
162
- const observer = new ResizeObserver((entry) => {
163
- const [elementEntry] = entry;
164
- if (elementEntry) {
165
- setMaxWidth(elementEntry.contentRect.width);
166
- setMaxHeight(elementEntry.contentRect.height);
169
+ >
170
+ <div
171
+ {...props}
172
+ className={cn(
173
+ 'relative flex min-w-0 grow p-4',
174
+ activeView === 'preview' && 'bg-gray-200',
175
+ activeView === 'preview' && isDarkModeEnabled && 'bg-gray-400',
176
+ className,
177
+ )}
178
+ ref={(element) => {
179
+ const observer = new ResizeObserver((entry) => {
180
+ const [elementEntry] = entry;
181
+ if (elementEntry) {
182
+ setMaxWidth(elementEntry.contentRect.width);
183
+ setMaxHeight(elementEntry.contentRect.height);
184
+ }
185
+ });
186
+
187
+ if (element) {
188
+ observer.observe(element);
167
189
  }
168
- });
169
190
 
170
- if (element) {
171
- observer.observe(element);
172
- }
191
+ return () => {
192
+ observer.disconnect();
193
+ };
194
+ }}
195
+ >
196
+ {hasErrors ? <ErrorOverlay error={renderingResult.error} /> : null}
173
197
 
174
- return () => {
175
- observer.disconnect();
176
- };
177
- }}
178
- >
179
- {hasErrors ? <ErrorOverlay error={renderingResult.error} /> : null}
180
-
181
- {hasRenderingMetadata ? (
182
- <>
183
- {activeView === 'preview' && (
184
- <ResizableWrapper
185
- minHeight={minHeight}
186
- minWidth={minWidth}
187
- maxHeight={maxHeight}
188
- maxWidth={maxWidth}
189
- height={height}
190
- onResizeEnd={() => {
191
- handleSaveViewSize();
192
- }}
193
- onResize={(value, direction) => {
194
- const isHorizontal =
195
- direction === 'east' || direction === 'west';
196
- if (isHorizontal) {
197
- setWidth(Math.round(value));
198
- } else {
199
- setHeight(Math.round(value));
200
- }
201
- }}
202
- width={width}
203
- >
204
- <EmailFrame
205
- className="max-h-full rounded-lg bg-white [color-scheme:auto]"
206
- darkMode={isDarkModeEnabled}
207
- markup={renderedEmailMetadata.markup}
208
- width={width}
198
+ {hasRenderingMetadata ? (
199
+ <>
200
+ {activeView === 'preview' && (
201
+ <ResizableWrapper
202
+ minHeight={minHeight}
203
+ minWidth={minWidth}
204
+ maxHeight={maxHeight}
205
+ maxWidth={maxWidth}
209
206
  height={height}
210
- title={emailTitle}
211
- ref={(iframe) => {
212
- if (!iframe) return;
213
-
214
- return makeIframeDocumentBubbleEvents(iframe);
207
+ onResizeEnd={() => {
208
+ handleSaveViewSize();
209
+ }}
210
+ onResize={(value, direction) => {
211
+ const isHorizontal =
212
+ direction === 'east' || direction === 'west';
213
+ if (isHorizontal) {
214
+ setWidth(Math.round(value));
215
+ } else {
216
+ setHeight(Math.round(value));
217
+ }
215
218
  }}
216
- />
217
- </ResizableWrapper>
218
- )}
219
+ width={width}
220
+ >
221
+ <EmailFrame
222
+ className="max-h-full rounded-lg bg-white [color-scheme:auto]"
223
+ darkMode={isDarkModeEnabled}
224
+ markup={renderedEmailMetadata.markup}
225
+ width={width}
226
+ height={height}
227
+ title={emailTitle}
228
+ ref={(iframe) => {
229
+ if (!iframe) return;
230
+
231
+ return makeIframeDocumentBubbleEvents(iframe);
232
+ }}
233
+ />
234
+ </ResizableWrapper>
235
+ )}
219
236
 
220
- {activeView === 'source' && (
221
- <div className="h-full w-full">
222
- <div className="m-auto h-full flex max-w-3xl p-6">
223
- <Tooltip.Provider>
224
- <CodeContainer
225
- activeLang={activeLang}
226
- basename={renderedEmailMetadata.basename}
227
- markups={
228
- isRawHtmlEmail
229
- ? [
230
- {
231
- language: 'html',
232
- extension: 'html',
233
- content: renderedEmailMetadata.prettyMarkup,
234
- },
235
- {
236
- language: 'markdown',
237
- extension: 'md',
238
- content: renderedEmailMetadata.plainText,
239
- },
240
- ]
241
- : [
242
- {
243
- language: 'tsx',
244
- extension: renderedEmailMetadata.extname,
245
- content: renderedEmailMetadata.reactMarkup,
246
- },
247
- {
248
- language: 'html',
249
- content: renderedEmailMetadata.prettyMarkup,
250
- },
251
- {
252
- language: 'markdown',
253
- extension: 'md',
254
- content: renderedEmailMetadata.plainText,
255
- },
256
- ]
257
- }
258
- setActiveLang={handleLangChange}
259
- />
260
- </Tooltip.Provider>
237
+ {activeView === 'source' && (
238
+ <div className="h-full w-full">
239
+ <div className="m-auto h-full flex max-w-3xl p-6">
240
+ <Tooltip.Provider>
241
+ <CodeContainer
242
+ activeLang={activeLang}
243
+ basename={renderedEmailMetadata.basename}
244
+ markups={
245
+ isRawHtmlEmail
246
+ ? [
247
+ {
248
+ language: 'html',
249
+ extension: 'html',
250
+ content: renderedEmailMetadata.prettyMarkup,
251
+ },
252
+ {
253
+ language: 'markdown',
254
+ extension: 'md',
255
+ content: renderedEmailMetadata.plainText,
256
+ },
257
+ ]
258
+ : [
259
+ {
260
+ language: 'tsx',
261
+ extension: renderedEmailMetadata.extname,
262
+ content: renderedEmailMetadata.reactMarkup,
263
+ },
264
+ {
265
+ language: 'html',
266
+ content: renderedEmailMetadata.prettyMarkup,
267
+ },
268
+ {
269
+ language: 'markdown',
270
+ extension: 'md',
271
+ content: renderedEmailMetadata.plainText,
272
+ },
273
+ ]
274
+ }
275
+ setActiveLang={handleLangChange}
276
+ />
277
+ </Tooltip.Provider>
278
+ </div>
261
279
  </div>
262
- </div>
263
- )}
264
- </>
265
- ) : null}
280
+ )}
281
+ </>
282
+ ) : null}
266
283
 
267
- <Toaster />
284
+ <Toaster />
285
+ </div>
286
+
287
+ {hasRenderingMetadata && !isRawHtmlEmail ? (
288
+ <PreviewPropsPanel open={propsPanelOpen} />
289
+ ) : null}
268
290
  </div>
269
291
  </>
270
292
  );
@@ -0,0 +1,9 @@
1
+ import { PropsPanelProvider } from '../../contexts/props-panel';
2
+
3
+ export default function PreviewLayout({
4
+ children,
5
+ }: {
6
+ children: React.ReactNode;
7
+ }) {
8
+ return <PropsPanelProvider>{children}</PropsPanelProvider>;
9
+ }
@@ -13,7 +13,7 @@ interface CodeProps {
13
13
  language?: Language;
14
14
  }
15
15
 
16
- const theme = {
16
+ export const codeTheme = {
17
17
  plain: {
18
18
  color: '#EDEDEF',
19
19
  fontSize: 13,
@@ -93,7 +93,7 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
93
93
  const value = children.trim();
94
94
 
95
95
  return (
96
- <Highlight code={value} language={language} theme={theme}>
96
+ <Highlight code={value} language={language} theme={codeTheme}>
97
97
  {({ tokens, getLineProps, getTokenProps }) => (
98
98
  <>
99
99
  <div
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import type { IconElement, IconProps } from './icon-base';
3
+ import { IconBase } from './icon-base';
4
+
5
+ export const IconPanelRight = React.forwardRef<
6
+ IconElement,
7
+ Readonly<IconProps>
8
+ >(({ ...props }, forwardedRef) => (
9
+ <IconBase ref={forwardedRef} {...props}>
10
+ <path
11
+ clipRule="evenodd"
12
+ d="M5 2.25H19C20.5188 2.25 21.75 3.48122 21.75 5V19C21.75 20.5188 20.5188 21.75 19 21.75L5 21.75C3.48122 21.75 2.25 20.5188 2.25 19L2.25 5C2.25 3.48122 3.48122 2.25 5 2.25ZM5 20.25C4.30964 20.25 3.75 19.6904 3.75 19L3.75 5C3.75 4.30964 4.30964 3.75 5 3.75L14.25 3.75L14.25 20.25L5 20.25ZM15.75 20.25L15.75 3.75L19 3.75C19.6904 3.75 20.25 4.30965 20.25 5L20.25 19C20.25 19.6904 19.6904 20.25 19 20.25H15.75Z"
13
+ fill="currentColor"
14
+ fillRule="evenodd"
15
+ />
16
+ </IconBase>
17
+ ));
18
+
19
+ IconPanelRight.displayName = 'IconPanelRight';
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import { Highlight } from 'prism-react-renderer';
4
+ import { cn } from '../utils';
5
+ import { codeTheme } from './code';
6
+
7
+ interface JsonEditorProps {
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ disabled?: boolean;
11
+ className?: string;
12
+ 'aria-label': string;
13
+ 'aria-invalid'?: boolean;
14
+ }
15
+
16
+ // Text metrics must match between the highlighted <pre> and the transparent
17
+ // <textarea> on top of it, or the caret drifts from the characters.
18
+ const sharedTextClasses =
19
+ 'whitespace-pre-wrap break-words p-2 text-[13px] leading-5 font-[MonoLisa,Menlo,monospace]';
20
+
21
+ /**
22
+ * Syntax-highlighted JSON editing surface: a highlighted render with an
23
+ * invisible textarea overlaid, so it looks like the source view but types
24
+ * like a plain input.
25
+ */
26
+ export const JsonEditor = ({
27
+ value,
28
+ onChange,
29
+ disabled = false,
30
+ className,
31
+ ...ariaProps
32
+ }: JsonEditorProps) => {
33
+ return (
34
+ <div
35
+ className={cn(
36
+ 'relative min-h-40 rounded-md border border-slate-6 transition-colors',
37
+ 'focus-within:border-slate-8',
38
+ disabled && 'opacity-60',
39
+ className,
40
+ )}
41
+ >
42
+ <Highlight code={value} language="json" theme={codeTheme}>
43
+ {({ tokens, getLineProps, getTokenProps }) => (
44
+ <pre aria-hidden className={sharedTextClasses}>
45
+ {tokens.map((line, lineIndex) => (
46
+ <div {...getLineProps({ line })} key={lineIndex}>
47
+ {line.map((token, tokenIndex) => (
48
+ <span {...getTokenProps({ token })} key={tokenIndex} />
49
+ ))}
50
+ {line.length === 1 && line[0]?.empty ? '​' : null}
51
+ </div>
52
+ ))}
53
+ </pre>
54
+ )}
55
+ </Highlight>
56
+ <textarea
57
+ {...ariaProps}
58
+ className={cn(
59
+ sharedTextClasses,
60
+ 'absolute inset-0 h-full w-full resize-none overflow-hidden bg-transparent text-transparent caret-slate-12 outline-none',
61
+ )}
62
+ disabled={disabled}
63
+ onChange={(event) => {
64
+ onChange(event.currentTarget.value);
65
+ }}
66
+ spellCheck={false}
67
+ value={value}
68
+ />
69
+ </div>
70
+ );
71
+ };
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useDebouncedCallback } from 'use-debounce';
5
+ import { isBuilding } from '../app/env';
6
+ import { usePreviewContext } from '../contexts/preview';
7
+ import { cn } from '../utils';
8
+ import { JsonEditor } from './json-editor';
9
+
10
+ export const PreviewPropsEditor = () => {
11
+ const {
12
+ renderedEmailMetadata,
13
+ previewPropsOverride,
14
+ setPreviewPropsOverride,
15
+ } = usePreviewContext();
16
+
17
+ const renderedPropsJson = JSON.stringify(
18
+ renderedEmailMetadata?.previewProps ?? {},
19
+ null,
20
+ 2,
21
+ );
22
+
23
+ // The applied override is the source of truth for what renders; the draft
24
+ // only preserves the user's raw text, which can be momentarily invalid
25
+ // JSON or formatted differently than the applied value.
26
+ const [draft, setDraft] = React.useState<string | undefined>(undefined);
27
+ const [parseError, setParseError] = React.useState<string | undefined>(
28
+ undefined,
29
+ );
30
+
31
+ const hasOverride = previewPropsOverride !== undefined;
32
+
33
+ // Applying is debounced because every applied value costs a server render:
34
+ // per-keystroke applies queue up behind each other and permanently cache
35
+ // every intermediate state.
36
+ const applyValue = useDebouncedCallback((newValue: string) => {
37
+ try {
38
+ const parsed = JSON.parse(newValue) as unknown;
39
+ if (
40
+ parsed === null ||
41
+ typeof parsed !== 'object' ||
42
+ Array.isArray(parsed)
43
+ ) {
44
+ setParseError('Props must be a JSON object');
45
+ return;
46
+ }
47
+ setParseError(undefined);
48
+ setPreviewPropsOverride(parsed as Record<string, unknown>);
49
+ } catch (exception) {
50
+ setParseError((exception as Error).message);
51
+ }
52
+ }, 400);
53
+
54
+ const handleChange = (newValue: string) => {
55
+ setDraft(newValue);
56
+ applyValue(newValue);
57
+ };
58
+
59
+ return (
60
+ <div className="flex h-full flex-col gap-3">
61
+ <div className="flex items-center justify-between gap-3">
62
+ {isBuilding ? (
63
+ <p className="text-slate-11">
64
+ Read-only. This preview was rendered at build time.
65
+ </p>
66
+ ) : (
67
+ <button
68
+ className={cn(
69
+ 'ml-auto shrink-0 rounded-md border border-slate-6 px-2 py-1 text-slate-11 transition-colors',
70
+ 'hover:border-slate-8 hover:text-slate-12',
71
+ 'disabled:opacity-40 disabled:hover:border-slate-6 disabled:hover:text-slate-11',
72
+ )}
73
+ disabled={!hasOverride && draft === undefined}
74
+ onClick={() => {
75
+ applyValue.cancel();
76
+ setDraft(undefined);
77
+ setParseError(undefined);
78
+ setPreviewPropsOverride(undefined);
79
+ }}
80
+ type="button"
81
+ >
82
+ Reset to defaults
83
+ </button>
84
+ )}
85
+ </div>
86
+ <JsonEditor
87
+ aria-invalid={parseError !== undefined}
88
+ aria-label="Preview props JSON"
89
+ className={cn(
90
+ parseError !== undefined && 'border-red-9 focus-within:border-red-9',
91
+ )}
92
+ disabled={isBuilding}
93
+ onChange={handleChange}
94
+ value={draft ?? renderedPropsJson}
95
+ />
96
+ {parseError !== undefined ? (
97
+ <p className="pb-3 text-red-11">{parseError}</p>
98
+ ) : null}
99
+ </div>
100
+ );
101
+ };
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { usePreviewContext } from '../contexts/preview';
4
+ import { usePropsPanel } from '../contexts/props-panel';
5
+ import { cn } from '../utils';
6
+ import { PreviewPropsEditor } from './preview-props-editor';
7
+
8
+ export const PreviewPropsPanel = ({ open }: { open: boolean }) => {
9
+ const { animated } = usePropsPanel();
10
+ const { emailSlug } = usePreviewContext();
11
+
12
+ return (
13
+ <aside
14
+ className={cn(
15
+ 'shrink-0 overflow-hidden border-l border-slate-6',
16
+ animated && 'transition-[width] duration-200 will-change-[width]',
17
+ open ? 'w-72' : 'w-0 border-l-0',
18
+ )}
19
+ inert={!open}
20
+ >
21
+ <div className="flex h-full w-72 flex-col">
22
+ <div className="flex h-10 shrink-0 items-center border-b border-slate-6 px-4 font-medium text-slate-12 text-xs">
23
+ Props
24
+ </div>
25
+ <div className="grow overflow-y-auto px-4 pt-3 text-slate-11 text-xs">
26
+ <PreviewPropsEditor key={emailSlug} />
27
+ </div>
28
+ </div>
29
+ </aside>
30
+ );
31
+ };