@azerion/bunosaurus-theme-common 0.0.0-stage → 4.0.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 (261) hide show
  1. package/README.md +2 -2
  2. package/lib/components/Collapsible/index.d.ts +59 -0
  3. package/lib/components/Collapsible/index.d.ts.map +1 -0
  4. package/lib/components/Collapsible/index.js +145 -0
  5. package/lib/components/Collapsible/index.js.map +1 -0
  6. package/lib/components/Details/index.d.ts +20 -0
  7. package/lib/components/Details/index.d.ts.map +1 -0
  8. package/lib/components/Details/index.js +86 -0
  9. package/lib/components/Details/index.js.map +1 -0
  10. package/lib/components/Details/styles.module.css +66 -0
  11. package/lib/components/ThemedComponent/index.d.ts +31 -0
  12. package/lib/components/ThemedComponent/index.d.ts.map +1 -0
  13. package/lib/components/ThemedComponent/index.js +47 -0
  14. package/lib/components/ThemedComponent/index.js.map +1 -0
  15. package/lib/components/ThemedComponent/styles.module.css +26 -0
  16. package/lib/contexts/announcementBar.d.ts +21 -0
  17. package/lib/contexts/announcementBar.d.ts.map +1 -0
  18. package/lib/contexts/announcementBar.js +72 -0
  19. package/lib/contexts/announcementBar.js.map +1 -0
  20. package/lib/contexts/colorMode.d.ts +23 -0
  21. package/lib/contexts/colorMode.d.ts.map +1 -0
  22. package/lib/contexts/colorMode.js +163 -0
  23. package/lib/contexts/colorMode.js.map +1 -0
  24. package/lib/contexts/navbarMobileSidebar.d.ts +31 -0
  25. package/lib/contexts/navbarMobileSidebar.d.ts.map +1 -0
  26. package/lib/contexts/navbarMobileSidebar.js +65 -0
  27. package/lib/contexts/navbarMobileSidebar.js.map +1 -0
  28. package/lib/contexts/navbarSecondaryMenu/content.d.ts +37 -0
  29. package/lib/contexts/navbarSecondaryMenu/content.d.ts.map +1 -0
  30. package/lib/contexts/navbarSecondaryMenu/content.js +50 -0
  31. package/lib/contexts/navbarSecondaryMenu/content.js.map +1 -0
  32. package/lib/contexts/navbarSecondaryMenu/display.d.ts +24 -0
  33. package/lib/contexts/navbarSecondaryMenu/display.d.ts.map +1 -0
  34. package/lib/contexts/navbarSecondaryMenu/display.js +62 -0
  35. package/lib/contexts/navbarSecondaryMenu/display.js.map +1 -0
  36. package/lib/hooks/useBackToTopButton.d.ts +27 -0
  37. package/lib/hooks/useBackToTopButton.d.ts.map +1 -0
  38. package/lib/hooks/useBackToTopButton.js +50 -0
  39. package/lib/hooks/useBackToTopButton.js.map +1 -0
  40. package/lib/hooks/useCodeWordWrap.d.ts +15 -0
  41. package/lib/hooks/useCodeWordWrap.d.ts.map +1 -0
  42. package/lib/hooks/useCodeWordWrap.js +67 -0
  43. package/lib/hooks/useCodeWordWrap.js.map +1 -0
  44. package/lib/hooks/useHideableNavbar.d.ts +17 -0
  45. package/lib/hooks/useHideableNavbar.d.ts.map +1 -0
  46. package/lib/hooks/useHideableNavbar.js +65 -0
  47. package/lib/hooks/useHideableNavbar.js.map +1 -0
  48. package/lib/hooks/useLockBodyScroll.d.ts +12 -0
  49. package/lib/hooks/useLockBodyScroll.d.ts.map +1 -0
  50. package/lib/hooks/useLockBodyScroll.js +20 -0
  51. package/lib/hooks/useLockBodyScroll.js.map +1 -0
  52. package/lib/hooks/useMutationObserver.d.ts +4 -0
  53. package/lib/hooks/useMutationObserver.d.ts.map +1 -0
  54. package/lib/hooks/useMutationObserver.js +29 -0
  55. package/lib/hooks/useMutationObserver.js.map +1 -0
  56. package/lib/hooks/usePrismTheme.d.ts +13 -0
  57. package/lib/hooks/usePrismTheme.d.ts.map +1 -0
  58. package/lib/hooks/usePrismTheme.js +21 -0
  59. package/lib/hooks/usePrismTheme.js.map +1 -0
  60. package/lib/hooks/useTOCHighlight.d.ts +25 -0
  61. package/lib/hooks/useTOCHighlight.d.ts.map +1 -0
  62. package/lib/hooks/useTOCHighlight.js +130 -0
  63. package/lib/hooks/useTOCHighlight.js.map +1 -0
  64. package/lib/hooks/useWindowSize.d.ts +27 -0
  65. package/lib/hooks/useWindowSize.d.ts.map +1 -0
  66. package/lib/hooks/useWindowSize.js +49 -0
  67. package/lib/hooks/useWindowSize.js.map +1 -0
  68. package/lib/index.d.ts +30 -0
  69. package/lib/index.d.ts.map +1 -0
  70. package/lib/index.js +37 -0
  71. package/lib/index.js.map +1 -0
  72. package/lib/internal.d.ts +35 -0
  73. package/lib/internal.d.ts.map +1 -0
  74. package/lib/internal.js +45 -0
  75. package/lib/internal.js.map +1 -0
  76. package/lib/translations/blogTranslations.d.ts +20 -0
  77. package/lib/translations/blogTranslations.d.ts.map +1 -0
  78. package/lib/translations/blogTranslations.js +52 -0
  79. package/lib/translations/blogTranslations.js.map +1 -0
  80. package/lib/translations/contentVisibilityTranslations.d.ts +12 -0
  81. package/lib/translations/contentVisibilityTranslations.d.ts.map +1 -0
  82. package/lib/translations/contentVisibilityTranslations.js +30 -0
  83. package/lib/translations/contentVisibilityTranslations.js.map +1 -0
  84. package/lib/translations/docsTranslations.d.ts +8 -0
  85. package/lib/translations/docsTranslations.d.ts.map +1 -0
  86. package/lib/translations/docsTranslations.js +17 -0
  87. package/lib/translations/docsTranslations.js.map +1 -0
  88. package/lib/utils/IntlUtils.d.ts +2 -0
  89. package/lib/utils/IntlUtils.d.ts.map +1 -0
  90. package/lib/utils/IntlUtils.js +20 -0
  91. package/lib/utils/IntlUtils.js.map +1 -0
  92. package/lib/utils/ThemeClassNames.d.ts +99 -0
  93. package/lib/utils/ThemeClassNames.d.ts.map +1 -0
  94. package/lib/utils/ThemeClassNames.js +107 -0
  95. package/lib/utils/ThemeClassNames.js.map +1 -0
  96. package/lib/utils/accessibilityUtils.d.ts +8 -0
  97. package/lib/utils/accessibilityUtils.d.ts.map +1 -0
  98. package/lib/utils/accessibilityUtils.js +10 -0
  99. package/lib/utils/accessibilityUtils.js.map +1 -0
  100. package/lib/utils/admonitionUtils.d.ts +12 -0
  101. package/lib/utils/admonitionUtils.d.ts.map +1 -0
  102. package/lib/utils/admonitionUtils.js +32 -0
  103. package/lib/utils/admonitionUtils.js.map +1 -0
  104. package/lib/utils/anchorUtils.d.ts +16 -0
  105. package/lib/utils/anchorUtils.d.ts.map +1 -0
  106. package/lib/utils/anchorUtils.js +26 -0
  107. package/lib/utils/anchorUtils.js.map +1 -0
  108. package/lib/utils/anchorUtils.module.css +14 -0
  109. package/lib/utils/codeBlockUtils.d.ts +101 -0
  110. package/lib/utils/codeBlockUtils.d.ts.map +1 -0
  111. package/lib/utils/codeBlockUtils.js +310 -0
  112. package/lib/utils/codeBlockUtils.js.map +1 -0
  113. package/lib/utils/emojiUtils.d.ts +20 -0
  114. package/lib/utils/emojiUtils.d.ts.map +1 -0
  115. package/lib/utils/emojiUtils.js +32 -0
  116. package/lib/utils/emojiUtils.js.map +1 -0
  117. package/lib/utils/errorBoundaryUtils.d.ts +31 -0
  118. package/lib/utils/errorBoundaryUtils.d.ts.map +1 -0
  119. package/lib/utils/errorBoundaryUtils.js +48 -0
  120. package/lib/utils/errorBoundaryUtils.js.map +1 -0
  121. package/lib/utils/errorBoundaryUtils.module.css +16 -0
  122. package/lib/utils/footerUtils.d.ts +13 -0
  123. package/lib/utils/footerUtils.d.ts.map +1 -0
  124. package/lib/utils/footerUtils.js +14 -0
  125. package/lib/utils/footerUtils.js.map +1 -0
  126. package/lib/utils/historyUtils.d.ts +39 -0
  127. package/lib/utils/historyUtils.d.ts.map +1 -0
  128. package/lib/utils/historyUtils.js +148 -0
  129. package/lib/utils/historyUtils.js.map +1 -0
  130. package/lib/utils/jsUtils.d.ts +24 -0
  131. package/lib/utils/jsUtils.d.ts.map +1 -0
  132. package/lib/utils/jsUtils.js +43 -0
  133. package/lib/utils/jsUtils.js.map +1 -0
  134. package/lib/utils/metadataUtils.d.ts +38 -0
  135. package/lib/utils/metadataUtils.d.ts.map +1 -0
  136. package/lib/utils/metadataUtils.js +87 -0
  137. package/lib/utils/metadataUtils.js.map +1 -0
  138. package/lib/utils/navbarUtils.d.ts +21 -0
  139. package/lib/utils/navbarUtils.d.ts.map +1 -0
  140. package/lib/utils/navbarUtils.js +35 -0
  141. package/lib/utils/navbarUtils.js.map +1 -0
  142. package/lib/utils/reactUtils.d.ts +60 -0
  143. package/lib/utils/reactUtils.d.ts.map +1 -0
  144. package/lib/utils/reactUtils.js +91 -0
  145. package/lib/utils/reactUtils.js.map +1 -0
  146. package/lib/utils/regexpUtils.d.ts +12 -0
  147. package/lib/utils/regexpUtils.d.ts.map +1 -0
  148. package/lib/utils/regexpUtils.js +18 -0
  149. package/lib/utils/regexpUtils.js.map +1 -0
  150. package/lib/utils/routesUtils.d.ts +26 -0
  151. package/lib/utils/routesUtils.d.ts.map +1 -0
  152. package/lib/utils/routesUtils.js +54 -0
  153. package/lib/utils/routesUtils.js.map +1 -0
  154. package/lib/utils/scrollUtils.d.ts +83 -0
  155. package/lib/utils/scrollUtils.d.ts.map +1 -0
  156. package/lib/utils/scrollUtils.js +201 -0
  157. package/lib/utils/scrollUtils.js.map +1 -0
  158. package/lib/utils/searchUtils.d.ts +8 -0
  159. package/lib/utils/searchUtils.d.ts.map +1 -0
  160. package/lib/utils/searchUtils.js +8 -0
  161. package/lib/utils/searchUtils.js.map +1 -0
  162. package/lib/utils/skipToContentUtils.d.ts +20 -0
  163. package/lib/utils/skipToContentUtils.d.ts.map +1 -0
  164. package/lib/utils/skipToContentUtils.js +75 -0
  165. package/lib/utils/skipToContentUtils.js.map +1 -0
  166. package/lib/utils/storageUtils.d.ts +36 -0
  167. package/lib/utils/storageUtils.d.ts.map +1 -0
  168. package/lib/utils/storageUtils.js +193 -0
  169. package/lib/utils/storageUtils.js.map +1 -0
  170. package/lib/utils/tabsUtils.d.ts +55 -0
  171. package/lib/utils/tabsUtils.d.ts.map +1 -0
  172. package/lib/utils/tabsUtils.js +179 -0
  173. package/lib/utils/tabsUtils.js.map +1 -0
  174. package/lib/utils/tagsUtils.d.ts +20 -0
  175. package/lib/utils/tagsUtils.d.ts.map +1 -0
  176. package/lib/utils/tagsUtils.js +41 -0
  177. package/lib/utils/tagsUtils.js.map +1 -0
  178. package/lib/utils/titleFormatterUtils.d.ts +59 -0
  179. package/lib/utils/titleFormatterUtils.d.ts.map +1 -0
  180. package/lib/utils/titleFormatterUtils.js +49 -0
  181. package/lib/utils/titleFormatterUtils.js.map +1 -0
  182. package/lib/utils/tocUtils.d.ts +36 -0
  183. package/lib/utils/tocUtils.d.ts.map +1 -0
  184. package/lib/utils/tocUtils.js +84 -0
  185. package/lib/utils/tocUtils.js.map +1 -0
  186. package/lib/utils/useAlternatePageUtils.d.ts +32 -0
  187. package/lib/utils/useAlternatePageUtils.d.ts.map +1 -0
  188. package/lib/utils/useAlternatePageUtils.js +42 -0
  189. package/lib/utils/useAlternatePageUtils.js.map +1 -0
  190. package/lib/utils/useLocalPathname.d.ts +13 -0
  191. package/lib/utils/useLocalPathname.d.ts.map +1 -0
  192. package/lib/utils/useLocalPathname.js +19 -0
  193. package/lib/utils/useLocalPathname.js.map +1 -0
  194. package/lib/utils/useLocationChange.d.ts +17 -0
  195. package/lib/utils/useLocationChange.d.ts.map +1 -0
  196. package/lib/utils/useLocationChange.js +31 -0
  197. package/lib/utils/useLocationChange.js.map +1 -0
  198. package/lib/utils/usePluralForm.d.ts +21 -0
  199. package/lib/utils/usePluralForm.d.ts.map +1 -0
  200. package/lib/utils/usePluralForm.js +93 -0
  201. package/lib/utils/usePluralForm.js.map +1 -0
  202. package/lib/utils/useThemeConfig.d.ts +120 -0
  203. package/lib/utils/useThemeConfig.d.ts.map +1 -0
  204. package/lib/utils/useThemeConfig.js +14 -0
  205. package/lib/utils/useThemeConfig.js.map +1 -0
  206. package/package.json +58 -4
  207. package/src/components/Collapsible/index.tsx +250 -0
  208. package/src/components/Details/index.tsx +130 -0
  209. package/src/components/Details/styles.module.css +66 -0
  210. package/src/components/ThemedComponent/index.tsx +75 -0
  211. package/src/components/ThemedComponent/styles.module.css +26 -0
  212. package/src/contexts/announcementBar.tsx +117 -0
  213. package/src/contexts/colorMode.tsx +257 -0
  214. package/src/contexts/navbarMobileSidebar.tsx +119 -0
  215. package/src/contexts/navbarSecondaryMenu/content.tsx +100 -0
  216. package/src/contexts/navbarSecondaryMenu/display.tsx +102 -0
  217. package/src/hooks/useBackToTopButton.ts +73 -0
  218. package/src/hooks/useCodeWordWrap.ts +107 -0
  219. package/src/hooks/useHideableNavbar.ts +81 -0
  220. package/src/hooks/useLockBodyScroll.ts +21 -0
  221. package/src/hooks/useMutationObserver.ts +38 -0
  222. package/src/hooks/usePrismTheme.ts +24 -0
  223. package/src/hooks/useTOCHighlight.ts +189 -0
  224. package/src/hooks/useWindowSize.ts +67 -0
  225. package/src/index.ts +112 -0
  226. package/src/internal.ts +107 -0
  227. package/src/translations/blogTranslations.tsx +89 -0
  228. package/src/translations/contentVisibilityTranslations.tsx +51 -0
  229. package/src/translations/docsTranslations.tsx +28 -0
  230. package/src/types.d.ts +8 -0
  231. package/src/utils/IntlUtils.ts +27 -0
  232. package/src/utils/ThemeClassNames.ts +119 -0
  233. package/src/utils/accessibilityUtils.ts +10 -0
  234. package/src/utils/admonitionUtils.tsx +44 -0
  235. package/src/utils/anchorUtils.module.css +14 -0
  236. package/src/utils/anchorUtils.ts +31 -0
  237. package/src/utils/codeBlockUtils.tsx +494 -0
  238. package/src/utils/emojiUtils.ts +40 -0
  239. package/src/utils/errorBoundaryUtils.module.css +16 -0
  240. package/src/utils/errorBoundaryUtils.tsx +71 -0
  241. package/src/utils/footerUtils.ts +18 -0
  242. package/src/utils/historyUtils.ts +199 -0
  243. package/src/utils/jsUtils.ts +54 -0
  244. package/src/utils/metadataUtils.tsx +145 -0
  245. package/src/utils/navbarUtils.tsx +45 -0
  246. package/src/utils/reactUtils.tsx +120 -0
  247. package/src/utils/regexpUtils.ts +24 -0
  248. package/src/utils/routesUtils.ts +75 -0
  249. package/src/utils/scrollUtils.tsx +303 -0
  250. package/src/utils/searchUtils.ts +8 -0
  251. package/src/utils/skipToContentUtils.tsx +108 -0
  252. package/src/utils/storageUtils.ts +260 -0
  253. package/src/utils/tabsUtils.tsx +307 -0
  254. package/src/utils/tagsUtils.tsx +61 -0
  255. package/src/utils/titleFormatterUtils.tsx +120 -0
  256. package/src/utils/tocUtils.ts +119 -0
  257. package/src/utils/useAlternatePageUtils.ts +80 -0
  258. package/src/utils/useLocalPathname.ts +22 -0
  259. package/src/utils/useLocationChange.ts +41 -0
  260. package/src/utils/usePluralForm.ts +131 -0
  261. package/src/utils/useThemeConfig.ts +147 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import React, {
9
+ useState,
10
+ useEffect,
11
+ useCallback,
12
+ useMemo,
13
+ useContext,
14
+ type ReactNode,
15
+ } from 'react';
16
+ import useIsBrowser from '@docusaurus/useIsBrowser';
17
+ import {createStorageSlot} from '../utils/storageUtils';
18
+ import {ReactContextError} from '../utils/reactUtils';
19
+ import {useThemeConfig} from '../utils/useThemeConfig';
20
+
21
+ // Keep these keys in sync with the inlined script
22
+ // See packages/docusaurus-theme-classic/src/inlineScripts.ts
23
+ const AnnouncementBarDismissStorage = createStorageSlot(
24
+ 'docusaurus.announcement.dismiss',
25
+ );
26
+ const IdStorage = createStorageSlot('docusaurus.announcement.id');
27
+
28
+ const isDismissedInStorage = () =>
29
+ AnnouncementBarDismissStorage.get() === 'true';
30
+ const setDismissedInStorage = (bool: boolean) =>
31
+ AnnouncementBarDismissStorage.set(String(bool));
32
+
33
+ type ContextValue = {
34
+ /** Whether the announcement bar should be displayed. */
35
+ readonly isActive: boolean;
36
+ /**
37
+ * Callback fired when the user closes the announcement. Will be saved.
38
+ */
39
+ readonly close: () => void;
40
+ };
41
+
42
+ const Context = React.createContext<ContextValue | null>(null);
43
+
44
+ function useContextValue(): ContextValue {
45
+ const {announcementBar} = useThemeConfig();
46
+ const isBrowser = useIsBrowser();
47
+
48
+ const [isClosed, setClosed] = useState(() =>
49
+ isBrowser
50
+ ? // On client navigation: init with local storage value
51
+ isDismissedInStorage()
52
+ : // On server/hydration: always visible to prevent layout shifts (will be hidden with css if needed)
53
+ false,
54
+ );
55
+ // Update state after hydration
56
+ useEffect(() => {
57
+ setClosed(isDismissedInStorage());
58
+ }, []);
59
+
60
+ const handleClose = useCallback(() => {
61
+ setDismissedInStorage(true);
62
+ setClosed(true);
63
+ }, []);
64
+
65
+ useEffect(() => {
66
+ if (!announcementBar) {
67
+ return;
68
+ }
69
+ const {id} = announcementBar;
70
+
71
+ let viewedId = IdStorage.get();
72
+
73
+ // Retrocompatibility due to spelling mistake of default id
74
+ // see https://github.com/facebook/docusaurus/issues/3338
75
+ // cSpell:ignore annoucement
76
+ if (viewedId === 'annoucement-bar') {
77
+ viewedId = 'announcement-bar';
78
+ }
79
+
80
+ const isNewAnnouncement = id !== viewedId;
81
+
82
+ IdStorage.set(id);
83
+
84
+ if (isNewAnnouncement) {
85
+ setDismissedInStorage(false);
86
+ }
87
+
88
+ if (isNewAnnouncement || !isDismissedInStorage()) {
89
+ setClosed(false);
90
+ }
91
+ }, [announcementBar]);
92
+
93
+ return useMemo(
94
+ () => ({
95
+ isActive: !!announcementBar && !isClosed,
96
+ close: handleClose,
97
+ }),
98
+ [announcementBar, isClosed, handleClose],
99
+ );
100
+ }
101
+
102
+ export function AnnouncementBarProvider({
103
+ children,
104
+ }: {
105
+ children: ReactNode;
106
+ }): ReactNode {
107
+ const value = useContextValue();
108
+ return <Context value={value}>{children}</Context>;
109
+ }
110
+
111
+ export function useAnnouncementBar(): ContextValue {
112
+ const api = useContext(Context);
113
+ if (!api) {
114
+ throw new ReactContextError('AnnouncementBarProvider');
115
+ }
116
+ return api;
117
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import React, {
9
+ useState,
10
+ useCallback,
11
+ useEffect,
12
+ useContext,
13
+ useMemo,
14
+ type ReactNode,
15
+ } from 'react';
16
+ import useIsBrowser from '@docusaurus/useIsBrowser';
17
+ import {ReactContextError} from '../utils/reactUtils';
18
+ import {createStorageSlot} from '../utils/storageUtils';
19
+ import {useThemeConfig} from '../utils/useThemeConfig';
20
+
21
+ // The "effective" color mode
22
+ export type ColorMode = 'light' | 'dark';
23
+
24
+ // The color mode explicitly chosen by the user
25
+ // null => no choice has been made, or the choice has been reverted to OS value
26
+ type ColorModeChoice = ColorMode | null;
27
+
28
+ function getSystemColorMode(): ColorMode {
29
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
30
+ ? 'dark'
31
+ : 'light';
32
+ }
33
+
34
+ function subscribeToMedia(
35
+ query: string,
36
+ listener: (event: MediaQueryListEvent) => void,
37
+ ): () => void {
38
+ const mql = window.matchMedia(query);
39
+ mql.addEventListener('change', listener);
40
+ return () => mql.removeEventListener('change', listener);
41
+ }
42
+
43
+ function subscribeToSystemColorModeChange(
44
+ onChange: (newSystemColorMode: ColorMode) => void,
45
+ ): () => void {
46
+ return subscribeToMedia('(prefers-color-scheme: dark)', () =>
47
+ onChange(getSystemColorMode()),
48
+ );
49
+ }
50
+
51
+ type ContextValue = {
52
+ /** The effective color mode. */
53
+ readonly colorMode: ColorMode;
54
+ /** The explicitly chosen color mode */
55
+ readonly colorModeChoice: ColorModeChoice;
56
+ /** Set new color mode. */
57
+ readonly setColorMode: (colorMode: ColorModeChoice) => void;
58
+ };
59
+
60
+ const Context = React.createContext<ContextValue | undefined>(undefined);
61
+
62
+ const ColorModeStorageKey = 'theme';
63
+ const ColorModeStorage = createStorageSlot(ColorModeStorageKey);
64
+
65
+ // We use data-theme-choice="system", not an absent attribute
66
+ // This is easier to handle for users with CSS
67
+ const SystemAttribute = 'system';
68
+
69
+ // Ensure to always return a valid colorMode even if input is invalid
70
+ const coerceToColorMode = (colorMode: string | null): ColorMode =>
71
+ colorMode === 'dark' ? 'dark' : 'light';
72
+ const coerceToColorModeChoice = (colorMode: string | null): ColorModeChoice =>
73
+ colorMode === null || colorMode === SystemAttribute
74
+ ? null
75
+ : coerceToColorMode(colorMode);
76
+
77
+ const ColorModeAttribute = {
78
+ get: () => {
79
+ return coerceToColorMode(
80
+ document.documentElement.getAttribute('data-theme'),
81
+ );
82
+ },
83
+ set: (colorMode: ColorMode) => {
84
+ document.documentElement.setAttribute(
85
+ 'data-theme',
86
+ coerceToColorMode(colorMode),
87
+ );
88
+ },
89
+ };
90
+
91
+ const ColorModeChoiceAttribute = {
92
+ get: () => {
93
+ return coerceToColorModeChoice(
94
+ document.documentElement.getAttribute('data-theme-choice'),
95
+ );
96
+ },
97
+ set: (colorMode: ColorModeChoice) => {
98
+ document.documentElement.setAttribute(
99
+ 'data-theme-choice',
100
+ coerceToColorModeChoice(colorMode) ?? SystemAttribute,
101
+ );
102
+ },
103
+ };
104
+
105
+ const persistColorModeChoice = (newColorMode: ColorModeChoice) => {
106
+ if (newColorMode === null) {
107
+ ColorModeStorage.del();
108
+ } else {
109
+ ColorModeStorage.set(coerceToColorMode(newColorMode));
110
+ }
111
+ };
112
+
113
+ // The color mode state is initialized in useEffect on purpose
114
+ // to avoid a React hydration mismatch errors
115
+ // The useColorMode() hook value lags behind on purpose
116
+ // This helps users avoid hydration mismatch errors in their code
117
+ // See also https://github.com/facebook/docusaurus/issues/7986
118
+ function useColorModeState() {
119
+ const {
120
+ colorMode: {defaultMode},
121
+ } = useThemeConfig();
122
+ const isBrowser = useIsBrowser();
123
+
124
+ // Since the provider may unmount/remount on client navigation, we need to
125
+ // reinitialize the state with the correct values to avoid visual glitches.
126
+ // See also https://github.com/facebook/docusaurus/issues/11399#issuecomment-3279181314
127
+ const [colorMode, setColorModeState] = useState<ColorMode>(
128
+ isBrowser ? ColorModeAttribute.get() : defaultMode,
129
+ );
130
+ const [colorModeChoice, setColorModeChoiceState] = useState<ColorModeChoice>(
131
+ isBrowser ? ColorModeChoiceAttribute.get() : null,
132
+ );
133
+
134
+ useEffect(() => {
135
+ setColorModeState(ColorModeAttribute.get());
136
+ setColorModeChoiceState(ColorModeChoiceAttribute.get());
137
+ }, []);
138
+
139
+ return {
140
+ colorMode,
141
+ setColorModeState,
142
+ colorModeChoice,
143
+ setColorModeChoiceState,
144
+ } as const;
145
+ }
146
+
147
+ function useContextValue(): ContextValue {
148
+ const {
149
+ colorMode: {defaultMode, disableSwitch, respectPrefersColorScheme},
150
+ } = useThemeConfig();
151
+ const {
152
+ colorMode,
153
+ setColorModeState,
154
+ colorModeChoice,
155
+ setColorModeChoiceState,
156
+ } = useColorModeState();
157
+
158
+ useEffect(() => {
159
+ // A site is deployed without disableSwitch
160
+ // => User visits the site and has a persisted value
161
+ // => Site later enabled disableSwitch
162
+ // => Clear the previously stored value to apply the site's setting
163
+ if (disableSwitch) {
164
+ ColorModeStorage.del();
165
+ }
166
+ }, [disableSwitch]);
167
+
168
+ const setColorMode = useCallback(
169
+ (
170
+ newColorModeChoice: ColorModeChoice,
171
+ options: {persist?: boolean} = {},
172
+ ) => {
173
+ const {persist = true} = options;
174
+
175
+ // Reset to system/default color mode
176
+ if (newColorModeChoice === null) {
177
+ // Set the effective color
178
+ const newColorMode = respectPrefersColorScheme
179
+ ? getSystemColorMode()
180
+ : defaultMode;
181
+ ColorModeAttribute.set(newColorMode);
182
+ setColorModeState(newColorMode);
183
+ // Set the chosen color
184
+ ColorModeChoiceAttribute.set(null);
185
+ setColorModeChoiceState(null);
186
+ }
187
+ // Happy case, when an explicit color is provided
188
+ else {
189
+ ColorModeAttribute.set(newColorModeChoice);
190
+ ColorModeChoiceAttribute.set(newColorModeChoice);
191
+ setColorModeState(newColorModeChoice);
192
+ setColorModeChoiceState(newColorModeChoice);
193
+ }
194
+
195
+ if (persist) {
196
+ persistColorModeChoice(newColorModeChoice);
197
+ }
198
+ },
199
+ [
200
+ setColorModeState,
201
+ setColorModeChoiceState,
202
+ respectPrefersColorScheme,
203
+ defaultMode,
204
+ ],
205
+ );
206
+
207
+ // Synchronize theme color/choice mode with browser storage
208
+ useEffect(() => {
209
+ return ColorModeStorage.listen((e) => {
210
+ setColorMode(coerceToColorModeChoice(e.newValue));
211
+ });
212
+ }, [setColorMode]);
213
+
214
+ // Synchronize theme color with system color
215
+ useEffect(() => {
216
+ if (colorModeChoice !== null || !respectPrefersColorScheme) {
217
+ return undefined;
218
+ }
219
+ return subscribeToSystemColorModeChange((newSystemColorMode) => {
220
+ // Note: we don't use "setColorMode" on purpose
221
+ // The system changes should never be considered an explicit theme choice
222
+ // They only affect the "effective" color, and should never be persisted
223
+ // Note: this listener also fire when printing, see https://github.com/facebook/docusaurus/pull/6490
224
+ setColorModeState(newSystemColorMode);
225
+ ColorModeAttribute.set(newSystemColorMode);
226
+ });
227
+ }, [respectPrefersColorScheme, colorModeChoice, setColorModeState]);
228
+
229
+ return useMemo(
230
+ () => ({
231
+ colorMode,
232
+ colorModeChoice,
233
+ setColorMode,
234
+ }),
235
+ [colorMode, colorModeChoice, setColorMode],
236
+ );
237
+ }
238
+
239
+ export function ColorModeProvider({
240
+ children,
241
+ }: {
242
+ children: ReactNode;
243
+ }): ReactNode {
244
+ const value = useContextValue();
245
+ return <Context value={value}>{children}</Context>;
246
+ }
247
+
248
+ export function useColorMode(): ContextValue {
249
+ const context = useContext(Context);
250
+ if (context == null) {
251
+ throw new ReactContextError(
252
+ 'ColorModeProvider',
253
+ 'Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.',
254
+ );
255
+ }
256
+ return context;
257
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import React, {
9
+ useCallback,
10
+ useEffect,
11
+ useState,
12
+ useMemo,
13
+ type ReactNode,
14
+ } from 'react';
15
+ import {useNavbarSecondaryMenuContent} from './navbarSecondaryMenu/content';
16
+ import {useWindowSize} from '../hooks/useWindowSize';
17
+ import {useHistoryPopHandler} from '../utils/historyUtils';
18
+ import {useThemeConfig} from '../utils/useThemeConfig';
19
+ import {ReactContextError} from '../utils/reactUtils';
20
+
21
+ type ContextValue = {
22
+ /**
23
+ * Mobile sidebar should be disabled in case it's empty, i.e. no secondary
24
+ * menu + no navbar items). If disabled, the toggle button should not be
25
+ * displayed at all.
26
+ */
27
+ disabled: boolean;
28
+ /**
29
+ * Signals whether the actual sidebar should be displayed (contrary to
30
+ * `disabled` which is about the toggle button). Sidebar should not visible
31
+ * until user interaction to avoid SSR rendering.
32
+ */
33
+ shouldRender: boolean;
34
+ /** The displayed state. Can be toggled with the `toggle` callback. */
35
+ shown: boolean;
36
+ /** Toggle the `shown` attribute. */
37
+ toggle: () => void;
38
+ };
39
+
40
+ const Context = React.createContext<ContextValue | undefined>(undefined);
41
+
42
+ function useIsNavbarMobileSidebarDisabled() {
43
+ const secondaryMenuContent = useNavbarSecondaryMenuContent();
44
+ const {items} = useThemeConfig().navbar;
45
+ return items.length === 0 && !secondaryMenuContent.component;
46
+ }
47
+
48
+ function useContextValue(): ContextValue {
49
+ const disabled = useIsNavbarMobileSidebarDisabled();
50
+ const windowSize = useWindowSize();
51
+
52
+ const shouldRender = !disabled && windowSize === 'mobile';
53
+
54
+ const [shown, setShown] = useState(false);
55
+
56
+ const toggle = useCallback(() => {
57
+ setShown((s) => !s);
58
+ }, []);
59
+
60
+ useEffect(() => {
61
+ if (windowSize === 'desktop') {
62
+ setShown(false);
63
+ }
64
+ }, [windowSize]);
65
+
66
+ return useMemo(
67
+ () => ({disabled, shouldRender, toggle, shown}),
68
+ [disabled, shouldRender, toggle, shown],
69
+ );
70
+ }
71
+
72
+ // A component hook wrapper enables conditional rendering
73
+ // See reason here: https://github.com/facebook/docusaurus/issues/10988
74
+ function OnHistoryPop({
75
+ handler,
76
+ }: {
77
+ handler: Parameters<typeof useHistoryPopHandler>[0];
78
+ }) {
79
+ useHistoryPopHandler(handler);
80
+ return null;
81
+ }
82
+
83
+ export function NavbarMobileSidebarProvider({
84
+ children,
85
+ }: {
86
+ children: ReactNode;
87
+ }): ReactNode {
88
+ const value = useContextValue();
89
+ return (
90
+ <>
91
+ {
92
+ // Close mobile sidebar on navigation pop
93
+ // Most likely firing when using the Android back button (but not only)
94
+ // Important: we can only have a single history blocker at a time
95
+ // That's why this needs to be rendered conditionally
96
+ // See bug report https://github.com/facebook/docusaurus/issues/10988
97
+ value.shown && (
98
+ <OnHistoryPop
99
+ handler={() => {
100
+ value.toggle();
101
+ // Prevent pop navigation; seems desirable enough
102
+ // See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846
103
+ return false;
104
+ }}
105
+ />
106
+ )
107
+ }
108
+ <Context value={value}>{children}</Context>
109
+ </>
110
+ );
111
+ }
112
+
113
+ export function useNavbarMobileSidebar(): ContextValue {
114
+ const context = React.useContext(Context);
115
+ if (context === undefined) {
116
+ throw new ReactContextError('NavbarMobileSidebarProvider');
117
+ }
118
+ return context;
119
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import React, {
9
+ useState,
10
+ useContext,
11
+ useEffect,
12
+ type ReactNode,
13
+ type ComponentType,
14
+ } from 'react';
15
+ import {ReactContextError, useShallowMemoObject} from '../../utils/reactUtils';
16
+
17
+ // This context represents a "global layout store". A component (usually a
18
+ // layout component) can request filling this store through
19
+ // `NavbarSecondaryMenuFiller`. It doesn't actually control rendering by itself,
20
+ // and this context should be considered internal implementation. The user-
21
+ // facing value comes from `display.tsx`, which takes the `component` and
22
+ // `props` stored here and renders the actual element.
23
+
24
+ export type NavbarSecondaryMenuComponent<Props> = ComponentType<Props>;
25
+
26
+ /** @internal */
27
+ export type Content =
28
+ | {
29
+ component: NavbarSecondaryMenuComponent<object>;
30
+ props: object;
31
+ }
32
+ | {component: null; props: null};
33
+
34
+ type ContextValue = [
35
+ content: Content,
36
+ setContent: React.Dispatch<React.SetStateAction<Content>>,
37
+ ];
38
+
39
+ const Context = React.createContext<ContextValue | null>(null);
40
+
41
+ /** @internal */
42
+ export function NavbarSecondaryMenuContentProvider({
43
+ children,
44
+ }: {
45
+ children: ReactNode;
46
+ }): ReactNode {
47
+ const value = useState({component: null, props: null});
48
+ return (
49
+ // @ts-expect-error: this context is hard to type
50
+ <Context value={value}>{children}</Context>
51
+ );
52
+ }
53
+
54
+ /** @internal */
55
+ export function useNavbarSecondaryMenuContent(): Content {
56
+ const value = useContext(Context);
57
+ if (!value) {
58
+ throw new ReactContextError('NavbarSecondaryMenuContentProvider');
59
+ }
60
+ return value[0];
61
+ }
62
+
63
+ /**
64
+ * This component renders nothing by itself, but it fills the placeholder in the
65
+ * generic secondary menu layout. This reduces coupling between the main layout
66
+ * and the specific page.
67
+ *
68
+ * This kind of feature is often called portal/teleport/gateway/outlet...
69
+ * Various unmaintained React libs exist. Most up-to-date one:
70
+ * https://github.com/gregberge/react-teleporter
71
+ * Not sure any of those is safe regarding concurrent mode.
72
+ */
73
+ export function NavbarSecondaryMenuFiller<P extends object>({
74
+ component,
75
+ props,
76
+ }: {
77
+ component: NavbarSecondaryMenuComponent<P>;
78
+ props: P;
79
+ }): ReactNode {
80
+ const context = useContext(Context);
81
+ if (!context) {
82
+ throw new ReactContextError('NavbarSecondaryMenuContentProvider');
83
+ }
84
+ const [, setContent] = context;
85
+
86
+ // To avoid useless context re-renders, props are memoized shallowly
87
+ const memoizedProps = useShallowMemoObject(props);
88
+
89
+ useEffect(() => {
90
+ // @ts-expect-error: this context is hard to type
91
+ setContent({component, props: memoizedProps});
92
+ }, [setContent, component, memoizedProps]);
93
+
94
+ useEffect(
95
+ () => () => setContent({component: null, props: null}),
96
+ [setContent],
97
+ );
98
+
99
+ return null;
100
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import React, {
9
+ useState,
10
+ useContext,
11
+ useEffect,
12
+ useMemo,
13
+ useCallback,
14
+ type ReactNode,
15
+ } from 'react';
16
+ import {ReactContextError, usePrevious} from '../../utils/reactUtils';
17
+ import {useNavbarMobileSidebar} from '../navbarMobileSidebar';
18
+ import {useNavbarSecondaryMenuContent, type Content} from './content';
19
+
20
+ type ContextValue = [
21
+ shown: boolean,
22
+ setShown: React.Dispatch<React.SetStateAction<boolean>>,
23
+ ];
24
+
25
+ const Context = React.createContext<ContextValue | null>(null);
26
+
27
+ function useContextValue(): ContextValue {
28
+ const mobileSidebar = useNavbarMobileSidebar();
29
+ const content = useNavbarSecondaryMenuContent();
30
+
31
+ const [shown, setShown] = useState(false);
32
+
33
+ const hasContent = content.component !== null;
34
+ const previousHasContent = usePrevious(hasContent);
35
+
36
+ // When content is become available for the first time (set in useEffect)
37
+ // we set this content to be shown!
38
+ useEffect(() => {
39
+ const contentBecameAvailable = hasContent && !previousHasContent;
40
+ if (contentBecameAvailable) {
41
+ setShown(true);
42
+ }
43
+ }, [hasContent, previousHasContent]);
44
+
45
+ // On sidebar close, secondary menu is set to be shown on next re-opening
46
+ // (if any secondary menu content available)
47
+ useEffect(() => {
48
+ if (!hasContent) {
49
+ setShown(false);
50
+ return;
51
+ }
52
+ if (!mobileSidebar.shown) {
53
+ setShown(true);
54
+ }
55
+ }, [mobileSidebar.shown, hasContent]);
56
+
57
+ return useMemo(() => [shown, setShown], [shown]);
58
+ }
59
+
60
+ /** @internal */
61
+ export function NavbarSecondaryMenuDisplayProvider({
62
+ children,
63
+ }: {
64
+ children: ReactNode;
65
+ }): ReactNode {
66
+ const value = useContextValue();
67
+ return <Context value={value}>{children}</Context>;
68
+ }
69
+
70
+ function renderElement(content: Content): ReactNode {
71
+ if (content.component) {
72
+ const Comp = content.component;
73
+ return <Comp {...content.props} />;
74
+ }
75
+ return undefined;
76
+ }
77
+
78
+ /** Wires the logic for rendering the mobile navbar secondary menu. */
79
+ export function useNavbarSecondaryMenu(): {
80
+ /** Whether secondary menu is displayed. */
81
+ shown: boolean;
82
+ /**
83
+ * Hide the secondary menu; fired either when hiding the entire sidebar, or
84
+ * when going back to the primary menu.
85
+ */
86
+ hide: () => void;
87
+ /** The content returned from the current secondary menu filler. */
88
+ content: ReactNode;
89
+ } {
90
+ const value = useContext(Context);
91
+ if (!value) {
92
+ throw new ReactContextError('NavbarSecondaryMenuDisplayProvider');
93
+ }
94
+ const [shown, setShown] = value;
95
+ const hide = useCallback(() => setShown(false), [setShown]);
96
+ const content = useNavbarSecondaryMenuContent();
97
+
98
+ return useMemo(
99
+ () => ({shown, hide, content: renderElement(content)}),
100
+ [hide, content, shown],
101
+ );
102
+ }