@docusaurus/theme-classic 2.3.1 → 2.4.1

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 (46) hide show
  1. package/lib/index.js +12 -3
  2. package/lib/theme/CodeBlock/Content/styles.module.css +2 -1
  3. package/lib/theme/CodeBlock/CopyButton/index.js +4 -6
  4. package/lib/theme/CodeBlock/CopyButton/styles.module.css +1 -1
  5. package/lib/theme/CodeBlock/WordWrapButton/index.js +2 -9
  6. package/lib/theme/ColorModeToggle/index.d.ts +1 -1
  7. package/lib/theme/ColorModeToggle/index.js +2 -1
  8. package/lib/theme/DocCard/index.js +13 -10
  9. package/lib/theme/DocPage/Layout/Sidebar/index.js +6 -1
  10. package/lib/theme/DocPage/Layout/styles.module.css +1 -0
  11. package/lib/theme/DocPaginator/index.js +1 -1
  12. package/lib/theme/ErrorPageContent.js +13 -9
  13. package/lib/theme/Icon/Copy/index.d.ts +9 -0
  14. package/lib/theme/Icon/Copy/index.js +17 -0
  15. package/lib/theme/Icon/Success/index.d.ts +9 -0
  16. package/lib/theme/Icon/Success/index.js +17 -0
  17. package/lib/theme/Icon/WordWrap/index.d.ts +9 -0
  18. package/lib/theme/Icon/WordWrap/index.js +17 -0
  19. package/lib/theme/Navbar/ColorModeToggle/index.js +5 -0
  20. package/lib/theme/Navbar/ColorModeToggle/styles.module.css +10 -0
  21. package/lib/theme/Navbar/Content/index.js +13 -2
  22. package/lib/theme/NavbarItem/DropdownNavbarItem.js +2 -16
  23. package/lib/theme/Tabs/index.js +5 -4
  24. package/lib/translations.js +41 -2
  25. package/package.json +15 -15
  26. package/src/index.ts +13 -3
  27. package/src/theme/CodeBlock/Content/styles.module.css +2 -1
  28. package/src/theme/CodeBlock/CopyButton/index.tsx +4 -6
  29. package/src/theme/CodeBlock/CopyButton/styles.module.css +1 -1
  30. package/src/theme/CodeBlock/WordWrapButton/index.tsx +2 -9
  31. package/src/theme/ColorModeToggle/index.tsx +7 -1
  32. package/src/theme/DocCard/index.tsx +13 -10
  33. package/src/theme/DocPage/Layout/Sidebar/index.tsx +6 -1
  34. package/src/theme/DocPage/Layout/styles.module.css +1 -0
  35. package/src/theme/DocPaginator/index.tsx +1 -1
  36. package/src/theme/ErrorPageContent.tsx +13 -9
  37. package/src/theme/Icon/Copy/index.tsx +20 -0
  38. package/src/theme/Icon/Success/index.tsx +20 -0
  39. package/src/theme/Icon/WordWrap/index.tsx +20 -0
  40. package/src/theme/Navbar/ColorModeToggle/index.tsx +5 -0
  41. package/src/theme/Navbar/ColorModeToggle/styles.module.css +10 -0
  42. package/src/theme/Navbar/Content/index.tsx +13 -2
  43. package/src/theme/NavbarItem/DropdownNavbarItem.tsx +5 -17
  44. package/src/theme/Tabs/index.tsx +7 -5
  45. package/src/theme-classic.d.ts +25 -0
  46. package/src/translations.ts +46 -2
package/lib/index.js CHANGED
@@ -18,6 +18,7 @@ const ContextReplacementPlugin = requireFromDocusaurusCore('webpack/lib/ContextR
18
18
  // Need to be inlined to prevent dark mode FOUC
19
19
  // Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
20
20
  const ThemeStorageKey = 'theme';
21
+ const ThemeQueryStringKey = 'docusaurus-theme';
21
22
  const noFlashColorMode = ({ defaultMode, respectPrefersColorScheme, }) =>
22
23
  /* language=js */
23
24
  `(function() {
@@ -28,6 +29,14 @@ const noFlashColorMode = ({ defaultMode, respectPrefersColorScheme, }) =>
28
29
  document.documentElement.setAttribute('data-theme', theme);
29
30
  }
30
31
 
32
+ function getQueryStringTheme() {
33
+ var theme = null;
34
+ try {
35
+ theme = new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
36
+ } catch(e) {}
37
+ return theme;
38
+ }
39
+
31
40
  function getStoredTheme() {
32
41
  var theme = null;
33
42
  try {
@@ -36,9 +45,9 @@ const noFlashColorMode = ({ defaultMode, respectPrefersColorScheme, }) =>
36
45
  return theme;
37
46
  }
38
47
 
39
- var storedTheme = getStoredTheme();
40
- if (storedTheme !== null) {
41
- setDataThemeAttribute(storedTheme);
48
+ var initialTheme = getQueryStringTheme() || getStoredTheme();
49
+ if (initialTheme !== null) {
50
+ setDataThemeAttribute(initialTheme);
42
51
  } else {
43
52
  if (
44
53
  respectPrefersColorScheme &&
@@ -59,6 +59,7 @@
59
59
  display: flex;
60
60
  column-gap: 0.2rem;
61
61
  position: absolute;
62
+ /* rtl:ignore */
62
63
  right: calc(var(--ifm-pre-padding) / 2);
63
64
  top: calc(var(--ifm-pre-padding) / 2);
64
65
  }
@@ -72,7 +73,7 @@
72
73
  border-radius: var(--ifm-global-radius);
73
74
  padding: 0.4rem;
74
75
  line-height: 0;
75
- transition: opacity 200ms ease-in-out;
76
+ transition: opacity var(--ifm-transition-fast) ease-in-out;
76
77
  opacity: 0;
77
78
  }
78
79
 
@@ -9,6 +9,8 @@ import clsx from 'clsx';
9
9
  // @ts-expect-error: TODO, we need to make theme-classic have type: module
10
10
  import copy from 'copy-text-to-clipboard';
11
11
  import {translate} from '@docusaurus/Translate';
12
+ import IconCopy from '@theme/Icon/Copy';
13
+ import IconSuccess from '@theme/Icon/Success';
12
14
  import styles from './styles.module.css';
13
15
  export default function CopyButton({code, className}) {
14
16
  const [isCopied, setIsCopied] = useState(false);
@@ -50,12 +52,8 @@ export default function CopyButton({code, className}) {
50
52
  )}
51
53
  onClick={handleCopyCode}>
52
54
  <span className={styles.copyButtonIcons} aria-hidden="true">
53
- <svg className={styles.copyButtonIcon} viewBox="0 0 24 24">
54
- <path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />
55
- </svg>
56
- <svg className={styles.copyButtonSuccessIcon} viewBox="0 0 24 24">
57
- <path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
58
- </svg>
55
+ <IconCopy className={styles.copyButtonIcon} />
56
+ <IconSuccess className={styles.copyButtonSuccessIcon} />
59
57
  </span>
60
58
  </button>
61
59
  );
@@ -24,7 +24,7 @@
24
24
  opacity: inherit;
25
25
  width: inherit;
26
26
  height: inherit;
27
- transition: all 0.15s ease;
27
+ transition: all var(--ifm-transition-fast) ease;
28
28
  }
29
29
 
30
30
  .copyButtonSuccessIcon {
@@ -7,6 +7,7 @@
7
7
  import React from 'react';
8
8
  import clsx from 'clsx';
9
9
  import {translate} from '@docusaurus/Translate';
10
+ import IconWordWrap from '@theme/Icon/WordWrap';
10
11
  import styles from './styles.module.css';
11
12
  export default function WordWrapButton({className, onClick, isEnabled}) {
12
13
  const title = translate({
@@ -26,15 +27,7 @@ export default function WordWrapButton({className, onClick, isEnabled}) {
26
27
  )}
27
28
  aria-label={title}
28
29
  title={title}>
29
- <svg
30
- className={styles.wordWrapButtonIcon}
31
- viewBox="0 0 24 24"
32
- aria-hidden="true">
33
- <path
34
- fill="currentColor"
35
- d="M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"
36
- />
37
- </svg>
30
+ <IconWordWrap className={styles.wordWrapButtonIcon} aria-hidden="true" />
38
31
  </button>
39
32
  );
40
33
  }
@@ -6,6 +6,6 @@
6
6
  */
7
7
  import React from 'react';
8
8
  import type { Props } from '@theme/ColorModeToggle';
9
- declare function ColorModeToggle({ className, value, onChange }: Props): JSX.Element;
9
+ declare function ColorModeToggle({ className, buttonClassName, value, onChange, }: Props): JSX.Element;
10
10
  declare const _default: React.MemoExoticComponent<typeof ColorModeToggle>;
11
11
  export default _default;
@@ -11,7 +11,7 @@ import {translate} from '@docusaurus/Translate';
11
11
  import IconLightMode from '@theme/Icon/LightMode';
12
12
  import IconDarkMode from '@theme/Icon/DarkMode';
13
13
  import styles from './styles.module.css';
14
- function ColorModeToggle({className, value, onChange}) {
14
+ function ColorModeToggle({className, buttonClassName, value, onChange}) {
15
15
  const isBrowser = useIsBrowser();
16
16
  const title = translate(
17
17
  {
@@ -41,6 +41,7 @@ function ColorModeToggle({className, value, onChange}) {
41
41
  'clean-btn',
42
42
  styles.toggleButton,
43
43
  !isBrowser && styles.toggleButtonDisabled,
44
+ buttonClassName,
44
45
  )}
45
46
  type="button"
46
47
  onClick={() => onChange(value === 'dark' ? 'light' : 'dark')}
@@ -50,15 +50,18 @@ function CardCategory({item}) {
50
50
  href={href}
51
51
  icon="🗃️"
52
52
  title={item.label}
53
- description={translate(
54
- {
55
- message: '{count} items',
56
- id: 'theme.docs.DocCard.categoryDescription',
57
- description:
58
- 'The default description for a category card in the generated index about how many items this category includes',
59
- },
60
- {count: item.items.length},
61
- )}
53
+ description={
54
+ item.description ??
55
+ translate(
56
+ {
57
+ message: '{count} items',
58
+ id: 'theme.docs.DocCard.categoryDescription',
59
+ description:
60
+ 'The default description for a category card in the generated index about how many items this category includes',
61
+ },
62
+ {count: item.items.length},
63
+ )
64
+ }
62
65
  />
63
66
  );
64
67
  }
@@ -70,7 +73,7 @@ function CardLink({item}) {
70
73
  href={item.href}
71
74
  icon={icon}
72
75
  title={item.label}
73
- description={doc?.description}
76
+ description={item.description ?? doc?.description}
74
77
  />
75
78
  );
76
79
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import React, {useState, useCallback} from 'react';
8
8
  import clsx from 'clsx';
9
- import {ThemeClassNames} from '@docusaurus/theme-common';
9
+ import {prefersReducedMotion, ThemeClassNames} from '@docusaurus/theme-common';
10
10
  import {useDocsSidebar} from '@docusaurus/theme-common/internal';
11
11
  import {useLocation} from '@docusaurus/router';
12
12
  import DocSidebar from '@theme/DocSidebar';
@@ -34,6 +34,11 @@ export default function DocPageLayoutSidebar({
34
34
  if (hiddenSidebar) {
35
35
  setHiddenSidebar(false);
36
36
  }
37
+ // onTransitionEnd won't fire when sidebar animation is disabled
38
+ // fixes https://github.com/facebook/docusaurus/issues/8918
39
+ if (!hiddenSidebar && prefersReducedMotion()) {
40
+ setHiddenSidebar(true);
41
+ }
37
42
  setHiddenSidebarContainer((value) => !value);
38
43
  }, [setHiddenSidebarContainer, hiddenSidebar]);
39
44
  return (
@@ -8,6 +8,7 @@
8
8
  .docPage {
9
9
  display: flex;
10
10
  width: 100%;
11
+ flex: 1 0;
11
12
  }
12
13
 
13
14
  .docsWrapper {
@@ -14,7 +14,7 @@ export default function DocPaginator(props) {
14
14
  className="pagination-nav docusaurus-mt-lg"
15
15
  aria-label={translate({
16
16
  id: 'theme.docs.paginator.navAriaLabel',
17
- message: 'Docs pages navigation',
17
+ message: 'Docs pages',
18
18
  description: 'The ARIA label for the docs pagination',
19
19
  })}>
20
20
  {previous && (
@@ -6,6 +6,10 @@
6
6
  */
7
7
  import React from 'react';
8
8
  import Translate from '@docusaurus/Translate';
9
+ import {
10
+ ErrorBoundaryError,
11
+ ErrorBoundaryTryAgainButton,
12
+ } from '@docusaurus/theme-common';
9
13
  export default function ErrorPageContent({error, tryAgain}) {
10
14
  return (
11
15
  <main className="container margin-vert--xl">
@@ -18,15 +22,15 @@ export default function ErrorPageContent({error, tryAgain}) {
18
22
  This page crashed.
19
23
  </Translate>
20
24
  </h1>
21
- <p>{error.message}</p>
22
- <div>
23
- <button type="button" onClick={tryAgain}>
24
- <Translate
25
- id="theme.ErrorPageContent.tryAgain"
26
- description="The label of the button to try again when the page crashed">
27
- Try again
28
- </Translate>
29
- </button>
25
+ <div className="margin-vert--lg">
26
+ <ErrorBoundaryTryAgainButton
27
+ onClick={tryAgain}
28
+ className="button button--primary shadow--lw"
29
+ />
30
+ </div>
31
+ <hr />
32
+ <div className="margin-vert--md">
33
+ <ErrorBoundaryError error={error} />
30
34
  </div>
31
35
  </div>
32
36
  </div>
@@ -0,0 +1,9 @@
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
+ /// <reference types="react" />
8
+ import type { Props } from '@theme/Icon/Copy';
9
+ export default function IconCopy(props: Props): JSX.Element;
@@ -0,0 +1,17 @@
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
+ import React from 'react';
8
+ export default function IconCopy(props) {
9
+ return (
10
+ <svg viewBox="0 0 24 24" {...props}>
11
+ <path
12
+ fill="currentColor"
13
+ d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
14
+ />
15
+ </svg>
16
+ );
17
+ }
@@ -0,0 +1,9 @@
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
+ /// <reference types="react" />
8
+ import type { Props } from '@theme/Icon/Success';
9
+ export default function IconSuccess(props: Props): JSX.Element;
@@ -0,0 +1,17 @@
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
+ import React from 'react';
8
+ export default function IconSuccess(props) {
9
+ return (
10
+ <svg viewBox="0 0 24 24" {...props}>
11
+ <path
12
+ fill="currentColor"
13
+ d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"
14
+ />
15
+ </svg>
16
+ );
17
+ }
@@ -0,0 +1,9 @@
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
+ /// <reference types="react" />
8
+ import type { Props } from '@theme/Icon/WordWrap';
9
+ export default function IconWordWrap(props: Props): JSX.Element;
@@ -0,0 +1,17 @@
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
+ import React from 'react';
8
+ export default function IconWordWrap(props) {
9
+ return (
10
+ <svg viewBox="0 0 24 24" {...props}>
11
+ <path
12
+ fill="currentColor"
13
+ d="M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"
14
+ />
15
+ </svg>
16
+ );
17
+ }
@@ -7,7 +7,9 @@
7
7
  import React from 'react';
8
8
  import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
9
9
  import ColorModeToggle from '@theme/ColorModeToggle';
10
+ import styles from './styles.module.css';
10
11
  export default function NavbarColorModeToggle({className}) {
12
+ const navbarStyle = useThemeConfig().navbar.style;
11
13
  const disabled = useThemeConfig().colorMode.disableSwitch;
12
14
  const {colorMode, setColorMode} = useColorMode();
13
15
  if (disabled) {
@@ -16,6 +18,9 @@ export default function NavbarColorModeToggle({className}) {
16
18
  return (
17
19
  <ColorModeToggle
18
20
  className={className}
21
+ buttonClassName={
22
+ navbarStyle === 'dark' ? styles.darkNavbarColorModeToggle : undefined
23
+ }
19
24
  value={colorMode}
20
25
  onChange={setColorMode}
21
26
  />
@@ -0,0 +1,10 @@
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
+ .darkNavbarColorModeToggle:hover {
9
+ background: var(--ifm-color-gray-800);
10
+ }
@@ -5,7 +5,7 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import React from 'react';
8
- import {useThemeConfig} from '@docusaurus/theme-common';
8
+ import {useThemeConfig, ErrorCauseBoundary} from '@docusaurus/theme-common';
9
9
  import {
10
10
  splitNavbarItems,
11
11
  useNavbarMobileSidebar,
@@ -25,7 +25,18 @@ function NavbarItems({items}) {
25
25
  return (
26
26
  <>
27
27
  {items.map((item, i) => (
28
- <NavbarItem {...item} key={i} />
28
+ <ErrorCauseBoundary
29
+ key={i}
30
+ onError={(error) =>
31
+ new Error(
32
+ `A theme navbar item failed to render.
33
+ Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:
34
+ ${JSON.stringify(item, null, 2)}`,
35
+ {cause: error},
36
+ )
37
+ }>
38
+ <NavbarItem {...item} />
39
+ </ErrorCauseBoundary>
29
40
  ))}
30
41
  </>
31
42
  );
@@ -47,9 +47,11 @@ function DropdownNavbarItemDesktop({
47
47
  };
48
48
  document.addEventListener('mousedown', handleClickOutside);
49
49
  document.addEventListener('touchstart', handleClickOutside);
50
+ document.addEventListener('focusin', handleClickOutside);
50
51
  return () => {
51
52
  document.removeEventListener('mousedown', handleClickOutside);
52
53
  document.removeEventListener('touchstart', handleClickOutside);
54
+ document.removeEventListener('focusin', handleClickOutside);
53
55
  };
54
56
  }, [dropdownRef]);
55
57
  return (
@@ -79,22 +81,6 @@ function DropdownNavbarItemDesktop({
79
81
  {items.map((childItemProps, i) => (
80
82
  <NavbarItem
81
83
  isDropdownItem
82
- onKeyDown={(e) => {
83
- if (i === items.length - 1 && e.key === 'Tab') {
84
- e.preventDefault();
85
- setShowDropdown(false);
86
- const nextNavbarItem = dropdownRef.current.nextElementSibling;
87
- if (nextNavbarItem) {
88
- const targetItem =
89
- nextNavbarItem instanceof HTMLAnchorElement
90
- ? nextNavbarItem
91
- : // Next item is another dropdown; focus on the inner
92
- // anchor element instead so there's outline
93
- nextNavbarItem.querySelector('a');
94
- targetItem.focus();
95
- }
96
- }
97
- }}
98
84
  activeClassName="dropdown__link--active"
99
85
  {...childItemProps}
100
86
  key={i}
@@ -79,10 +79,11 @@ function TabList({className, block, selectedValue, selectValue, tabValues}) {
79
79
  );
80
80
  }
81
81
  function TabContent({lazy, children, selectedValue}) {
82
- // eslint-disable-next-line no-param-reassign
83
- children = Array.isArray(children) ? children : [children];
82
+ const childTabs = (Array.isArray(children) ? children : [children]).filter(
83
+ Boolean,
84
+ );
84
85
  if (lazy) {
85
- const selectedTabItem = children.find(
86
+ const selectedTabItem = childTabs.find(
86
87
  (tabItem) => tabItem.props.value === selectedValue,
87
88
  );
88
89
  if (!selectedTabItem) {
@@ -93,7 +94,7 @@ function TabContent({lazy, children, selectedValue}) {
93
94
  }
94
95
  return (
95
96
  <div className="margin-top--md">
96
- {children.map((tabItem, i) =>
97
+ {childTabs.map((tabItem, i) =>
97
98
  cloneElement(tabItem, {
98
99
  key: i,
99
100
  hidden: tabItem.props.value !== selectedValue,
@@ -32,15 +32,34 @@ function getNavbarTranslationFile(navbar) {
32
32
  const titleTranslations = navbar.title
33
33
  ? { title: { message: navbar.title, description: 'The title in the navbar' } }
34
34
  : {};
35
- return (0, utils_1.mergeTranslations)([titleTranslations, navbarItemsTranslations]);
35
+ const logoAlt = navbar.logo?.alt
36
+ ? {
37
+ 'logo.alt': {
38
+ message: navbar.logo.alt,
39
+ description: 'The alt text of navbar logo',
40
+ },
41
+ }
42
+ : {};
43
+ return (0, utils_1.mergeTranslations)([
44
+ titleTranslations,
45
+ logoAlt,
46
+ navbarItemsTranslations,
47
+ ]);
36
48
  }
37
49
  function translateNavbar(navbar, navbarTranslations) {
38
50
  if (!navbarTranslations) {
39
51
  return navbar;
40
52
  }
53
+ const logo = navbar.logo
54
+ ? {
55
+ ...navbar.logo,
56
+ alt: navbarTranslations[`logo.alt`]?.message ?? navbar.logo?.alt,
57
+ }
58
+ : undefined;
41
59
  return {
42
60
  ...navbar,
43
61
  title: navbarTranslations.title?.message ?? navbar.title,
62
+ logo,
44
63
  // TODO handle properly all the navbar item types here!
45
64
  items: navbar.items.map((item) => {
46
65
  const subItems = item.items?.map((subItem) => ({
@@ -86,7 +105,20 @@ function getFooterTranslationFile(footer) {
86
105
  },
87
106
  }
88
107
  : {};
89
- return (0, utils_1.mergeTranslations)([footerLinkTitles, footerLinkLabels, copyright]);
108
+ const logoAlt = footer.logo?.alt
109
+ ? {
110
+ 'logo.alt': {
111
+ message: footer.logo.alt,
112
+ description: 'The alt text of footer logo',
113
+ },
114
+ }
115
+ : {};
116
+ return (0, utils_1.mergeTranslations)([
117
+ footerLinkTitles,
118
+ footerLinkLabels,
119
+ copyright,
120
+ logoAlt,
121
+ ]);
90
122
  }
91
123
  function translateFooter(footer, footerTranslations) {
92
124
  if (!footerTranslations) {
@@ -108,10 +140,17 @@ function translateFooter(footer, footerTranslations) {
108
140
  link.label,
109
141
  }));
110
142
  const copyright = footerTranslations.copyright?.message ?? footer.copyright;
143
+ const logo = footer.logo
144
+ ? {
145
+ ...footer.logo,
146
+ alt: footerTranslations[`logo.alt`]?.message ?? footer.logo?.alt,
147
+ }
148
+ : undefined;
111
149
  return {
112
150
  ...footer,
113
151
  links,
114
152
  copyright,
153
+ logo,
115
154
  };
116
155
  }
117
156
  function getTranslationFiles({ themeConfig, }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-classic",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "description": "Classic theme for Docusaurus",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/theme-classic.d.ts",
@@ -20,22 +20,22 @@
20
20
  "copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
21
21
  },
22
22
  "dependencies": {
23
- "@docusaurus/core": "2.3.1",
24
- "@docusaurus/mdx-loader": "2.3.1",
25
- "@docusaurus/module-type-aliases": "2.3.1",
26
- "@docusaurus/plugin-content-blog": "2.3.1",
27
- "@docusaurus/plugin-content-docs": "2.3.1",
28
- "@docusaurus/plugin-content-pages": "2.3.1",
29
- "@docusaurus/theme-common": "2.3.1",
30
- "@docusaurus/theme-translations": "2.3.1",
31
- "@docusaurus/types": "2.3.1",
32
- "@docusaurus/utils": "2.3.1",
33
- "@docusaurus/utils-common": "2.3.1",
34
- "@docusaurus/utils-validation": "2.3.1",
23
+ "@docusaurus/core": "2.4.1",
24
+ "@docusaurus/mdx-loader": "2.4.1",
25
+ "@docusaurus/module-type-aliases": "2.4.1",
26
+ "@docusaurus/plugin-content-blog": "2.4.1",
27
+ "@docusaurus/plugin-content-docs": "2.4.1",
28
+ "@docusaurus/plugin-content-pages": "2.4.1",
29
+ "@docusaurus/theme-common": "2.4.1",
30
+ "@docusaurus/theme-translations": "2.4.1",
31
+ "@docusaurus/types": "2.4.1",
32
+ "@docusaurus/utils": "2.4.1",
33
+ "@docusaurus/utils-common": "2.4.1",
34
+ "@docusaurus/utils-validation": "2.4.1",
35
35
  "@mdx-js/react": "^1.6.22",
36
36
  "clsx": "^1.2.1",
37
37
  "copy-text-to-clipboard": "^3.0.1",
38
- "infima": "0.2.0-alpha.42",
38
+ "infima": "0.2.0-alpha.43",
39
39
  "lodash": "^4.17.21",
40
40
  "nprogress": "^0.2.0",
41
41
  "postcss": "^8.4.14",
@@ -62,5 +62,5 @@
62
62
  "engines": {
63
63
  "node": ">=16.14"
64
64
  },
65
- "gitHead": "cf12f21a10f6c5439ff931e61419c4bb03a5a2dc"
65
+ "gitHead": "60e657d8ae5a4a9ed1c2d777f9defd882cc12681"
66
66
  }
package/src/index.ts CHANGED
@@ -26,6 +26,8 @@ const ContextReplacementPlugin = requireFromDocusaurusCore(
26
26
  // Need to be inlined to prevent dark mode FOUC
27
27
  // Make sure the key is the same as the one in `/theme/hooks/useTheme.js`
28
28
  const ThemeStorageKey = 'theme';
29
+ const ThemeQueryStringKey = 'docusaurus-theme';
30
+
29
31
  const noFlashColorMode = ({
30
32
  defaultMode,
31
33
  respectPrefersColorScheme,
@@ -39,6 +41,14 @@ const noFlashColorMode = ({
39
41
  document.documentElement.setAttribute('data-theme', theme);
40
42
  }
41
43
 
44
+ function getQueryStringTheme() {
45
+ var theme = null;
46
+ try {
47
+ theme = new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
48
+ } catch(e) {}
49
+ return theme;
50
+ }
51
+
42
52
  function getStoredTheme() {
43
53
  var theme = null;
44
54
  try {
@@ -47,9 +57,9 @@ const noFlashColorMode = ({
47
57
  return theme;
48
58
  }
49
59
 
50
- var storedTheme = getStoredTheme();
51
- if (storedTheme !== null) {
52
- setDataThemeAttribute(storedTheme);
60
+ var initialTheme = getQueryStringTheme() || getStoredTheme();
61
+ if (initialTheme !== null) {
62
+ setDataThemeAttribute(initialTheme);
53
63
  } else {
54
64
  if (
55
65
  respectPrefersColorScheme &&
@@ -59,6 +59,7 @@
59
59
  display: flex;
60
60
  column-gap: 0.2rem;
61
61
  position: absolute;
62
+ /* rtl:ignore */
62
63
  right: calc(var(--ifm-pre-padding) / 2);
63
64
  top: calc(var(--ifm-pre-padding) / 2);
64
65
  }
@@ -72,7 +73,7 @@
72
73
  border-radius: var(--ifm-global-radius);
73
74
  padding: 0.4rem;
74
75
  line-height: 0;
75
- transition: opacity 200ms ease-in-out;
76
+ transition: opacity var(--ifm-transition-fast) ease-in-out;
76
77
  opacity: 0;
77
78
  }
78
79
 
@@ -11,6 +11,8 @@ import clsx from 'clsx';
11
11
  import copy from 'copy-text-to-clipboard';
12
12
  import {translate} from '@docusaurus/Translate';
13
13
  import type {Props} from '@theme/CodeBlock/CopyButton';
14
+ import IconCopy from '@theme/Icon/Copy';
15
+ import IconSuccess from '@theme/Icon/Success';
14
16
 
15
17
  import styles from './styles.module.css';
16
18
 
@@ -56,12 +58,8 @@ export default function CopyButton({code, className}: Props): JSX.Element {
56
58
  )}
57
59
  onClick={handleCopyCode}>
58
60
  <span className={styles.copyButtonIcons} aria-hidden="true">
59
- <svg className={styles.copyButtonIcon} viewBox="0 0 24 24">
60
- <path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />
61
- </svg>
62
- <svg className={styles.copyButtonSuccessIcon} viewBox="0 0 24 24">
63
- <path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
64
- </svg>
61
+ <IconCopy className={styles.copyButtonIcon} />
62
+ <IconSuccess className={styles.copyButtonSuccessIcon} />
65
63
  </span>
66
64
  </button>
67
65
  );
@@ -24,7 +24,7 @@
24
24
  opacity: inherit;
25
25
  width: inherit;
26
26
  height: inherit;
27
- transition: all 0.15s ease;
27
+ transition: all var(--ifm-transition-fast) ease;
28
28
  }
29
29
 
30
30
  .copyButtonSuccessIcon {
@@ -9,6 +9,7 @@ import React from 'react';
9
9
  import clsx from 'clsx';
10
10
  import {translate} from '@docusaurus/Translate';
11
11
  import type {Props} from '@theme/CodeBlock/WordWrapButton';
12
+ import IconWordWrap from '@theme/Icon/WordWrap';
12
13
 
13
14
  import styles from './styles.module.css';
14
15
 
@@ -35,15 +36,7 @@ export default function WordWrapButton({
35
36
  )}
36
37
  aria-label={title}
37
38
  title={title}>
38
- <svg
39
- className={styles.wordWrapButtonIcon}
40
- viewBox="0 0 24 24"
41
- aria-hidden="true">
42
- <path
43
- fill="currentColor"
44
- d="M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"
45
- />
46
- </svg>
39
+ <IconWordWrap className={styles.wordWrapButtonIcon} aria-hidden="true" />
47
40
  </button>
48
41
  );
49
42
  }
@@ -15,7 +15,12 @@ import type {Props} from '@theme/ColorModeToggle';
15
15
 
16
16
  import styles from './styles.module.css';
17
17
 
18
- function ColorModeToggle({className, value, onChange}: Props): JSX.Element {
18
+ function ColorModeToggle({
19
+ className,
20
+ buttonClassName,
21
+ value,
22
+ onChange,
23
+ }: Props): JSX.Element {
19
24
  const isBrowser = useIsBrowser();
20
25
 
21
26
  const title = translate(
@@ -47,6 +52,7 @@ function ColorModeToggle({className, value, onChange}: Props): JSX.Element {
47
52
  'clean-btn',
48
53
  styles.toggleButton,
49
54
  !isBrowser && styles.toggleButtonDisabled,
55
+ buttonClassName,
50
56
  )}
51
57
  type="button"
52
58
  onClick={() => onChange(value === 'dark' ? 'light' : 'dark')}
@@ -82,15 +82,18 @@ function CardCategory({
82
82
  href={href}
83
83
  icon="🗃️"
84
84
  title={item.label}
85
- description={translate(
86
- {
87
- message: '{count} items',
88
- id: 'theme.docs.DocCard.categoryDescription',
89
- description:
90
- 'The default description for a category card in the generated index about how many items this category includes',
91
- },
92
- {count: item.items.length},
93
- )}
85
+ description={
86
+ item.description ??
87
+ translate(
88
+ {
89
+ message: '{count} items',
90
+ id: 'theme.docs.DocCard.categoryDescription',
91
+ description:
92
+ 'The default description for a category card in the generated index about how many items this category includes',
93
+ },
94
+ {count: item.items.length},
95
+ )
96
+ }
94
97
  />
95
98
  );
96
99
  }
@@ -103,7 +106,7 @@ function CardLink({item}: {item: PropSidebarItemLink}): JSX.Element {
103
106
  href={item.href}
104
107
  icon={icon}
105
108
  title={item.label}
106
- description={doc?.description}
109
+ description={item.description ?? doc?.description}
107
110
  />
108
111
  );
109
112
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  import React, {type ReactNode, useState, useCallback} from 'react';
9
9
  import clsx from 'clsx';
10
- import {ThemeClassNames} from '@docusaurus/theme-common';
10
+ import {prefersReducedMotion, ThemeClassNames} from '@docusaurus/theme-common';
11
11
  import {useDocsSidebar} from '@docusaurus/theme-common/internal';
12
12
  import {useLocation} from '@docusaurus/router';
13
13
  import DocSidebar from '@theme/DocSidebar';
@@ -40,6 +40,11 @@ export default function DocPageLayoutSidebar({
40
40
  if (hiddenSidebar) {
41
41
  setHiddenSidebar(false);
42
42
  }
43
+ // onTransitionEnd won't fire when sidebar animation is disabled
44
+ // fixes https://github.com/facebook/docusaurus/issues/8918
45
+ if (!hiddenSidebar && prefersReducedMotion()) {
46
+ setHiddenSidebar(true);
47
+ }
43
48
  setHiddenSidebarContainer((value) => !value);
44
49
  }, [setHiddenSidebarContainer, hiddenSidebar]);
45
50
 
@@ -8,6 +8,7 @@
8
8
  .docPage {
9
9
  display: flex;
10
10
  width: 100%;
11
+ flex: 1 0;
11
12
  }
12
13
 
13
14
  .docsWrapper {
@@ -17,7 +17,7 @@ export default function DocPaginator(props: Props): JSX.Element {
17
17
  className="pagination-nav docusaurus-mt-lg"
18
18
  aria-label={translate({
19
19
  id: 'theme.docs.paginator.navAriaLabel',
20
- message: 'Docs pages navigation',
20
+ message: 'Docs pages',
21
21
  description: 'The ARIA label for the docs pagination',
22
22
  })}>
23
23
  {previous && (
@@ -7,6 +7,10 @@
7
7
 
8
8
  import React from 'react';
9
9
  import Translate from '@docusaurus/Translate';
10
+ import {
11
+ ErrorBoundaryError,
12
+ ErrorBoundaryTryAgainButton,
13
+ } from '@docusaurus/theme-common';
10
14
  import type {Props} from '@theme/Error';
11
15
 
12
16
  export default function ErrorPageContent({
@@ -24,15 +28,15 @@ export default function ErrorPageContent({
24
28
  This page crashed.
25
29
  </Translate>
26
30
  </h1>
27
- <p>{error.message}</p>
28
- <div>
29
- <button type="button" onClick={tryAgain}>
30
- <Translate
31
- id="theme.ErrorPageContent.tryAgain"
32
- description="The label of the button to try again when the page crashed">
33
- Try again
34
- </Translate>
35
- </button>
31
+ <div className="margin-vert--lg">
32
+ <ErrorBoundaryTryAgainButton
33
+ onClick={tryAgain}
34
+ className="button button--primary shadow--lw"
35
+ />
36
+ </div>
37
+ <hr />
38
+ <div className="margin-vert--md">
39
+ <ErrorBoundaryError error={error} />
36
40
  </div>
37
41
  </div>
38
42
  </div>
@@ -0,0 +1,20 @@
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 from 'react';
9
+ import type {Props} from '@theme/Icon/Copy';
10
+
11
+ export default function IconCopy(props: Props): JSX.Element {
12
+ return (
13
+ <svg viewBox="0 0 24 24" {...props}>
14
+ <path
15
+ fill="currentColor"
16
+ d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
17
+ />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,20 @@
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 from 'react';
9
+ import type {Props} from '@theme/Icon/Success';
10
+
11
+ export default function IconSuccess(props: Props): JSX.Element {
12
+ return (
13
+ <svg viewBox="0 0 24 24" {...props}>
14
+ <path
15
+ fill="currentColor"
16
+ d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"
17
+ />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,20 @@
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 from 'react';
9
+ import type {Props} from '@theme/Icon/WordWrap';
10
+
11
+ export default function IconWordWrap(props: Props): JSX.Element {
12
+ return (
13
+ <svg viewBox="0 0 24 24" {...props}>
14
+ <path
15
+ fill="currentColor"
16
+ d="M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"
17
+ />
18
+ </svg>
19
+ );
20
+ }
@@ -9,10 +9,12 @@ import React from 'react';
9
9
  import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
10
10
  import ColorModeToggle from '@theme/ColorModeToggle';
11
11
  import type {Props} from '@theme/Navbar/ColorModeToggle';
12
+ import styles from './styles.module.css';
12
13
 
13
14
  export default function NavbarColorModeToggle({
14
15
  className,
15
16
  }: Props): JSX.Element | null {
17
+ const navbarStyle = useThemeConfig().navbar.style;
16
18
  const disabled = useThemeConfig().colorMode.disableSwitch;
17
19
  const {colorMode, setColorMode} = useColorMode();
18
20
 
@@ -23,6 +25,9 @@ export default function NavbarColorModeToggle({
23
25
  return (
24
26
  <ColorModeToggle
25
27
  className={className}
28
+ buttonClassName={
29
+ navbarStyle === 'dark' ? styles.darkNavbarColorModeToggle : undefined
30
+ }
26
31
  value={colorMode}
27
32
  onChange={setColorMode}
28
33
  />
@@ -0,0 +1,10 @@
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
+ .darkNavbarColorModeToggle:hover {
9
+ background: var(--ifm-color-gray-800);
10
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import React, {type ReactNode} from 'react';
9
- import {useThemeConfig} from '@docusaurus/theme-common';
9
+ import {useThemeConfig, ErrorCauseBoundary} from '@docusaurus/theme-common';
10
10
  import {
11
11
  splitNavbarItems,
12
12
  useNavbarMobileSidebar,
@@ -29,7 +29,18 @@ function NavbarItems({items}: {items: NavbarItemConfig[]}): JSX.Element {
29
29
  return (
30
30
  <>
31
31
  {items.map((item, i) => (
32
- <NavbarItem {...item} key={i} />
32
+ <ErrorCauseBoundary
33
+ key={i}
34
+ onError={(error) =>
35
+ new Error(
36
+ `A theme navbar item failed to render.
37
+ Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:
38
+ ${JSON.stringify(item, null, 2)}`,
39
+ {cause: error},
40
+ )
41
+ }>
42
+ <NavbarItem {...item} />
43
+ </ErrorCauseBoundary>
33
44
  ))}
34
45
  </>
35
46
  );
@@ -54,7 +54,9 @@ function DropdownNavbarItemDesktop({
54
54
  const [showDropdown, setShowDropdown] = useState(false);
55
55
 
56
56
  useEffect(() => {
57
- const handleClickOutside = (event: MouseEvent | TouchEvent) => {
57
+ const handleClickOutside = (
58
+ event: MouseEvent | TouchEvent | FocusEvent,
59
+ ) => {
58
60
  if (
59
61
  !dropdownRef.current ||
60
62
  dropdownRef.current.contains(event.target as Node)
@@ -66,10 +68,12 @@ function DropdownNavbarItemDesktop({
66
68
 
67
69
  document.addEventListener('mousedown', handleClickOutside);
68
70
  document.addEventListener('touchstart', handleClickOutside);
71
+ document.addEventListener('focusin', handleClickOutside);
69
72
 
70
73
  return () => {
71
74
  document.removeEventListener('mousedown', handleClickOutside);
72
75
  document.removeEventListener('touchstart', handleClickOutside);
76
+ document.removeEventListener('focusin', handleClickOutside);
73
77
  };
74
78
  }, [dropdownRef]);
75
79
 
@@ -100,22 +104,6 @@ function DropdownNavbarItemDesktop({
100
104
  {items.map((childItemProps, i) => (
101
105
  <NavbarItem
102
106
  isDropdownItem
103
- onKeyDown={(e) => {
104
- if (i === items.length - 1 && e.key === 'Tab') {
105
- e.preventDefault();
106
- setShowDropdown(false);
107
- const nextNavbarItem = dropdownRef.current!.nextElementSibling;
108
- if (nextNavbarItem) {
109
- const targetItem =
110
- nextNavbarItem instanceof HTMLAnchorElement
111
- ? nextNavbarItem
112
- : // Next item is another dropdown; focus on the inner
113
- // anchor element instead so there's outline
114
- nextNavbarItem.querySelector('a')!;
115
- targetItem.focus();
116
- }
117
- }
118
- }}
119
107
  activeClassName="dropdown__link--active"
120
108
  {...childItemProps}
121
109
  key={i}
@@ -5,11 +5,12 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import React, {cloneElement} from 'react';
8
+ import React, {cloneElement, type ReactElement} from 'react';
9
9
  import clsx from 'clsx';
10
10
  import {
11
11
  useScrollPositionBlocker,
12
12
  useTabs,
13
+ type TabItemProps,
13
14
  } from '@docusaurus/theme-common/internal';
14
15
  import useIsBrowser from '@docusaurus/useIsBrowser';
15
16
  import type {Props} from '@theme/Tabs';
@@ -109,10 +110,11 @@ function TabContent({
109
110
  children,
110
111
  selectedValue,
111
112
  }: Props & ReturnType<typeof useTabs>) {
112
- // eslint-disable-next-line no-param-reassign
113
- children = Array.isArray(children) ? children : [children];
113
+ const childTabs = (Array.isArray(children) ? children : [children]).filter(
114
+ Boolean,
115
+ ) as ReactElement<TabItemProps>[];
114
116
  if (lazy) {
115
- const selectedTabItem = children.find(
117
+ const selectedTabItem = childTabs.find(
116
118
  (tabItem) => tabItem.props.value === selectedValue,
117
119
  );
118
120
  if (!selectedTabItem) {
@@ -123,7 +125,7 @@ function TabContent({
123
125
  }
124
126
  return (
125
127
  <div className="margin-top--md">
126
- {children.map((tabItem, i) =>
128
+ {childTabs.map((tabItem, i) =>
127
129
  cloneElement(tabItem, {
128
130
  key: i,
129
131
  hidden: tabItem.props.value !== selectedValue,
@@ -1243,6 +1243,7 @@ declare module '@theme/ColorModeToggle' {
1243
1243
 
1244
1244
  export interface Props {
1245
1245
  readonly className?: string;
1246
+ readonly buttonClassName?: string;
1246
1247
  readonly value: ColorMode;
1247
1248
  /**
1248
1249
  * The parameter represents the "to-be" value. For example, if currently in
@@ -1321,6 +1322,14 @@ declare module '@theme/Icon/Close' {
1321
1322
  export default function IconClose(props: Props): JSX.Element;
1322
1323
  }
1323
1324
 
1325
+ declare module '@theme/Icon/Copy' {
1326
+ import type {ComponentProps} from 'react';
1327
+
1328
+ export interface Props extends ComponentProps<'svg'> {}
1329
+
1330
+ export default function IconCopy(props: Props): JSX.Element;
1331
+ }
1332
+
1324
1333
  declare module '@theme/Icon/Language' {
1325
1334
  import type {ComponentProps} from 'react';
1326
1335
 
@@ -1329,6 +1338,14 @@ declare module '@theme/Icon/Language' {
1329
1338
  export default function IconLanguage(props: Props): JSX.Element;
1330
1339
  }
1331
1340
 
1341
+ declare module '@theme/Icon/Success' {
1342
+ import type {ComponentProps} from 'react';
1343
+
1344
+ export interface Props extends ComponentProps<'svg'> {}
1345
+
1346
+ export default function IconSuccess(props: Props): JSX.Element;
1347
+ }
1348
+
1332
1349
  declare module '@theme/Icon/ExternalLink' {
1333
1350
  import type {ComponentProps} from 'react';
1334
1351
 
@@ -1337,6 +1354,14 @@ declare module '@theme/Icon/ExternalLink' {
1337
1354
  export default function IconExternalLink(props: Props): JSX.Element;
1338
1355
  }
1339
1356
 
1357
+ declare module '@theme/Icon/WordWrap' {
1358
+ import type {ComponentProps} from 'react';
1359
+
1360
+ export interface Props extends ComponentProps<'svg'> {}
1361
+
1362
+ export default function IconWordWrap(props: Props): JSX.Element;
1363
+ }
1364
+
1340
1365
  declare module '@theme/TagsListByLetter' {
1341
1366
  import type {TagsListItem} from '@docusaurus/utils';
1342
1367
 
@@ -45,7 +45,20 @@ function getNavbarTranslationFile(navbar: Navbar): TranslationFileContent {
45
45
  ? {title: {message: navbar.title, description: 'The title in the navbar'}}
46
46
  : {};
47
47
 
48
- return mergeTranslations([titleTranslations, navbarItemsTranslations]);
48
+ const logoAlt: TranslationFileContent = navbar.logo?.alt
49
+ ? {
50
+ 'logo.alt': {
51
+ message: navbar.logo.alt,
52
+ description: 'The alt text of navbar logo',
53
+ },
54
+ }
55
+ : {};
56
+
57
+ return mergeTranslations([
58
+ titleTranslations,
59
+ logoAlt,
60
+ navbarItemsTranslations,
61
+ ]);
49
62
  }
50
63
  function translateNavbar(
51
64
  navbar: Navbar,
@@ -54,9 +67,18 @@ function translateNavbar(
54
67
  if (!navbarTranslations) {
55
68
  return navbar;
56
69
  }
70
+
71
+ const logo = navbar.logo
72
+ ? {
73
+ ...navbar.logo,
74
+ alt: navbarTranslations[`logo.alt`]?.message ?? navbar.logo?.alt,
75
+ }
76
+ : undefined;
77
+
57
78
  return {
58
79
  ...navbar,
59
80
  title: navbarTranslations.title?.message ?? navbar.title,
81
+ logo,
60
82
  // TODO handle properly all the navbar item types here!
61
83
  items: navbar.items.map((item) => {
62
84
  const subItems = item.items?.map((subItem) => ({
@@ -119,7 +141,21 @@ function getFooterTranslationFile(footer: Footer): TranslationFileContent {
119
141
  }
120
142
  : {};
121
143
 
122
- return mergeTranslations([footerLinkTitles, footerLinkLabels, copyright]);
144
+ const logoAlt: TranslationFileContent = footer.logo?.alt
145
+ ? {
146
+ 'logo.alt': {
147
+ message: footer.logo.alt,
148
+ description: 'The alt text of footer logo',
149
+ },
150
+ }
151
+ : {};
152
+
153
+ return mergeTranslations([
154
+ footerLinkTitles,
155
+ footerLinkLabels,
156
+ copyright,
157
+ logoAlt,
158
+ ]);
123
159
  }
124
160
  function translateFooter(
125
161
  footer: Footer,
@@ -149,10 +185,18 @@ function translateFooter(
149
185
 
150
186
  const copyright = footerTranslations.copyright?.message ?? footer.copyright;
151
187
 
188
+ const logo = footer.logo
189
+ ? {
190
+ ...footer.logo,
191
+ alt: footerTranslations[`logo.alt`]?.message ?? footer.logo?.alt,
192
+ }
193
+ : undefined;
194
+
152
195
  return {
153
196
  ...footer,
154
197
  links,
155
198
  copyright,
199
+ logo,
156
200
  };
157
201
  }
158
202