reflex 0.7.14a6__py3-none-any.whl → 0.8.0a2__py3-none-any.whl

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.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (211) hide show
  1. reflex/.templates/jinja/app/rxconfig.py.jinja2 +4 -1
  2. reflex/.templates/jinja/web/package.json.jinja2 +1 -1
  3. reflex/.templates/jinja/web/pages/_app.js.jinja2 +16 -10
  4. reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
  5. reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
  6. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
  7. reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
  8. reflex/.templates/web/app/entry.client.js +8 -0
  9. reflex/.templates/web/app/routes.js +10 -0
  10. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
  11. reflex/.templates/web/postcss.config.js +1 -1
  12. reflex/.templates/web/react-router.config.js +6 -0
  13. reflex/.templates/web/utils/client_side_routing.js +21 -19
  14. reflex/.templates/web/utils/react-theme.js +92 -0
  15. reflex/.templates/web/utils/state.js +160 -67
  16. reflex/.templates/web/vite.config.js +32 -0
  17. reflex/__init__.py +1 -6
  18. reflex/__init__.pyi +0 -4
  19. reflex/app.py +53 -116
  20. reflex/base.py +1 -87
  21. reflex/compiler/compiler.py +41 -8
  22. reflex/compiler/templates.py +3 -3
  23. reflex/compiler/utils.py +73 -33
  24. reflex/components/__init__.py +0 -2
  25. reflex/components/__init__.pyi +0 -3
  26. reflex/components/base/__init__.py +1 -5
  27. reflex/components/base/__init__.pyi +4 -6
  28. reflex/components/base/app_wrap.pyi +5 -4
  29. reflex/components/base/body.pyi +5 -4
  30. reflex/components/base/document.py +18 -14
  31. reflex/components/base/document.pyi +83 -27
  32. reflex/components/base/error_boundary.pyi +5 -4
  33. reflex/components/base/fragment.pyi +5 -4
  34. reflex/components/base/link.pyi +9 -7
  35. reflex/components/base/meta.pyi +17 -13
  36. reflex/components/base/script.py +60 -58
  37. reflex/components/base/script.pyi +246 -31
  38. reflex/components/base/strict_mode.pyi +5 -4
  39. reflex/components/component.py +146 -217
  40. reflex/components/core/__init__.py +1 -0
  41. reflex/components/core/__init__.pyi +1 -0
  42. reflex/components/core/auto_scroll.pyi +5 -4
  43. reflex/components/core/banner.pyi +25 -19
  44. reflex/components/core/client_side_routing.py +7 -6
  45. reflex/components/core/client_side_routing.pyi +6 -56
  46. reflex/components/core/clipboard.pyi +5 -4
  47. reflex/components/core/debounce.py +1 -0
  48. reflex/components/core/debounce.pyi +5 -4
  49. reflex/components/core/foreach.py +3 -2
  50. reflex/components/core/helmet.py +14 -0
  51. reflex/components/{next/base.pyi → core/helmet.pyi} +10 -7
  52. reflex/components/core/html.pyi +5 -4
  53. reflex/components/core/sticky.pyi +17 -13
  54. reflex/components/core/upload.py +2 -1
  55. reflex/components/core/upload.pyi +21 -16
  56. reflex/components/datadisplay/code.py +2 -72
  57. reflex/components/datadisplay/code.pyi +9 -10
  58. reflex/components/datadisplay/dataeditor.pyi +11 -6
  59. reflex/components/datadisplay/shiki_code_block.pyi +13 -10
  60. reflex/components/dynamic.py +5 -5
  61. reflex/components/el/element.pyi +5 -4
  62. reflex/components/el/elements/base.pyi +5 -4
  63. reflex/components/el/elements/forms.pyi +69 -52
  64. reflex/components/el/elements/inline.pyi +113 -85
  65. reflex/components/el/elements/media.pyi +105 -79
  66. reflex/components/el/elements/metadata.pyi +25 -19
  67. reflex/components/el/elements/other.pyi +29 -22
  68. reflex/components/el/elements/scripts.pyi +13 -10
  69. reflex/components/el/elements/sectioning.pyi +61 -46
  70. reflex/components/el/elements/tables.pyi +41 -31
  71. reflex/components/el/elements/typography.pyi +61 -46
  72. reflex/components/field.py +175 -0
  73. reflex/components/gridjs/datatable.py +2 -2
  74. reflex/components/gridjs/datatable.pyi +11 -9
  75. reflex/components/lucide/icon.py +6 -2
  76. reflex/components/lucide/icon.pyi +15 -10
  77. reflex/components/markdown/markdown.pyi +5 -4
  78. reflex/components/moment/moment.pyi +5 -4
  79. reflex/components/plotly/plotly.pyi +19 -10
  80. reflex/components/props.py +376 -27
  81. reflex/components/radix/primitives/accordion.py +8 -1
  82. reflex/components/radix/primitives/accordion.pyi +29 -22
  83. reflex/components/radix/primitives/base.pyi +9 -7
  84. reflex/components/radix/primitives/drawer.pyi +45 -34
  85. reflex/components/radix/primitives/form.pyi +41 -31
  86. reflex/components/radix/primitives/progress.pyi +21 -16
  87. reflex/components/radix/primitives/slider.pyi +21 -16
  88. reflex/components/radix/themes/base.py +3 -3
  89. reflex/components/radix/themes/base.pyi +33 -25
  90. reflex/components/radix/themes/color_mode.pyi +13 -10
  91. reflex/components/radix/themes/components/alert_dialog.pyi +29 -22
  92. reflex/components/radix/themes/components/aspect_ratio.pyi +5 -4
  93. reflex/components/radix/themes/components/avatar.pyi +5 -4
  94. reflex/components/radix/themes/components/badge.pyi +5 -4
  95. reflex/components/radix/themes/components/button.pyi +5 -4
  96. reflex/components/radix/themes/components/callout.pyi +21 -16
  97. reflex/components/radix/themes/components/card.pyi +5 -4
  98. reflex/components/radix/themes/components/checkbox.pyi +13 -10
  99. reflex/components/radix/themes/components/checkbox_cards.pyi +9 -7
  100. reflex/components/radix/themes/components/checkbox_group.pyi +9 -7
  101. reflex/components/radix/themes/components/context_menu.pyi +53 -40
  102. reflex/components/radix/themes/components/data_list.pyi +17 -13
  103. reflex/components/radix/themes/components/dialog.pyi +29 -22
  104. reflex/components/radix/themes/components/dropdown_menu.pyi +33 -25
  105. reflex/components/radix/themes/components/hover_card.pyi +17 -13
  106. reflex/components/radix/themes/components/icon_button.pyi +5 -4
  107. reflex/components/radix/themes/components/inset.pyi +5 -4
  108. reflex/components/radix/themes/components/popover.pyi +17 -13
  109. reflex/components/radix/themes/components/progress.pyi +5 -4
  110. reflex/components/radix/themes/components/radio.pyi +5 -4
  111. reflex/components/radix/themes/components/radio_cards.pyi +9 -7
  112. reflex/components/radix/themes/components/radio_group.pyi +17 -13
  113. reflex/components/radix/themes/components/scroll_area.pyi +5 -4
  114. reflex/components/radix/themes/components/segmented_control.pyi +9 -7
  115. reflex/components/radix/themes/components/select.pyi +37 -28
  116. reflex/components/radix/themes/components/separator.pyi +5 -4
  117. reflex/components/radix/themes/components/skeleton.pyi +5 -4
  118. reflex/components/radix/themes/components/slider.pyi +5 -4
  119. reflex/components/radix/themes/components/spinner.pyi +5 -4
  120. reflex/components/radix/themes/components/switch.pyi +5 -4
  121. reflex/components/radix/themes/components/table.pyi +29 -22
  122. reflex/components/radix/themes/components/tabs.pyi +21 -16
  123. reflex/components/radix/themes/components/text_area.pyi +5 -4
  124. reflex/components/radix/themes/components/text_field.pyi +13 -10
  125. reflex/components/radix/themes/components/tooltip.pyi +5 -4
  126. reflex/components/radix/themes/layout/base.pyi +5 -4
  127. reflex/components/radix/themes/layout/box.pyi +5 -4
  128. reflex/components/radix/themes/layout/center.pyi +5 -4
  129. reflex/components/radix/themes/layout/container.pyi +5 -4
  130. reflex/components/radix/themes/layout/flex.pyi +5 -4
  131. reflex/components/radix/themes/layout/grid.pyi +5 -4
  132. reflex/components/radix/themes/layout/list.pyi +21 -16
  133. reflex/components/radix/themes/layout/section.pyi +5 -4
  134. reflex/components/radix/themes/layout/spacer.pyi +5 -4
  135. reflex/components/radix/themes/layout/stack.pyi +13 -10
  136. reflex/components/radix/themes/typography/blockquote.pyi +5 -4
  137. reflex/components/radix/themes/typography/code.pyi +5 -4
  138. reflex/components/radix/themes/typography/heading.pyi +5 -4
  139. reflex/components/radix/themes/typography/link.py +46 -11
  140. reflex/components/radix/themes/typography/link.pyi +311 -6
  141. reflex/components/radix/themes/typography/text.pyi +29 -22
  142. reflex/components/react_player/audio.pyi +5 -4
  143. reflex/components/react_player/react_player.pyi +5 -4
  144. reflex/components/react_player/video.pyi +5 -4
  145. reflex/components/recharts/cartesian.py +2 -1
  146. reflex/components/recharts/cartesian.pyi +65 -46
  147. reflex/components/recharts/charts.py +4 -2
  148. reflex/components/recharts/charts.pyi +36 -24
  149. reflex/components/recharts/general.pyi +24 -18
  150. reflex/components/recharts/polar.py +8 -4
  151. reflex/components/recharts/polar.pyi +16 -10
  152. reflex/components/recharts/recharts.pyi +9 -7
  153. reflex/components/sonner/toast.py +2 -2
  154. reflex/components/sonner/toast.pyi +10 -8
  155. reflex/config.py +3 -77
  156. reflex/constants/__init__.py +2 -2
  157. reflex/constants/base.py +28 -11
  158. reflex/constants/compiler.py +5 -3
  159. reflex/constants/event.py +1 -0
  160. reflex/constants/installer.py +22 -16
  161. reflex/constants/route.py +19 -7
  162. reflex/constants/state.py +2 -0
  163. reflex/custom_components/custom_components.py +0 -14
  164. reflex/environment.py +1 -1
  165. reflex/event.py +178 -81
  166. reflex/experimental/__init__.py +0 -30
  167. reflex/istate/proxy.py +5 -3
  168. reflex/page.py +0 -27
  169. reflex/plugins/__init__.py +3 -2
  170. reflex/plugins/base.py +5 -1
  171. reflex/plugins/shared_tailwind.py +158 -0
  172. reflex/plugins/sitemap.py +206 -0
  173. reflex/plugins/tailwind_v3.py +13 -106
  174. reflex/plugins/tailwind_v4.py +15 -108
  175. reflex/reflex.py +1 -0
  176. reflex/route.py +15 -21
  177. reflex/state.py +134 -140
  178. reflex/testing.py +58 -10
  179. reflex/utils/build.py +38 -82
  180. reflex/utils/exec.py +59 -161
  181. reflex/utils/export.py +2 -2
  182. reflex/utils/imports.py +0 -4
  183. reflex/utils/misc.py +28 -0
  184. reflex/utils/prerequisites.py +65 -62
  185. reflex/utils/processes.py +8 -7
  186. reflex/utils/pyi_generator.py +21 -9
  187. reflex/utils/serializers.py +14 -1
  188. reflex/utils/types.py +196 -61
  189. reflex/vars/__init__.py +2 -0
  190. reflex/vars/base.py +367 -134
  191. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/METADATA +12 -5
  192. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/RECORD +195 -202
  193. reflex/.templates/web/next.config.js +0 -7
  194. reflex/components/base/head.py +0 -20
  195. reflex/components/base/head.pyi +0 -116
  196. reflex/components/next/__init__.py +0 -10
  197. reflex/components/next/base.py +0 -7
  198. reflex/components/next/image.py +0 -117
  199. reflex/components/next/image.pyi +0 -94
  200. reflex/components/next/link.py +0 -20
  201. reflex/components/next/link.pyi +0 -67
  202. reflex/components/next/video.py +0 -38
  203. reflex/components/next/video.pyi +0 -68
  204. reflex/components/suneditor/__init__.py +0 -5
  205. reflex/components/suneditor/editor.py +0 -269
  206. reflex/components/suneditor/editor.pyi +0 -199
  207. reflex/experimental/layout.py +0 -254
  208. reflex/experimental/layout.pyi +0 -814
  209. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/WHEEL +0 -0
  210. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/entry_points.txt +0 -0
  211. {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -2,5 +2,8 @@ import reflex as rx
2
2
 
3
3
  config = rx.Config(
4
4
  app_name="{{ app_name }}",
5
- plugins=[rx.plugins.TailwindV3Plugin()],
5
+ plugins=[
6
+ rx.plugins.SitemapPlugin(),
7
+ rx.plugins.TailwindV4Plugin(),
8
+ ],
6
9
  )
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "reflex",
3
+ "type": "module",
3
4
  "scripts": {
4
5
  "dev": "{{ scripts.dev }}",
5
6
  "export": "{{ scripts.export }}",
6
- "export-sitemap": "{{ scripts.export_sitemap }}",
7
7
  "prod": "{{ scripts.prod }}"
8
8
  },
9
9
  "dependencies": {
@@ -6,8 +6,10 @@ import '$/styles/__reflex_global_styles.css'
6
6
  {% endblock %}
7
7
 
8
8
  {% block declaration %}
9
- import { EventLoopProvider, StateProvider, defaultColorMode } from "$/utils/context.js";
10
- import { ThemeProvider } from 'next-themes'
9
+ import { EventLoopProvider, StateProvider, defaultColorMode } from "$/utils/context";
10
+ import { ThemeProvider } from '$/utils/react-theme';
11
+ import { Layout as AppLayout } from './_document';
12
+ import { Outlet } from 'react-router';
11
13
  {% for library_alias, library_path in window_libraries %}
12
14
  import * as {{library_alias}} from "{{library_path}}";
13
15
  {% endfor %}
@@ -26,8 +28,9 @@ function AppWrap({children}) {
26
28
  )
27
29
  }
28
30
 
29
- export default function MyApp({ Component, pageProps }) {
30
- React.useEffect(() => {
31
+
32
+ export function Layout({children}) {
33
+ React.useEffect(() => {
31
34
  // Make contexts and state objects available globally for dynamic eval'd components
32
35
  let windowImports = {
33
36
  {% for library_alias, library_path in window_libraries %}
@@ -36,17 +39,20 @@ export default function MyApp({ Component, pageProps }) {
36
39
  };
37
40
  window["__reflex"] = windowImports;
38
41
  }, []);
39
- return (
40
- jsx(ThemeProvider, {defaultTheme:defaultColorMode,attribute:"class"},
42
+
43
+ return jsx(AppLayout, {},
44
+ jsx(ThemeProvider, {defaultTheme: defaultColorMode, attribute: "class"},
41
45
  jsx(StateProvider, {},
42
- jsx(EventLoopProvider, {},
43
- jsx(AppWrap, {},
44
- jsx(Component, pageProps)
45
- )
46
+ jsx(EventLoopProvider, {},
47
+ jsx(AppWrap, {}, children)
46
48
  )
47
49
  )
48
50
  )
49
51
  );
50
52
  }
51
53
 
54
+ export default function App() {
55
+ return jsx(Outlet, {});
56
+ }
57
+
52
58
  {% endblock %}
@@ -1,7 +1,7 @@
1
1
  {% extends "web/pages/base_page.js.jinja2" %}
2
2
 
3
3
  {% block export %}
4
- export default function Document() {
4
+ export function Layout({children}) {
5
5
  return (
6
6
  {{utils.render(document)}}
7
7
  )
@@ -1,5 +1,4 @@
1
1
  {% import 'web/pages/utils.js.jinja2' as utils %}
2
- /** @jsxImportSource @emotion/react */
3
2
 
4
3
  {% block early_imports %}
5
4
  {% endblock %}
@@ -2,7 +2,11 @@
2
2
  {% from 'web/pages/macros.js.jinja2' import renderHooksWithMemo %}
3
3
  {% set all_hooks = component._get_all_hooks() %}
4
4
 
5
+ {% if export %}
5
6
  export function {{tag_name}} () {
7
+ {% else %}
8
+ function {{tag_name}} () {
9
+ {% endif %}
6
10
  {{ renderHooksWithMemo(all_hooks, memo_trigger_hooks) }}
7
11
 
8
12
  return (
@@ -1,5 +1,6 @@
1
- import { createContext, useContext, useMemo, useReducer, useState, createElement } from "react"
2
- import { applyDelta, Event, hydrateClientStorage, useEventLoop, refs } from "$/utils/state.js"
1
+ import { createContext, useContext, useMemo, useReducer, useState, createElement, useEffect } from "react"
2
+ import { applyDelta, Event, hydrateClientStorage, useEventLoop, refs } from "$/utils/state"
3
+ import { jsx } from "@emotion/react";
3
4
 
4
5
  {% if initial_state %}
5
6
  export const initialState = {{ initial_state|json_dumps }}
@@ -68,8 +69,6 @@ export const initialEvents = () => []
68
69
 
69
70
  export const isDevMode = {{ is_dev_mode|json_dumps }}
70
71
 
71
- export const lastCompiledTimeStamp = {{ last_compiled_time|json_dumps }}
72
-
73
72
  export function UploadFilesProvider({ children }) {
74
73
  const [filesById, setFilesById] = useState({})
75
74
  refs["__clear_selected_files"] = (id) => setFilesById(filesById => {
@@ -77,7 +76,21 @@ export function UploadFilesProvider({ children }) {
77
76
  delete newFilesById[id]
78
77
  return newFilesById
79
78
  })
80
- return createElement(UploadFilesContext, {value:[filesById, setFilesById]}, children);
79
+ return createElement(
80
+ UploadFilesContext.Provider,
81
+ { value: [filesById, setFilesById] },
82
+ children
83
+ );
84
+ }
85
+
86
+ export function ClientSide(component) {
87
+ return ({ children, ...props }) => {
88
+ const [Component, setComponent] = useState(null);
89
+ useEffect(() => {
90
+ setComponent(component);
91
+ }, []);
92
+ return Component ? jsx(Component, props, children) : null;
93
+ };
81
94
  }
82
95
 
83
96
  export function EventLoopProvider({ children }) {
@@ -87,7 +100,11 @@ export function EventLoopProvider({ children }) {
87
100
  initialEvents,
88
101
  clientStorage,
89
102
  )
90
- return createElement(EventLoopContext, {value:[addEvents, connectErrors]}, children);
103
+ return createElement(
104
+ EventLoopContext.Provider,
105
+ { value: [addEvents, connectErrors] },
106
+ children
107
+ );
91
108
  }
92
109
 
93
110
  export function StateProvider({ children }) {
@@ -106,7 +123,7 @@ export function StateProvider({ children }) {
106
123
  {% for state_name in initial_state %}
107
124
  createElement(StateContexts.{{state_name|var_name}},{value: {{state_name|var_name}}},
108
125
  {% endfor %}
109
- createElement(DispatchContext.Provider, {value: dispatchers}, children),
110
- {% for state_name in initial_state|reverse %}){% endfor %}
126
+ createElement(DispatchContext, {value: dispatchers}, children)
127
+ {% for state_name in initial_state %}){% endfor %}
111
128
  )
112
129
  }
@@ -0,0 +1,8 @@
1
+ import { startTransition } from "react";
2
+ import { hydrateRoot } from "react-dom/client";
3
+ import { HydratedRouter } from "react-router/dom";
4
+ import { createElement } from "react";
5
+
6
+ startTransition(() => {
7
+ hydrateRoot(document, createElement(HydratedRouter));
8
+ });
@@ -0,0 +1,10 @@
1
+ import { route } from "@react-router/dev/routes";
2
+ import { flatRoutes } from "@react-router/fs-routes";
3
+
4
+ export default [
5
+ route("404", "routes/[404]_._index.jsx", { id: "404" }),
6
+ ...(await flatRoutes({
7
+ ignoredRouteFiles: ["routes/\\[404\\]_._index.jsx"],
8
+ })),
9
+ route("*", "routes/[404]_._index.jsx"),
10
+ ];
@@ -1,45 +1,14 @@
1
- import { useTheme } from "next-themes";
2
- import { useRef, useEffect, useState, createElement } from "react";
3
- import {
4
- ColorModeContext,
5
- defaultColorMode,
6
- isDevMode,
7
- lastCompiledTimeStamp,
8
- } from "$/utils/context.js";
1
+ import { useTheme } from "$/utils/react-theme";
2
+ import { createElement } from "react";
3
+ import { ColorModeContext, defaultColorMode } from "$/utils/context";
9
4
 
10
5
  export default function RadixThemesColorModeProvider({ children }) {
11
6
  const { theme, resolvedTheme, setTheme } = useTheme();
12
- const [rawColorMode, setRawColorMode] = useState(defaultColorMode);
13
- const [resolvedColorMode, setResolvedColorMode] = useState(
14
- defaultColorMode === "dark" ? "dark" : "light",
15
- );
16
- const firstUpdate = useRef(true);
17
- useEffect(() => {
18
- if (firstUpdate.current) {
19
- firstUpdate.current = false;
20
- setRawColorMode(theme);
21
- setResolvedColorMode(resolvedTheme);
22
- }
23
- });
24
-
25
- useEffect(() => {
26
- if (isDevMode) {
27
- const lastCompiledTimeInLocalStorage =
28
- localStorage.getItem("last_compiled_time");
29
- if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
30
- // on app startup, make sure the application color mode is persisted correctly.
31
- setTheme(defaultColorMode);
32
- localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
33
- return;
34
- }
35
- }
36
- setRawColorMode(theme);
37
- setResolvedColorMode(resolvedTheme);
38
- }, [theme, resolvedTheme]);
39
7
 
40
8
  const toggleColorMode = () => {
41
9
  setTheme(resolvedTheme === "light" ? "dark" : "light");
42
10
  };
11
+
43
12
  const setColorMode = (mode) => {
44
13
  const allowedModes = ["light", "dark", "system"];
45
14
  if (!allowedModes.includes(mode)) {
@@ -50,10 +19,16 @@ export default function RadixThemesColorModeProvider({ children }) {
50
19
  }
51
20
  setTheme(mode);
52
21
  };
22
+
53
23
  return createElement(
54
- ColorModeContext,
24
+ ColorModeContext.Provider,
55
25
  {
56
- value: { rawColorMode, resolvedColorMode, toggleColorMode, setColorMode },
26
+ value: {
27
+ rawColorMode: theme,
28
+ resolvedColorMode: resolvedTheme,
29
+ toggleColorMode,
30
+ setColorMode,
31
+ },
57
32
  },
58
33
  children,
59
34
  );
@@ -1,4 +1,4 @@
1
- module.exports = {
1
+ export default {
2
2
  plugins: {
3
3
  "postcss-import": {},
4
4
  autoprefixer: {},
@@ -0,0 +1,6 @@
1
+ export default {
2
+ future: {
3
+ unstable_optimizeDeps: true,
4
+ },
5
+ ssr: false,
6
+ };
@@ -1,41 +1,43 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
- import { useRouter } from "next/router";
2
+ import { useLocation, useNavigate } from "react-router-dom";
3
3
 
4
4
  /**
5
- * React hook for use in /404 page to enable client-side routing.
5
+ * React hook for use in NotFound page to enable client-side routing.
6
6
  *
7
- * Uses the next/router to redirect to the provided URL when loading
8
- * the 404 page (for example as a fallback in static hosting situations).
7
+ * Uses React Router to redirect to the provided URL when loading
8
+ * the NotFound page (for example as a fallback in static hosting situations).
9
9
  *
10
10
  * @returns {boolean} routeNotFound - true if the current route is an actual 404
11
11
  */
12
12
  export const useClientSideRouting = () => {
13
13
  const [routeNotFound, setRouteNotFound] = useState(false);
14
14
  const didRedirect = useRef(false);
15
- const router = useRouter();
15
+ const location = useLocation();
16
+ const navigate = useNavigate();
17
+
16
18
  useEffect(() => {
17
- if (
18
- router.isReady &&
19
- !didRedirect.current // have not tried redirecting yet
20
- ) {
21
- didRedirect.current = true; // never redirect twice to avoid "Hard Navigate" error
19
+ if (!didRedirect.current) {
20
+ // have not tried redirecting yet
21
+ didRedirect.current = true; // never redirect twice to avoid navigation loops
22
+
22
23
  // attempt to redirect to the route in the browser address bar once
23
- router
24
- .replace({
25
- pathname: window.location.pathname,
26
- query: window.location.search.slice(1),
27
- })
24
+ const path = window.location.pathname;
25
+ const search = window.location.search;
26
+
27
+ // Use navigate instead of replace
28
+ navigate(path + search, { replace: true })
28
29
  .then(() => {
29
- // Check if the current route is /404
30
- if (router.pathname === "/404") {
30
+ // Check if we're still on a NotFound route
31
+ // Note: This depends on how your routes are set up
32
+ if (location.pathname === path) {
31
33
  setRouteNotFound(true); // Mark as an actual 404
32
34
  }
33
35
  })
34
- .catch((e) => {
36
+ .catch(() => {
35
37
  setRouteNotFound(true); // navigation failed, so this is a real 404
36
38
  });
37
39
  }
38
- }, [router.isReady]);
40
+ }, [location, navigate]);
39
41
 
40
42
  // Return the reactive bool, to avoid flashing 404 page until we know for sure
41
43
  // the route is not found.
@@ -0,0 +1,92 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useEffect,
6
+ createElement,
7
+ useRef,
8
+ useMemo,
9
+ } from "react";
10
+
11
+ import { isDevMode, defaultColorMode } from "$/utils/context";
12
+
13
+ const ThemeContext = createContext({
14
+ theme: defaultColorMode,
15
+ resolvedTheme: defaultColorMode !== "system" ? defaultColorMode : "light",
16
+ setTheme: () => {},
17
+ });
18
+
19
+ export function ThemeProvider({ children, defaultTheme = "system" }) {
20
+ const [theme, setTheme] = useState(defaultTheme);
21
+ const [systemTheme, setSystemTheme] = useState(
22
+ defaultTheme !== "system" ? defaultTheme : "light",
23
+ );
24
+
25
+ const firstRender = useRef(true);
26
+
27
+ useEffect(() => {
28
+ if (!firstRender.current) {
29
+ return;
30
+ }
31
+
32
+ firstRender.current = false;
33
+
34
+ if (isDevMode) {
35
+ const lastCompiledTheme = localStorage.getItem("last_compiled_theme");
36
+ if (lastCompiledTheme !== defaultColorMode) {
37
+ // on app startup, make sure the application color mode is persisted correctly.
38
+ localStorage.setItem("last_compiled_theme", defaultColorMode);
39
+ return;
40
+ }
41
+ }
42
+
43
+ // Load saved theme from localStorage
44
+ const savedTheme = localStorage.getItem("theme") || defaultTheme;
45
+ setTheme(savedTheme);
46
+ });
47
+
48
+ const resolvedTheme = useMemo(
49
+ () => (theme === "system" ? systemTheme : theme),
50
+ [theme, systemTheme],
51
+ );
52
+
53
+ useEffect(() => {
54
+ // Set up media query for system preference detection
55
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
56
+
57
+ // Listen for system preference changes
58
+ const handleChange = () => {
59
+ setSystemTheme(mediaQuery.matches ? "dark" : "light");
60
+ };
61
+
62
+ handleChange();
63
+
64
+ mediaQuery.addEventListener("change", handleChange);
65
+
66
+ return () => {
67
+ mediaQuery.removeEventListener("change", handleChange);
68
+ };
69
+ });
70
+
71
+ // Save theme to localStorage whenever it changes
72
+ useEffect(() => {
73
+ localStorage.setItem("theme", theme);
74
+ }, [theme]);
75
+
76
+ useEffect(() => {
77
+ const root = window.document.documentElement;
78
+ root.classList.remove("light", "dark");
79
+ root.classList.add(resolvedTheme);
80
+ root.style.colorScheme = resolvedTheme;
81
+ }, [resolvedTheme]);
82
+
83
+ return createElement(
84
+ ThemeContext.Provider,
85
+ { value: { theme, resolvedTheme, setTheme } },
86
+ children,
87
+ );
88
+ }
89
+
90
+ export function useTheme() {
91
+ return useContext(ThemeContext);
92
+ }