@docusaurus/theme-common 2.0.0-beta.17 → 2.0.0-beta.18

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 (254) hide show
  1. package/lib/components/Collapsible/index.d.ts +32 -4
  2. package/lib/components/Collapsible/index.d.ts.map +1 -1
  3. package/lib/components/Collapsible/index.js +12 -3
  4. package/lib/components/Collapsible/index.js.map +1 -1
  5. package/lib/components/Details/index.d.ts +6 -1
  6. package/lib/components/Details/index.d.ts.map +1 -1
  7. package/lib/components/Details/index.js +8 -4
  8. package/lib/components/Details/index.js.map +1 -1
  9. package/lib/{utils/announcementBarUtils.d.ts → contexts/announcementBar.d.ts} +7 -3
  10. package/lib/contexts/announcementBar.d.ts.map +1 -0
  11. package/lib/{utils/announcementBarUtils.js → contexts/announcementBar.js} +11 -11
  12. package/lib/contexts/announcementBar.js.map +1 -0
  13. package/lib/{utils/colorModeUtils.d.ts → contexts/colorMode.d.ts} +12 -3
  14. package/lib/contexts/colorMode.d.ts.map +1 -0
  15. package/lib/contexts/colorMode.js +114 -0
  16. package/lib/contexts/colorMode.js.map +1 -0
  17. package/lib/contexts/docSidebarItemsExpandedState.d.ts +31 -0
  18. package/lib/contexts/docSidebarItemsExpandedState.d.ts.map +1 -0
  19. package/lib/{utils → contexts}/docSidebarItemsExpandedState.js +9 -4
  20. package/lib/contexts/docSidebarItemsExpandedState.js.map +1 -0
  21. package/lib/contexts/docsPreferredVersion.d.ts +28 -0
  22. package/lib/contexts/docsPreferredVersion.d.ts.map +1 -0
  23. package/lib/contexts/docsPreferredVersion.js +125 -0
  24. package/lib/contexts/docsPreferredVersion.js.map +1 -0
  25. package/lib/contexts/docsSidebar.d.ts +20 -0
  26. package/lib/contexts/docsSidebar.d.ts.map +1 -0
  27. package/lib/contexts/docsSidebar.js +29 -0
  28. package/lib/contexts/docsSidebar.js.map +1 -0
  29. package/lib/contexts/docsVersion.d.ts +20 -0
  30. package/lib/contexts/docsVersion.d.ts.map +1 -0
  31. package/lib/contexts/docsVersion.js +26 -0
  32. package/lib/contexts/docsVersion.js.map +1 -0
  33. package/lib/contexts/navbarMobileSidebar.d.ts +31 -0
  34. package/lib/contexts/navbarMobileSidebar.d.ts.map +1 -0
  35. package/lib/contexts/navbarMobileSidebar.js +56 -0
  36. package/lib/contexts/navbarMobileSidebar.js.map +1 -0
  37. package/lib/contexts/navbarSecondaryMenu.d.ts +38 -0
  38. package/lib/contexts/navbarSecondaryMenu.d.ts.map +1 -0
  39. package/lib/contexts/navbarSecondaryMenu.js +93 -0
  40. package/lib/contexts/navbarSecondaryMenu.js.map +1 -0
  41. package/lib/{utils/tabGroupChoiceUtils.d.ts → contexts/tabGroupChoice.d.ts} +5 -3
  42. package/lib/contexts/tabGroupChoice.d.ts.map +1 -0
  43. package/lib/{utils/tabGroupChoiceUtils.js → contexts/tabGroupChoice.js} +14 -20
  44. package/lib/contexts/tabGroupChoice.js.map +1 -0
  45. package/lib/hooks/useHideableNavbar.d.ts +7 -3
  46. package/lib/hooks/useHideableNavbar.d.ts.map +1 -1
  47. package/lib/hooks/useHideableNavbar.js +8 -6
  48. package/lib/hooks/useHideableNavbar.js.map +1 -1
  49. package/lib/hooks/useKeyboardNavigation.d.ts +11 -1
  50. package/lib/hooks/useKeyboardNavigation.d.ts.map +1 -1
  51. package/lib/hooks/useKeyboardNavigation.js +11 -3
  52. package/lib/hooks/useKeyboardNavigation.js.map +1 -1
  53. package/lib/hooks/useLockBodyScroll.d.ts +5 -1
  54. package/lib/hooks/useLockBodyScroll.d.ts.map +1 -1
  55. package/lib/hooks/useLockBodyScroll.js +5 -1
  56. package/lib/hooks/useLockBodyScroll.js.map +1 -1
  57. package/lib/hooks/usePrismTheme.d.ts +5 -1
  58. package/lib/hooks/usePrismTheme.d.ts.map +1 -1
  59. package/lib/hooks/usePrismTheme.js +8 -4
  60. package/lib/hooks/usePrismTheme.js.map +1 -1
  61. package/lib/hooks/useSearchPage.d.ts +15 -4
  62. package/lib/hooks/useSearchPage.d.ts.map +1 -1
  63. package/lib/hooks/useSearchPage.js +3 -2
  64. package/lib/hooks/useSearchPage.js.map +1 -1
  65. package/lib/hooks/useTOCHighlight.d.ts +25 -0
  66. package/lib/hooks/useTOCHighlight.d.ts.map +1 -0
  67. package/lib/{utils → hooks}/useTOCHighlight.js +13 -9
  68. package/lib/hooks/useTOCHighlight.js.map +1 -0
  69. package/lib/hooks/useWindowSize.d.ts +14 -1
  70. package/lib/hooks/useWindowSize.d.ts.map +1 -1
  71. package/lib/hooks/useWindowSize.js +14 -11
  72. package/lib/hooks/useWindowSize.js.map +1 -1
  73. package/lib/index.d.ts +26 -30
  74. package/lib/index.d.ts.map +1 -1
  75. package/lib/index.js +26 -24
  76. package/lib/index.js.map +1 -1
  77. package/lib/utils/ThemeClassNames.d.ts +7 -0
  78. package/lib/utils/ThemeClassNames.d.ts.map +1 -1
  79. package/lib/utils/ThemeClassNames.js +7 -4
  80. package/lib/utils/ThemeClassNames.js.map +1 -1
  81. package/lib/utils/codeBlockUtils.d.ts +25 -2
  82. package/lib/utils/codeBlockUtils.d.ts.map +1 -1
  83. package/lib/utils/codeBlockUtils.js +36 -35
  84. package/lib/utils/codeBlockUtils.js.map +1 -1
  85. package/lib/utils/docsUtils.d.ts +25 -18
  86. package/lib/utils/docsUtils.d.ts.map +1 -1
  87. package/lib/utils/docsUtils.js +38 -56
  88. package/lib/utils/docsUtils.js.map +1 -1
  89. package/lib/utils/footerUtils.d.ts +13 -0
  90. package/lib/utils/footerUtils.d.ts.map +1 -0
  91. package/lib/utils/footerUtils.js +14 -0
  92. package/lib/utils/footerUtils.js.map +1 -0
  93. package/lib/utils/generalUtils.d.ts +4 -1
  94. package/lib/utils/generalUtils.d.ts.map +1 -1
  95. package/lib/utils/generalUtils.js +6 -3
  96. package/lib/utils/generalUtils.js.map +1 -1
  97. package/lib/utils/historyUtils.d.ts +1 -7
  98. package/lib/utils/historyUtils.d.ts.map +1 -1
  99. package/lib/utils/historyUtils.js +10 -13
  100. package/lib/utils/historyUtils.js.map +1 -1
  101. package/lib/utils/jsUtils.d.ts +1 -1
  102. package/lib/utils/jsUtils.js +1 -1
  103. package/lib/utils/metadataUtils.d.ts +38 -0
  104. package/lib/utils/metadataUtils.d.ts.map +1 -0
  105. package/lib/utils/metadataUtils.js +61 -0
  106. package/lib/utils/metadataUtils.js.map +1 -0
  107. package/lib/utils/navbarUtils.d.ts +21 -0
  108. package/lib/utils/navbarUtils.d.ts.map +1 -0
  109. package/lib/utils/navbarUtils.js +30 -0
  110. package/lib/utils/navbarUtils.js.map +1 -0
  111. package/lib/utils/reactUtils.d.ts +16 -5
  112. package/lib/utils/reactUtils.d.ts.map +1 -1
  113. package/lib/utils/reactUtils.js +28 -7
  114. package/lib/utils/reactUtils.js.map +1 -1
  115. package/lib/utils/regexpUtils.d.ts +2 -1
  116. package/lib/utils/regexpUtils.d.ts.map +1 -1
  117. package/lib/utils/regexpUtils.js +2 -1
  118. package/lib/utils/regexpUtils.js.map +1 -1
  119. package/lib/utils/routesUtils.d.ts +14 -2
  120. package/lib/utils/routesUtils.d.ts.map +1 -1
  121. package/lib/utils/routesUtils.js +20 -7
  122. package/lib/utils/routesUtils.js.map +1 -1
  123. package/lib/utils/scrollUtils.d.ts +32 -26
  124. package/lib/utils/scrollUtils.d.ts.map +1 -1
  125. package/lib/utils/scrollUtils.js +30 -17
  126. package/lib/utils/scrollUtils.js.map +1 -1
  127. package/lib/utils/searchUtils.d.ts +12 -0
  128. package/lib/utils/searchUtils.d.ts.map +1 -1
  129. package/lib/utils/searchUtils.js +34 -0
  130. package/lib/utils/searchUtils.js.map +1 -1
  131. package/lib/utils/storageUtils.d.ts +10 -7
  132. package/lib/utils/storageUtils.d.ts.map +1 -1
  133. package/lib/utils/storageUtils.js +20 -12
  134. package/lib/utils/storageUtils.js.map +1 -1
  135. package/lib/utils/tagsUtils.d.ts +5 -2
  136. package/lib/utils/tagsUtils.d.ts.map +1 -1
  137. package/lib/utils/tagsUtils.js +7 -4
  138. package/lib/utils/tagsUtils.js.map +1 -1
  139. package/lib/utils/tocUtils.d.ts +16 -0
  140. package/lib/utils/tocUtils.d.ts.map +1 -1
  141. package/lib/utils/tocUtils.js +17 -6
  142. package/lib/utils/tocUtils.js.map +1 -1
  143. package/lib/utils/useAlternatePageUtils.d.ts +20 -1
  144. package/lib/utils/useAlternatePageUtils.d.ts.map +1 -1
  145. package/lib/utils/useAlternatePageUtils.js +6 -3
  146. package/lib/utils/useAlternatePageUtils.js.map +1 -1
  147. package/lib/utils/useLocalPathname.d.ts +5 -0
  148. package/lib/utils/useLocalPathname.d.ts.map +1 -1
  149. package/lib/utils/useLocalPathname.js +6 -4
  150. package/lib/utils/useLocalPathname.js.map +1 -1
  151. package/lib/utils/useLocationChange.d.ts +7 -5
  152. package/lib/utils/useLocationChange.d.ts.map +1 -1
  153. package/lib/utils/useLocationChange.js +6 -2
  154. package/lib/utils/useLocationChange.js.map +1 -1
  155. package/lib/utils/usePluralForm.d.ts +11 -0
  156. package/lib/utils/usePluralForm.d.ts.map +1 -1
  157. package/lib/utils/usePluralForm.js +19 -24
  158. package/lib/utils/usePluralForm.js.map +1 -1
  159. package/lib/utils/useThemeConfig.d.ts +21 -11
  160. package/lib/utils/useThemeConfig.d.ts.map +1 -1
  161. package/lib/utils/useThemeConfig.js +3 -0
  162. package/lib/utils/useThemeConfig.js.map +1 -1
  163. package/package.json +8 -9
  164. package/src/components/Collapsible/index.tsx +40 -22
  165. package/src/components/Details/index.tsx +11 -6
  166. package/src/{utils/announcementBarUtils.tsx → contexts/announcementBar.tsx} +17 -18
  167. package/src/contexts/colorMode.tsx +176 -0
  168. package/src/contexts/docSidebarItemsExpandedState.tsx +55 -0
  169. package/src/contexts/docsPreferredVersion.tsx +250 -0
  170. package/src/contexts/docsSidebar.tsx +42 -0
  171. package/src/contexts/docsVersion.tsx +36 -0
  172. package/src/contexts/navbarMobileSidebar.tsx +99 -0
  173. package/src/contexts/navbarSecondaryMenu.tsx +170 -0
  174. package/src/{utils/tabGroupChoiceUtils.tsx → contexts/tabGroupChoice.tsx} +21 -28
  175. package/src/hooks/useHideableNavbar.ts +11 -11
  176. package/src/hooks/useKeyboardNavigation.ts +11 -3
  177. package/src/hooks/useLockBodyScroll.ts +5 -2
  178. package/src/hooks/usePrismTheme.ts +8 -4
  179. package/src/hooks/useSearchPage.ts +18 -5
  180. package/src/{utils → hooks}/useTOCHighlight.ts +21 -12
  181. package/src/hooks/useWindowSize.ts +14 -12
  182. package/src/index.ts +68 -56
  183. package/src/utils/ThemeClassNames.ts +10 -6
  184. package/src/utils/codeBlockUtils.ts +49 -47
  185. package/src/utils/docsUtils.tsx +48 -99
  186. package/src/utils/footerUtils.ts +18 -0
  187. package/src/utils/generalUtils.ts +6 -3
  188. package/src/utils/historyUtils.ts +11 -17
  189. package/src/utils/jsUtils.ts +1 -1
  190. package/src/utils/metadataUtils.tsx +115 -0
  191. package/src/utils/navbarUtils.tsx +40 -0
  192. package/src/utils/reactUtils.tsx +31 -8
  193. package/src/utils/regexpUtils.ts +2 -1
  194. package/src/utils/routesUtils.ts +27 -8
  195. package/src/utils/scrollUtils.tsx +44 -45
  196. package/src/utils/searchUtils.ts +49 -0
  197. package/src/utils/storageUtils.ts +21 -13
  198. package/src/utils/tagsUtils.ts +14 -7
  199. package/src/utils/tocUtils.ts +18 -7
  200. package/src/utils/useAlternatePageUtils.ts +17 -5
  201. package/src/utils/useLocalPathname.ts +6 -4
  202. package/src/utils/useLocationChange.ts +12 -10
  203. package/src/utils/usePluralForm.ts +27 -24
  204. package/src/utils/useThemeConfig.ts +16 -11
  205. package/lib/utils/announcementBarUtils.d.ts.map +0 -1
  206. package/lib/utils/announcementBarUtils.js.map +0 -1
  207. package/lib/utils/colorModeUtils.d.ts.map +0 -1
  208. package/lib/utils/colorModeUtils.js +0 -107
  209. package/lib/utils/colorModeUtils.js.map +0 -1
  210. package/lib/utils/docSidebarItemsExpandedState.d.ts +0 -17
  211. package/lib/utils/docSidebarItemsExpandedState.d.ts.map +0 -1
  212. package/lib/utils/docSidebarItemsExpandedState.js.map +0 -1
  213. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.d.ts +0 -22
  214. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.d.ts.map +0 -1
  215. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.js +0 -92
  216. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.js.map +0 -1
  217. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.d.ts +0 -14
  218. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.d.ts.map +0 -1
  219. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.js +0 -19
  220. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.js.map +0 -1
  221. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.d.ts +0 -14
  222. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.d.ts.map +0 -1
  223. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.js +0 -41
  224. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.js.map +0 -1
  225. package/lib/utils/mobileSecondaryMenu.d.ts +0 -21
  226. package/lib/utils/mobileSecondaryMenu.d.ts.map +0 -1
  227. package/lib/utils/mobileSecondaryMenu.js +0 -51
  228. package/lib/utils/mobileSecondaryMenu.js.map +0 -1
  229. package/lib/utils/pathUtils.d.ts +0 -8
  230. package/lib/utils/pathUtils.d.ts.map +0 -1
  231. package/lib/utils/pathUtils.js +0 -14
  232. package/lib/utils/pathUtils.js.map +0 -1
  233. package/lib/utils/tabGroupChoiceUtils.d.ts.map +0 -1
  234. package/lib/utils/tabGroupChoiceUtils.js.map +0 -1
  235. package/lib/utils/useContextualSearchFilters.d.ts +0 -12
  236. package/lib/utils/useContextualSearchFilters.d.ts.map +0 -1
  237. package/lib/utils/useContextualSearchFilters.js +0 -36
  238. package/lib/utils/useContextualSearchFilters.js.map +0 -1
  239. package/lib/utils/usePrevious.d.ts +0 -8
  240. package/lib/utils/usePrevious.d.ts.map +0 -1
  241. package/lib/utils/usePrevious.js +0 -16
  242. package/lib/utils/usePrevious.js.map +0 -1
  243. package/lib/utils/useTOCHighlight.d.ts +0 -14
  244. package/lib/utils/useTOCHighlight.d.ts.map +0 -1
  245. package/lib/utils/useTOCHighlight.js.map +0 -1
  246. package/src/utils/colorModeUtils.tsx +0 -158
  247. package/src/utils/docSidebarItemsExpandedState.tsx +0 -40
  248. package/src/utils/docsPreferredVersion/DocsPreferredVersionProvider.tsx +0 -166
  249. package/src/utils/docsPreferredVersion/DocsPreferredVersionStorage.ts +0 -33
  250. package/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts +0 -70
  251. package/src/utils/mobileSecondaryMenu.tsx +0 -114
  252. package/src/utils/pathUtils.ts +0 -19
  253. package/src/utils/useContextualSearchFilters.ts +0 -53
  254. package/src/utils/usePrevious.ts +0 -19
@@ -0,0 +1,250 @@
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
+ useContext,
10
+ useEffect,
11
+ useMemo,
12
+ useState,
13
+ useCallback,
14
+ type ReactNode,
15
+ } from 'react';
16
+ import {
17
+ useThemeConfig,
18
+ type DocsVersionPersistence,
19
+ } from '../utils/useThemeConfig';
20
+ import {isDocsPluginEnabled} from '../utils/docsUtils';
21
+ import {ReactContextError} from '../utils/reactUtils';
22
+ import {createStorageSlot} from '../utils/storageUtils';
23
+
24
+ import {
25
+ useAllDocsData,
26
+ useDocsData,
27
+ type GlobalPluginData,
28
+ type GlobalVersion,
29
+ } from '@docusaurus/plugin-content-docs/client';
30
+
31
+ import {DEFAULT_PLUGIN_ID} from '@docusaurus/constants';
32
+
33
+ const storageKey = (pluginId: string) => `docs-preferred-version-${pluginId}`;
34
+
35
+ const DocsPreferredVersionStorage = {
36
+ save: (
37
+ pluginId: string,
38
+ persistence: DocsVersionPersistence,
39
+ versionName: string,
40
+ ): void => {
41
+ createStorageSlot(storageKey(pluginId), {persistence}).set(versionName);
42
+ },
43
+
44
+ read: (
45
+ pluginId: string,
46
+ persistence: DocsVersionPersistence,
47
+ ): string | null =>
48
+ createStorageSlot(storageKey(pluginId), {persistence}).get(),
49
+
50
+ clear: (pluginId: string, persistence: DocsVersionPersistence): void => {
51
+ createStorageSlot(storageKey(pluginId), {persistence}).del();
52
+ },
53
+ };
54
+
55
+ type DocsPreferredVersionName = string | null;
56
+
57
+ /** State for a single docs plugin instance */
58
+ type DocsPreferredVersionPluginState = {
59
+ preferredVersionName: DocsPreferredVersionName;
60
+ };
61
+
62
+ /**
63
+ * We need to store the state in storage globally, with one preferred version
64
+ * per docs plugin instance.
65
+ */
66
+ type DocsPreferredVersionState = {
67
+ [pluginId: string]: DocsPreferredVersionPluginState;
68
+ };
69
+
70
+ /**
71
+ * Initial state is always null as we can't read local storage from node SSR
72
+ */
73
+ const getInitialState = (pluginIds: string[]): DocsPreferredVersionState =>
74
+ Object.fromEntries(pluginIds.map((id) => [id, {preferredVersionName: null}]));
75
+
76
+ /**
77
+ * Read storage for all docs plugins, assigning each doc plugin a preferred
78
+ * version (if found)
79
+ */
80
+ function readStorageState({
81
+ pluginIds,
82
+ versionPersistence,
83
+ allDocsData,
84
+ }: {
85
+ pluginIds: string[];
86
+ versionPersistence: DocsVersionPersistence;
87
+ allDocsData: {[pluginId: string]: GlobalPluginData};
88
+ }): DocsPreferredVersionState {
89
+ /**
90
+ * The storage value we read might be stale, and belong to a version that does
91
+ * not exist in the site anymore. In such case, we remove the storage value to
92
+ * avoid downstream errors.
93
+ */
94
+ function restorePluginState(
95
+ pluginId: string,
96
+ ): DocsPreferredVersionPluginState {
97
+ const preferredVersionNameUnsafe = DocsPreferredVersionStorage.read(
98
+ pluginId,
99
+ versionPersistence,
100
+ );
101
+ const pluginData = allDocsData[pluginId]!;
102
+ const versionExists = pluginData.versions.some(
103
+ (version) => version.name === preferredVersionNameUnsafe,
104
+ );
105
+ if (versionExists) {
106
+ return {preferredVersionName: preferredVersionNameUnsafe};
107
+ }
108
+ DocsPreferredVersionStorage.clear(pluginId, versionPersistence);
109
+ return {preferredVersionName: null};
110
+ }
111
+ return Object.fromEntries(
112
+ pluginIds.map((id) => [id, restorePluginState(id)]),
113
+ );
114
+ }
115
+
116
+ function useVersionPersistence(): DocsVersionPersistence {
117
+ return useThemeConfig().docs.versionPersistence;
118
+ }
119
+
120
+ type ContextValue = [
121
+ state: DocsPreferredVersionState,
122
+ api: {
123
+ savePreferredVersion: (pluginId: string, versionName: string) => void;
124
+ },
125
+ ];
126
+
127
+ const Context = React.createContext<ContextValue | null>(null);
128
+
129
+ function useContextValue(): ContextValue {
130
+ const allDocsData = useAllDocsData();
131
+ const versionPersistence = useVersionPersistence();
132
+ const pluginIds = useMemo(() => Object.keys(allDocsData), [allDocsData]);
133
+
134
+ // Initial state is empty, as we can't read browser storage in node/SSR
135
+ const [state, setState] = useState(() => getInitialState(pluginIds));
136
+
137
+ // On mount, we set the state read from browser storage
138
+ useEffect(() => {
139
+ setState(readStorageState({allDocsData, versionPersistence, pluginIds}));
140
+ }, [allDocsData, versionPersistence, pluginIds]);
141
+
142
+ // The API that we expose to consumer hooks (memo for constant object)
143
+ const api = useMemo(() => {
144
+ function savePreferredVersion(pluginId: string, versionName: string) {
145
+ DocsPreferredVersionStorage.save(
146
+ pluginId,
147
+ versionPersistence,
148
+ versionName,
149
+ );
150
+ setState((s) => ({
151
+ ...s,
152
+ [pluginId]: {preferredVersionName: versionName},
153
+ }));
154
+ }
155
+
156
+ return {
157
+ savePreferredVersion,
158
+ };
159
+ }, [versionPersistence]);
160
+
161
+ return [state, api];
162
+ }
163
+
164
+ function DocsPreferredVersionContextProviderUnsafe({
165
+ children,
166
+ }: {
167
+ children: ReactNode;
168
+ }): JSX.Element {
169
+ const value = useContextValue();
170
+ return <Context.Provider value={value}>{children}</Context.Provider>;
171
+ }
172
+
173
+ /**
174
+ * This is a maybe-layer. If the docs plugin is not enabled, this provider is a
175
+ * simple pass-through.
176
+ */
177
+ export function DocsPreferredVersionContextProvider({
178
+ children,
179
+ }: {
180
+ children: JSX.Element;
181
+ }): JSX.Element {
182
+ if (isDocsPluginEnabled) {
183
+ return (
184
+ <DocsPreferredVersionContextProviderUnsafe>
185
+ {children}
186
+ </DocsPreferredVersionContextProviderUnsafe>
187
+ );
188
+ }
189
+ return children;
190
+ }
191
+
192
+ function useDocsPreferredVersionContext(): ContextValue {
193
+ const value = useContext(Context);
194
+ if (!value) {
195
+ throw new ReactContextError('DocsPreferredVersionContextProvider');
196
+ }
197
+ return value;
198
+ }
199
+
200
+ /**
201
+ * Returns a read-write interface to a plugin's preferred version.
202
+ * Note, the `preferredVersion` attribute will always be `null` before mount.
203
+ */
204
+ export function useDocsPreferredVersion(
205
+ pluginId: string | undefined = DEFAULT_PLUGIN_ID,
206
+ ): {
207
+ preferredVersion: GlobalVersion | null;
208
+ savePreferredVersionName: (versionName: string) => void;
209
+ } {
210
+ const docsData = useDocsData(pluginId);
211
+ const [state, api] = useDocsPreferredVersionContext();
212
+
213
+ const {preferredVersionName} = state[pluginId]!;
214
+
215
+ const preferredVersion =
216
+ docsData.versions.find(
217
+ (version) => version.name === preferredVersionName,
218
+ ) ?? null;
219
+
220
+ const savePreferredVersionName = useCallback(
221
+ (versionName: string) => {
222
+ api.savePreferredVersion(pluginId, versionName);
223
+ },
224
+ [api, pluginId],
225
+ );
226
+
227
+ return {preferredVersion, savePreferredVersionName};
228
+ }
229
+
230
+ export function useDocsPreferredVersionByPluginId(): {
231
+ [pluginId: string]: GlobalVersion | null;
232
+ } {
233
+ const allDocsData = useAllDocsData();
234
+ const [state] = useDocsPreferredVersionContext();
235
+
236
+ function getPluginIdPreferredVersion(pluginId: string) {
237
+ const docsData = allDocsData[pluginId]!;
238
+ const {preferredVersionName} = state[pluginId]!;
239
+
240
+ return (
241
+ docsData.versions.find(
242
+ (version) => version.name === preferredVersionName,
243
+ ) ?? null
244
+ );
245
+ }
246
+ const pluginIds = Object.keys(allDocsData);
247
+ return Object.fromEntries(
248
+ pluginIds.map((id) => [id, getPluginIdPreferredVersion(id)]),
249
+ );
250
+ }
@@ -0,0 +1,42 @@
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, {type ReactNode, useContext} from 'react';
9
+ import type {PropSidebar} from '@docusaurus/plugin-content-docs';
10
+ import {ReactContextError} from '../utils/reactUtils';
11
+
12
+ // Using a Symbol because null is a valid context value (a doc with no sidebar)
13
+ // Inspired by https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx
14
+ const EmptyContext: unique symbol = Symbol('EmptyContext');
15
+
16
+ const Context = React.createContext<PropSidebar | null | typeof EmptyContext>(
17
+ EmptyContext,
18
+ );
19
+
20
+ /**
21
+ * Provide the current sidebar to your children.
22
+ */
23
+ export function DocsSidebarProvider({
24
+ children,
25
+ sidebar,
26
+ }: {
27
+ children: ReactNode;
28
+ sidebar: PropSidebar | null;
29
+ }): JSX.Element {
30
+ return <Context.Provider value={sidebar}>{children}</Context.Provider>;
31
+ }
32
+
33
+ /**
34
+ * Gets the sidebar that's currently displayed, or `null` if there isn't one
35
+ */
36
+ export function useDocsSidebar(): PropSidebar | null {
37
+ const sidebar = useContext(Context);
38
+ if (sidebar === EmptyContext) {
39
+ throw new ReactContextError('DocsSidebarProvider');
40
+ }
41
+ return sidebar;
42
+ }
@@ -0,0 +1,36 @@
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, {type ReactNode, useContext} from 'react';
9
+ import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
10
+ import {ReactContextError} from '../utils/reactUtils';
11
+
12
+ const Context = React.createContext<PropVersionMetadata | null>(null);
13
+
14
+ /**
15
+ * Provide the current version's metadata to your children.
16
+ */
17
+ export function DocsVersionProvider({
18
+ children,
19
+ version,
20
+ }: {
21
+ children: ReactNode;
22
+ version: PropVersionMetadata | null;
23
+ }): JSX.Element {
24
+ return <Context.Provider value={version}>{children}</Context.Provider>;
25
+ }
26
+
27
+ /**
28
+ * Gets the version metadata of the current doc page.
29
+ */
30
+ export function useDocsVersion(): PropVersionMetadata {
31
+ const version = useContext(Context);
32
+ if (version === null) {
33
+ throw new ReactContextError('DocsVersionProvider');
34
+ }
35
+ return version;
36
+ }
@@ -0,0 +1,99 @@
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 {useWindowSize} from '../hooks/useWindowSize';
16
+ import {useHistoryPopHandler} from '../utils/historyUtils';
17
+ import {useActivePlugin} from '@docusaurus/plugin-content-docs/client';
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 activeDocPlugin = useActivePlugin();
44
+ const {items} = useThemeConfig().navbar;
45
+ return items.length === 0 && !activeDocPlugin;
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
+ // Close mobile sidebar on navigation pop
57
+ // Most likely firing when using the Android back button (but not only)
58
+ useHistoryPopHandler(() => {
59
+ if (shown) {
60
+ setShown(false);
61
+ // Should we prevent the navigation here?
62
+ // See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846
63
+ return false; // prevent pop navigation
64
+ }
65
+ return undefined;
66
+ });
67
+
68
+ const toggle = useCallback(() => {
69
+ setShown((s) => !s);
70
+ }, []);
71
+
72
+ useEffect(() => {
73
+ if (windowSize === 'desktop') {
74
+ setShown(false);
75
+ }
76
+ }, [windowSize]);
77
+
78
+ return useMemo(
79
+ () => ({disabled, shouldRender, toggle, shown}),
80
+ [disabled, shouldRender, toggle, shown],
81
+ );
82
+ }
83
+
84
+ export function NavbarMobileSidebarProvider({
85
+ children,
86
+ }: {
87
+ children: ReactNode;
88
+ }): JSX.Element {
89
+ const value = useContextValue();
90
+ return <Context.Provider value={value}>{children}</Context.Provider>;
91
+ }
92
+
93
+ export function useNavbarMobileSidebar(): ContextValue {
94
+ const context = React.useContext(Context);
95
+ if (context === undefined) {
96
+ throw new ReactContextError('NavbarMobileSidebarProvider');
97
+ }
98
+ return context;
99
+ }
@@ -0,0 +1,170 @@
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
+ type ComponentType,
16
+ } from 'react';
17
+ import {ReactContextError, usePrevious} from '../utils/reactUtils';
18
+ import {useNavbarMobileSidebar} from './navbarMobileSidebar';
19
+
20
+ export type NavbarSecondaryMenuComponent<Props> = ComponentType<Props>;
21
+
22
+ type State = {
23
+ shown: boolean;
24
+ content:
25
+ | {
26
+ component: NavbarSecondaryMenuComponent<object>;
27
+ props: object;
28
+ }
29
+ | {component: null; props: null};
30
+ };
31
+
32
+ const InitialState: State = {
33
+ shown: false,
34
+ content: {component: null, props: null},
35
+ };
36
+
37
+ type ContextValue = [
38
+ state: State,
39
+ setState: React.Dispatch<React.SetStateAction<State>>,
40
+ ];
41
+
42
+ const Context = React.createContext<ContextValue | null>(null);
43
+
44
+ function useContextValue(): ContextValue {
45
+ const mobileSidebar = useNavbarMobileSidebar();
46
+
47
+ const [state, setState] = useState<State>(InitialState);
48
+
49
+ const setShown = (shown: boolean) => setState((s) => ({...s, shown}));
50
+
51
+ const hasContent = state.content?.component !== null;
52
+ const previousHasContent = usePrevious(state.content?.component !== null);
53
+
54
+ // When content is become available for the first time (set in useEffect)
55
+ // we set this content to be shown!
56
+ useEffect(() => {
57
+ const contentBecameAvailable = hasContent && !previousHasContent;
58
+ if (contentBecameAvailable) {
59
+ setShown(true);
60
+ }
61
+ }, [hasContent, previousHasContent]);
62
+
63
+ // On sidebar close, secondary menu is set to be shown on next re-opening
64
+ // (if any secondary menu content available)
65
+ useEffect(() => {
66
+ if (!hasContent) {
67
+ setShown(false);
68
+ return;
69
+ }
70
+ if (!mobileSidebar.shown) {
71
+ setShown(true);
72
+ }
73
+ }, [mobileSidebar.shown, hasContent]);
74
+
75
+ return [state, setState];
76
+ }
77
+
78
+ export function NavbarSecondaryMenuProvider({
79
+ children,
80
+ }: {
81
+ children: ReactNode;
82
+ }): JSX.Element {
83
+ const value = useContextValue();
84
+ return <Context.Provider value={value}>{children}</Context.Provider>;
85
+ }
86
+
87
+ function useNavbarSecondaryMenuContext(): ContextValue {
88
+ const value = useContext(Context);
89
+ if (value === null) {
90
+ throw new ReactContextError('MobileSecondaryMenuProvider');
91
+ }
92
+ return value;
93
+ }
94
+
95
+ function useShallowMemoizedObject<O>(obj: O) {
96
+ return useMemo(
97
+ () => obj,
98
+ // Is this safe?
99
+ // eslint-disable-next-line react-hooks/exhaustive-deps
100
+ [...Object.keys(obj), ...Object.values(obj)],
101
+ );
102
+ }
103
+
104
+ /**
105
+ * This component renders nothing by itself, but it fills the placeholder in the
106
+ * generic secondary menu layout. This reduces coupling between the main layout
107
+ * and the specific page.
108
+ *
109
+ * This kind of feature is often called portal/teleport/gateway/outlet...
110
+ * Various unmaintained React libs exist. Most up-to-date one:
111
+ * https://github.com/gregberge/react-teleporter
112
+ * Not sure any of those is safe regarding concurrent mode.
113
+ */
114
+ export function NavbarSecondaryMenuFiller<P extends object>({
115
+ component,
116
+ props,
117
+ }: {
118
+ component: NavbarSecondaryMenuComponent<P>;
119
+ props: P;
120
+ }): JSX.Element | null {
121
+ const [, setState] = useNavbarSecondaryMenuContext();
122
+
123
+ // To avoid useless context re-renders, props are memoized shallowly
124
+ const memoizedProps = useShallowMemoizedObject(props);
125
+
126
+ useEffect(() => {
127
+ // @ts-expect-error: context is not 100% type-safe but it's ok
128
+ setState((s) => ({...s, content: {component, props: memoizedProps}}));
129
+ }, [setState, component, memoizedProps]);
130
+
131
+ useEffect(
132
+ () => () => setState((s) => ({...s, component: null, props: null})),
133
+ [setState],
134
+ );
135
+
136
+ return null;
137
+ }
138
+
139
+ function renderElement(state: State): JSX.Element | undefined {
140
+ if (state.content?.component) {
141
+ const Comp = state.content.component;
142
+ return <Comp {...state.content.props} />;
143
+ }
144
+ return undefined;
145
+ }
146
+
147
+ /** Wires the logic for rendering the mobile navbar secondary menu. */
148
+ export function useNavbarSecondaryMenu(): {
149
+ /** Whether secondary menu is displayed. */
150
+ shown: boolean;
151
+ /**
152
+ * Hide the secondary menu; fired either when hiding the entire sidebar, or
153
+ * when going back to the primary menu.
154
+ */
155
+ hide: () => void;
156
+ /** The content returned from the current secondary menu filler. */
157
+ content: JSX.Element | undefined;
158
+ } {
159
+ const [state, setState] = useNavbarSecondaryMenuContext();
160
+
161
+ const hide = useCallback(
162
+ () => setState((s) => ({...s, shown: false})),
163
+ [setState],
164
+ );
165
+
166
+ return useMemo(
167
+ () => ({shown: state.shown, hide, content: renderElement(state)}),
168
+ [hide, state],
169
+ );
170
+ }
@@ -9,26 +9,25 @@ import React, {
9
9
  useState,
10
10
  useCallback,
11
11
  useEffect,
12
- createContext,
13
12
  useMemo,
14
13
  useContext,
15
14
  type ReactNode,
16
15
  } from 'react';
17
- import {createStorageSlot, listStorageKeys} from './storageUtils';
18
- import {ReactContextError} from './reactUtils';
16
+ import {createStorageSlot, listStorageKeys} from '../utils/storageUtils';
17
+ import {ReactContextError} from '../utils/reactUtils';
19
18
 
20
19
  const TAB_CHOICE_PREFIX = 'docusaurus.tab.';
21
20
 
22
- type TabGroupChoiceContextValue = {
21
+ type ContextValue = {
22
+ /** A map from `groupId` to the `value` of the saved choice. */
23
23
  readonly tabGroupChoices: {readonly [groupId: string]: string};
24
+ /** Set the new choice value of a group. */
24
25
  readonly setTabGroupChoices: (groupId: string, newChoice: string) => void;
25
26
  };
26
27
 
27
- const TabGroupChoiceContext = createContext<
28
- TabGroupChoiceContextValue | undefined
29
- >(undefined);
28
+ const Context = React.createContext<ContextValue | undefined>(undefined);
30
29
 
31
- function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue {
30
+ function useContextValue(): ContextValue {
32
31
  const [tabGroupChoices, setChoices] = useState<{
33
32
  readonly [groupId: string]: string;
34
33
  }>({});
@@ -38,7 +37,7 @@ function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue {
38
37
 
39
38
  useEffect(() => {
40
39
  try {
41
- const localStorageChoices: Record<string, string> = {};
40
+ const localStorageChoices: {[groupId: string]: string} = {};
42
41
  listStorageKeys().forEach((storageKey) => {
43
42
  if (storageKey.startsWith(TAB_CHOICE_PREFIX)) {
44
43
  const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length);
@@ -51,13 +50,18 @@ function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue {
51
50
  }
52
51
  }, []);
53
52
 
54
- return {
55
- tabGroupChoices,
56
- setTabGroupChoices: (groupId: string, newChoice: string) => {
53
+ const setTabGroupChoices = useCallback(
54
+ (groupId: string, newChoice: string) => {
57
55
  setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice}));
58
56
  setChoiceSyncWithLocalStorage(groupId, newChoice);
59
57
  },
60
- };
58
+ [setChoiceSyncWithLocalStorage],
59
+ );
60
+
61
+ return useMemo(
62
+ () => ({tabGroupChoices, setTabGroupChoices}),
63
+ [tabGroupChoices, setTabGroupChoices],
64
+ );
61
65
  }
62
66
 
63
67
  export function TabGroupChoiceProvider({
@@ -65,23 +69,12 @@ export function TabGroupChoiceProvider({
65
69
  }: {
66
70
  children: ReactNode;
67
71
  }): JSX.Element {
68
- const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoiceContextValue();
69
- const contextValue = useMemo(
70
- () => ({
71
- tabGroupChoices,
72
- setTabGroupChoices,
73
- }),
74
- [tabGroupChoices, setTabGroupChoices],
75
- );
76
- return (
77
- <TabGroupChoiceContext.Provider value={contextValue}>
78
- {children}
79
- </TabGroupChoiceContext.Provider>
80
- );
72
+ const value = useContextValue();
73
+ return <Context.Provider value={value}>{children}</Context.Provider>;
81
74
  }
82
75
 
83
- export function useTabGroupChoice(): TabGroupChoiceContextValue {
84
- const context = useContext(TabGroupChoiceContext);
76
+ export function useTabGroupChoice(): ContextValue {
77
+ const context = useContext(Context);
85
78
  if (context == null) {
86
79
  throw new ReactContextError('TabGroupChoiceProvider');
87
80
  }