@docusaurus/theme-classic 3.8.1 → 3.9.0-canary-6403

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 (54) hide show
  1. package/lib/index.js +0 -1
  2. package/lib/inlineScripts.d.ts +1 -1
  3. package/lib/inlineScripts.js +21 -20
  4. package/lib/theme/Blog/Components/Author/Socials/index.js +3 -1
  5. package/lib/theme/Blog/Components/Author/index.js +6 -2
  6. package/lib/theme/CodeBlock/Buttons/CopyButton/index.js +6 -6
  7. package/lib/theme/DocSidebarItem/Category/index.d.ts +1 -1
  8. package/lib/theme/DocSidebarItem/Category/index.js +96 -33
  9. package/lib/theme/DocSidebarItem/Category/styles.module.css +26 -0
  10. package/lib/theme/DocSidebarItem/Link/index.js +8 -1
  11. package/lib/theme/DocSidebarItem/Link/styles.module.css +8 -0
  12. package/lib/theme/Heading/index.js +2 -1
  13. package/lib/theme/Icon/ExternalLink/index.js +6 -1
  14. package/lib/theme/Icon/Socials/Bluesky/index.js +6 -5
  15. package/lib/theme/{MDXComponents/A.js → Icon/Socials/Bluesky/styles.module.css} +7 -4
  16. package/lib/theme/Icon/Socials/Default/index.js +6 -6
  17. package/lib/theme/Icon/Socials/Email/index.d.ts +9 -0
  18. package/lib/theme/Icon/Socials/Email/index.js +25 -0
  19. package/lib/theme/Icon/Socials/LinkedIn/index.d.ts +1 -1
  20. package/lib/theme/Icon/Socials/LinkedIn/index.js +6 -5
  21. package/lib/theme/Icon/Socials/LinkedIn/styles.module.css +14 -0
  22. package/lib/theme/MDXComponents/A/index.js +36 -0
  23. package/lib/theme/MDXComponents/A/styles.module.css +20 -0
  24. package/lib/theme/Navbar/Search/styles.module.css +1 -2
  25. package/lib/theme/NavbarItem/DocsVersionDropdownNavbarItem.js +3 -2
  26. package/lib/theme/NavbarItem/LocaleDropdownNavbarItem/index.js +61 -15
  27. package/lib/theme/Tabs/index.js +9 -1
  28. package/package.json +16 -17
  29. package/src/index.ts +0 -1
  30. package/src/inlineScripts.ts +27 -20
  31. package/src/theme/Blog/Components/Author/Socials/index.tsx +3 -1
  32. package/src/theme/Blog/Components/Author/index.tsx +6 -2
  33. package/src/theme/CodeBlock/Buttons/CopyButton/index.tsx +6 -6
  34. package/src/theme/DocSidebarItem/Category/index.tsx +104 -32
  35. package/src/theme/DocSidebarItem/Category/styles.module.css +26 -0
  36. package/src/theme/DocSidebarItem/Link/index.tsx +9 -1
  37. package/src/theme/DocSidebarItem/Link/styles.module.css +8 -0
  38. package/src/theme/Heading/index.tsx +2 -1
  39. package/src/theme/Icon/ExternalLink/index.tsx +6 -1
  40. package/src/theme/Icon/Socials/Bluesky/index.tsx +7 -6
  41. package/src/theme/Icon/Socials/Bluesky/styles.module.css +14 -0
  42. package/src/theme/Icon/Socials/Default/index.tsx +6 -6
  43. package/src/theme/Icon/Socials/Email/index.tsx +28 -0
  44. package/src/theme/Icon/Socials/LinkedIn/index.tsx +7 -6
  45. package/src/theme/Icon/Socials/LinkedIn/styles.module.css +14 -0
  46. package/src/theme/MDXComponents/A/index.tsx +42 -0
  47. package/src/theme/MDXComponents/A/styles.module.css +20 -0
  48. package/src/theme/Navbar/Search/styles.module.css +1 -2
  49. package/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx +3 -2
  50. package/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx +66 -16
  51. package/src/theme/Tabs/index.tsx +9 -1
  52. package/src/theme-classic.d.ts +11 -1
  53. package/src/theme/MDXComponents/A.tsx +0 -14
  54. /package/lib/theme/MDXComponents/{A.d.ts → A/index.d.ts} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  useDocsPreferredVersion,
13
13
  } from '@docusaurus/plugin-content-docs/client';
14
14
  import {translate} from '@docusaurus/Translate';
15
- import {useLocation} from '@docusaurus/router';
15
+ import {useHistorySelector} from '@docusaurus/theme-common';
16
16
  import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
17
17
  import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
18
18
  function getVersionItems(versions, configs) {
@@ -73,7 +73,8 @@ export default function DocsVersionDropdownNavbarItem({
73
73
  versions: configs,
74
74
  ...props
75
75
  }) {
76
- const {search, hash} = useLocation();
76
+ const search = useHistorySelector((history) => history.location.search);
77
+ const hash = useHistorySelector((history) => history.location.hash);
77
78
  const activeDocContext = useActiveDocContext(docsPluginId);
78
79
  const {savePreferredVersionName} = useDocsPreferredVersion(docsPluginId);
79
80
  const versionItems = useVersionItems({docsPluginId, configs});
@@ -8,33 +8,79 @@ import React from 'react';
8
8
  import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
9
9
  import {useAlternatePageUtils} from '@docusaurus/theme-common/internal';
10
10
  import {translate} from '@docusaurus/Translate';
11
- import {useLocation} from '@docusaurus/router';
11
+ import {mergeSearchStrings, useHistorySelector} from '@docusaurus/theme-common';
12
12
  import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
13
13
  import IconLanguage from '@theme/Icon/Language';
14
14
  import styles from './styles.module.css';
15
+ function useLocaleDropdownUtils() {
16
+ const {
17
+ siteConfig,
18
+ i18n: {localeConfigs},
19
+ } = useDocusaurusContext();
20
+ const alternatePageUtils = useAlternatePageUtils();
21
+ const search = useHistorySelector((history) => history.location.search);
22
+ const hash = useHistorySelector((history) => history.location.hash);
23
+ const getLocaleConfig = (locale) => {
24
+ const localeConfig = localeConfigs[locale];
25
+ if (!localeConfig) {
26
+ throw new Error(
27
+ `Docusaurus bug, no locale config found for locale=${locale}`,
28
+ );
29
+ }
30
+ return localeConfig;
31
+ };
32
+ const getBaseURLForLocale = (locale) => {
33
+ const localeConfig = getLocaleConfig(locale);
34
+ const isSameDomain = localeConfig.url === siteConfig.url;
35
+ if (isSameDomain) {
36
+ // Shorter paths if localized sites are hosted on the same domain
37
+ // This reduces HTML size a bit
38
+ return `pathname://${alternatePageUtils.createUrl({
39
+ locale,
40
+ fullyQualified: false,
41
+ })}`;
42
+ }
43
+ return alternatePageUtils.createUrl({
44
+ locale,
45
+ fullyQualified: true,
46
+ });
47
+ };
48
+ return {
49
+ getURL: (locale, options) => {
50
+ // We have 2 query strings because
51
+ // - there's the current one
52
+ // - there's one user can provide through navbar config
53
+ // see https://github.com/facebook/docusaurus/pull/8915
54
+ const finalSearch = mergeSearchStrings(
55
+ [search, options.queryString],
56
+ 'append',
57
+ );
58
+ return `${getBaseURLForLocale(locale)}${finalSearch}${hash}`;
59
+ },
60
+ getLabel: (locale) => {
61
+ return getLocaleConfig(locale).label;
62
+ },
63
+ getLang: (locale) => {
64
+ return getLocaleConfig(locale).htmlLang;
65
+ },
66
+ };
67
+ }
15
68
  export default function LocaleDropdownNavbarItem({
16
69
  mobile,
17
70
  dropdownItemsBefore,
18
71
  dropdownItemsAfter,
19
- queryString = '',
72
+ queryString,
20
73
  ...props
21
74
  }) {
75
+ const utils = useLocaleDropdownUtils();
22
76
  const {
23
- i18n: {currentLocale, locales, localeConfigs},
77
+ i18n: {currentLocale, locales},
24
78
  } = useDocusaurusContext();
25
- const alternatePageUtils = useAlternatePageUtils();
26
- const {search, hash} = useLocation();
27
79
  const localeItems = locales.map((locale) => {
28
- const baseTo = `pathname://${alternatePageUtils.createUrl({
29
- locale,
30
- fullyQualified: false,
31
- })}`;
32
- // preserve ?search#hash suffix on locale switches
33
- const to = `${baseTo}${search}${hash}${queryString}`;
34
80
  return {
35
- label: localeConfigs[locale].label,
36
- lang: localeConfigs[locale].htmlLang,
37
- to,
81
+ label: utils.getLabel(locale),
82
+ lang: utils.getLang(locale),
83
+ to: utils.getURL(locale, {queryString}),
38
84
  target: '_self',
39
85
  autoAddBaseUrl: false,
40
86
  className:
@@ -57,7 +103,7 @@ export default function LocaleDropdownNavbarItem({
57
103
  id: 'theme.navbar.mobileLanguageDropdown.label',
58
104
  description: 'The label for the mobile language switcher dropdown',
59
105
  })
60
- : localeConfigs[currentLocale].label;
106
+ : utils.getLabel(currentLocale);
61
107
  return (
62
108
  <DropdownNavbarItem
63
109
  {...props}
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import React, {cloneElement} from 'react';
8
8
  import clsx from 'clsx';
9
+ import {ThemeClassNames} from '@docusaurus/theme-common';
9
10
  import {
10
11
  useScrollPositionBlocker,
11
12
  useTabs,
@@ -111,7 +112,14 @@ function TabContent({lazy, children, selectedValue}) {
111
112
  function TabsComponent(props) {
112
113
  const tabs = useTabs(props);
113
114
  return (
114
- <div className={clsx('tabs-container', styles.tabList)}>
115
+ <div
116
+ className={clsx(
117
+ ThemeClassNames.tabs.container,
118
+ // former name kept for backward compatibility
119
+ // see https://github.com/facebook/docusaurus/pull/4086
120
+ 'tabs-container',
121
+ styles.tabList,
122
+ )}>
115
123
  <TabList {...tabs} {...props} />
116
124
  <TabContent {...tabs} {...props} />
117
125
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-classic",
3
- "version": "3.8.1",
3
+ "version": "3.9.0-canary-6403",
4
4
  "description": "Classic theme for Docusaurus",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/theme-classic.d.ts",
@@ -20,22 +20,21 @@
20
20
  "copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
21
21
  },
22
22
  "dependencies": {
23
- "@docusaurus/core": "3.8.1",
24
- "@docusaurus/logger": "3.8.1",
25
- "@docusaurus/mdx-loader": "3.8.1",
26
- "@docusaurus/module-type-aliases": "3.8.1",
27
- "@docusaurus/plugin-content-blog": "3.8.1",
28
- "@docusaurus/plugin-content-docs": "3.8.1",
29
- "@docusaurus/plugin-content-pages": "3.8.1",
30
- "@docusaurus/theme-common": "3.8.1",
31
- "@docusaurus/theme-translations": "3.8.1",
32
- "@docusaurus/types": "3.8.1",
33
- "@docusaurus/utils": "3.8.1",
34
- "@docusaurus/utils-common": "3.8.1",
35
- "@docusaurus/utils-validation": "3.8.1",
23
+ "@docusaurus/core": "3.9.0-canary-6403",
24
+ "@docusaurus/logger": "3.9.0-canary-6403",
25
+ "@docusaurus/mdx-loader": "3.9.0-canary-6403",
26
+ "@docusaurus/module-type-aliases": "3.9.0-canary-6403",
27
+ "@docusaurus/plugin-content-blog": "3.9.0-canary-6403",
28
+ "@docusaurus/plugin-content-docs": "3.9.0-canary-6403",
29
+ "@docusaurus/plugin-content-pages": "3.9.0-canary-6403",
30
+ "@docusaurus/theme-common": "3.9.0-canary-6403",
31
+ "@docusaurus/theme-translations": "3.9.0-canary-6403",
32
+ "@docusaurus/types": "3.9.0-canary-6403",
33
+ "@docusaurus/utils": "3.9.0-canary-6403",
34
+ "@docusaurus/utils-common": "3.9.0-canary-6403",
35
+ "@docusaurus/utils-validation": "3.9.0-canary-6403",
36
36
  "@mdx-js/react": "^3.0.0",
37
37
  "clsx": "^2.0.0",
38
- "copy-text-to-clipboard": "^3.2.0",
39
38
  "infima": "0.2.0-alpha.45",
40
39
  "lodash": "^4.17.21",
41
40
  "nprogress": "^0.2.0",
@@ -60,7 +59,7 @@
60
59
  "react-dom": "^18.0.0 || ^19.0.0"
61
60
  },
62
61
  "engines": {
63
- "node": ">=18.0"
62
+ "node": ">=20.0"
64
63
  },
65
- "gitHead": "fa8ae13e668fcbc0481ce10c0a734e2a5b397293"
64
+ "gitHead": "22c95f8cb5c070ce47e115be376fa9b828dd54df"
66
65
  }
package/src/index.ts CHANGED
@@ -125,7 +125,6 @@ export default function themeClassic(
125
125
  {
126
126
  tagName: 'svg',
127
127
  attributes: {
128
- xmlns: 'http://www.w3.org/2000/svg',
129
128
  style: 'display: none;',
130
129
  },
131
130
  innerHTML: SvgSpriteDefs,
@@ -15,7 +15,7 @@ const ThemeQueryStringKey = 'docusaurus-theme';
15
15
  const DataQueryStringPrefixKey = 'docusaurus-data-';
16
16
 
17
17
  export function getThemeInlineScript({
18
- colorMode: {defaultMode, respectPrefersColorScheme},
18
+ colorMode: {disableSwitch, defaultMode, respectPrefersColorScheme},
19
19
  siteStorage,
20
20
  }: {
21
21
  colorMode: ThemeConfig['colorMode'];
@@ -25,28 +25,35 @@ export function getThemeInlineScript({
25
25
  // Make sure the key is the same as the one in the color mode React context
26
26
  // Currently defined in: `docusaurus-theme-common/src/contexts/colorMode.tsx`
27
27
  const themeStorageKey = `theme${siteStorage.namespace}`;
28
+ const isThemeUserConfigurable = !disableSwitch;
28
29
 
29
30
  /* language=js */
30
31
  return `(function() {
31
- var defaultMode = '${defaultMode}';
32
- var respectPrefersColorScheme = ${respectPrefersColorScheme};
33
- function getSystemColorMode() {
34
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
35
- }
36
- function getQueryStringTheme() {
37
- try {
38
- return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
39
- } catch (e) {}
40
- }
41
- function getStoredTheme() {
42
- try {
43
- return window['${siteStorage.type}'].getItem('${themeStorageKey}');
44
- } catch (err) {}
45
- }
46
- var initialTheme = getQueryStringTheme() || getStoredTheme();
47
- document.documentElement.setAttribute('data-theme', initialTheme || (respectPrefersColorScheme ? getSystemColorMode() : defaultMode));
48
- document.documentElement.setAttribute('data-theme-choice', initialTheme || (respectPrefersColorScheme ? 'system' : defaultMode));
49
- })();`;
32
+ function getSystemColorMode() {
33
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
34
+ }
35
+ ${
36
+ isThemeUserConfigurable
37
+ ? ` function getQueryStringTheme() {
38
+ try {
39
+ return new URLSearchParams(window.location.search).get('${ThemeQueryStringKey}')
40
+ } catch (e) {}
41
+ }
42
+ function getStoredTheme() {
43
+ try {
44
+ return window['${siteStorage.type}'].getItem('${themeStorageKey}');
45
+ } catch (err) {}
46
+ }
47
+ var initialTheme = getQueryStringTheme() || getStoredTheme();`
48
+ : ' var initialTheme;'
49
+ }
50
+ document.documentElement.setAttribute('data-theme', initialTheme || ${
51
+ respectPrefersColorScheme ? 'getSystemColorMode()' : `'${defaultMode}'`
52
+ });
53
+ document.documentElement.setAttribute('data-theme-choice', initialTheme || ${
54
+ respectPrefersColorScheme ? `'system'` : `'${defaultMode}'`
55
+ });
56
+ })();`;
50
57
  }
51
58
 
52
59
  /* language=js */
@@ -23,6 +23,7 @@ import Threads from '@theme/Icon/Socials/Threads';
23
23
  import Youtube from '@theme/Icon/Socials/YouTube';
24
24
  import Mastodon from '@theme/Icon/Socials/Mastodon';
25
25
  import Twitch from '@theme/Icon/Socials/Twitch';
26
+ import Email from '@theme/Icon/Socials/Email';
26
27
 
27
28
  import styles from './styles.module.css';
28
29
 
@@ -42,6 +43,7 @@ const SocialPlatformConfigs: Record<string, SocialPlatformConfig> = {
42
43
  mastodon: {Icon: Mastodon, label: 'Mastodon'},
43
44
  youtube: {Icon: Youtube, label: 'YouTube'},
44
45
  twitch: {Icon: Twitch, label: 'Twitch'},
46
+ email: {Icon: Email, label: 'Email'},
45
47
  };
46
48
 
47
49
  function getSocialPlatformConfig(platformKey: string): SocialPlatformConfig {
@@ -57,7 +59,7 @@ function SocialLink({platform, link}: {platform: string; link: string}) {
57
59
  const {Icon, label} = getSocialPlatformConfig(platform);
58
60
  return (
59
61
  <Link className={styles.authorSocialLink} href={link} title={label}>
60
- <Icon className={clsx(styles.authorSocialLink)} />
62
+ <Icon className={clsx(styles.authorSocialIcon)} />
61
63
  </Link>
62
64
  );
63
65
  }
@@ -30,10 +30,14 @@ function AuthorTitle({title}: {title: string}) {
30
30
 
31
31
  function AuthorName({name, as}: {name: string; as: Props['as']}) {
32
32
  if (!as) {
33
- return <span className={styles.authorName}>{name}</span>;
33
+ return (
34
+ <span className={styles.authorName} translate="no">
35
+ {name}
36
+ </span>
37
+ );
34
38
  } else {
35
39
  return (
36
- <Heading as={as} className={styles.authorName}>
40
+ <Heading as={as} className={styles.authorName} translate="no">
37
41
  {name}
38
42
  </Heading>
39
43
  );
@@ -13,7 +13,6 @@ import React, {
13
13
  type ReactNode,
14
14
  } from 'react';
15
15
  import clsx from 'clsx';
16
- import copy from 'copy-text-to-clipboard';
17
16
  import {translate} from '@docusaurus/Translate';
18
17
  import {useCodeBlockContext} from '@docusaurus/theme-common/internal';
19
18
  import Button from '@theme/CodeBlock/Buttons/Button';
@@ -53,11 +52,12 @@ function useCopyButton() {
53
52
  const copyTimeout = useRef<number | undefined>(undefined);
54
53
 
55
54
  const copyCode = useCallback(() => {
56
- copy(code);
57
- setIsCopied(true);
58
- copyTimeout.current = window.setTimeout(() => {
59
- setIsCopied(false);
60
- }, 1000);
55
+ navigator.clipboard.writeText(code).then(() => {
56
+ setIsCopied(true);
57
+ copyTimeout.current = window.setTimeout(() => {
58
+ setIsCopied(false);
59
+ }, 1000);
60
+ });
61
61
  }, [code]);
62
62
 
63
63
  useEffect(() => () => window.clearTimeout(copyTimeout.current), []);
@@ -24,31 +24,51 @@ import {
24
24
  isActiveSidebarItem,
25
25
  findFirstSidebarItemLink,
26
26
  useDocSidebarItemsExpandedState,
27
+ useVisibleSidebarItems,
27
28
  } from '@docusaurus/plugin-content-docs/client';
28
29
  import Link from '@docusaurus/Link';
29
30
  import {translate} from '@docusaurus/Translate';
30
31
  import useIsBrowser from '@docusaurus/useIsBrowser';
31
32
  import DocSidebarItems from '@theme/DocSidebarItems';
33
+ import DocSidebarItemLink from '@theme/DocSidebarItem/Link';
32
34
  import type {Props} from '@theme/DocSidebarItem/Category';
33
35
 
36
+ import type {
37
+ PropSidebarItemCategory,
38
+ PropSidebarItemLink,
39
+ } from '@docusaurus/plugin-content-docs';
40
+ import styles from './styles.module.css';
41
+
34
42
  // If we navigate to a category and it becomes active, it should automatically
35
43
  // expand itself
36
44
  function useAutoExpandActiveCategory({
37
45
  isActive,
38
46
  collapsed,
39
47
  updateCollapsed,
48
+ activePath,
40
49
  }: {
41
50
  isActive: boolean;
42
51
  collapsed: boolean;
43
52
  updateCollapsed: (b: boolean) => void;
53
+ activePath: string;
44
54
  }) {
45
55
  const wasActive = usePrevious(isActive);
56
+ const previousActivePath = usePrevious(activePath);
46
57
  useEffect(() => {
47
58
  const justBecameActive = isActive && !wasActive;
48
- if (justBecameActive && collapsed) {
59
+ const stillActiveButPathChanged =
60
+ isActive && wasActive && activePath !== previousActivePath;
61
+ if ((justBecameActive || stillActiveButPathChanged) && collapsed) {
49
62
  updateCollapsed(false);
50
63
  }
51
- }, [isActive, wasActive, collapsed, updateCollapsed]);
64
+ }, [
65
+ isActive,
66
+ wasActive,
67
+ collapsed,
68
+ updateCollapsed,
69
+ activePath,
70
+ previousActivePath,
71
+ ]);
52
72
  }
53
73
 
54
74
  /**
@@ -114,7 +134,56 @@ function CollapseButton({
114
134
  );
115
135
  }
116
136
 
117
- export default function DocSidebarItemCategory({
137
+ function CategoryLinkLabel({label}: {label: string}) {
138
+ return (
139
+ <span title={label} className={styles.categoryLinkLabel}>
140
+ {label}
141
+ </span>
142
+ );
143
+ }
144
+
145
+ export default function DocSidebarItemCategory(props: Props): ReactNode {
146
+ const visibleChildren = useVisibleSidebarItems(
147
+ props.item.items,
148
+ props.activePath,
149
+ );
150
+ if (visibleChildren.length === 0) {
151
+ return <DocSidebarItemCategoryEmpty {...props} />;
152
+ } else {
153
+ return <DocSidebarItemCategoryCollapsible {...props} />;
154
+ }
155
+ }
156
+
157
+ function isCategoryWithHref(
158
+ category: PropSidebarItemCategory,
159
+ ): category is PropSidebarItemCategory & {href: string} {
160
+ return typeof category.href === 'string';
161
+ }
162
+
163
+ // If a category doesn't have any visible children, we render it as a link
164
+ function DocSidebarItemCategoryEmpty({item, ...props}: Props): ReactNode {
165
+ // If the category has no link, we don't render anything
166
+ // It's not super useful to render a category you can't open nor click
167
+ if (!isCategoryWithHref(item)) {
168
+ return null;
169
+ }
170
+ // We remove props that don't make sense for a link and forward the rest
171
+ const {
172
+ type,
173
+ collapsed,
174
+ collapsible,
175
+ items,
176
+ linkUnlisted,
177
+ ...forwardableProps
178
+ } = item;
179
+ const linkItem: PropSidebarItemLink = {
180
+ type: 'link',
181
+ ...forwardableProps,
182
+ };
183
+ return <DocSidebarItemLink item={linkItem} {...props} />;
184
+ }
185
+
186
+ function DocSidebarItemCategoryCollapsible({
118
187
  item,
119
188
  onItemClick,
120
189
  activePath,
@@ -150,7 +219,12 @@ export default function DocSidebarItemCategory({
150
219
  setExpandedItem(toCollapsed ? null : index);
151
220
  setCollapsed(toCollapsed);
152
221
  };
153
- useAutoExpandActiveCategory({isActive, collapsed, updateCollapsed});
222
+ useAutoExpandActiveCategory({
223
+ isActive,
224
+ collapsed,
225
+ updateCollapsed,
226
+ activePath,
227
+ });
154
228
  useEffect(() => {
155
229
  if (
156
230
  collapsible &&
@@ -162,6 +236,29 @@ export default function DocSidebarItemCategory({
162
236
  }
163
237
  }, [collapsible, expandedItem, index, setCollapsed, autoCollapseCategories]);
164
238
 
239
+ const handleItemClick: ComponentProps<'a'>['onClick'] = (e) => {
240
+ onItemClick?.(item);
241
+ if (collapsible) {
242
+ if (href) {
243
+ // When already on the category's page, we collapse it
244
+ // We don't use "isActive" because it would collapse the
245
+ // category even when we browse a children element
246
+ // See https://github.com/facebook/docusaurus/issues/11213
247
+ if (isCurrentPage) {
248
+ e.preventDefault();
249
+ updateCollapsed();
250
+ } else {
251
+ // When navigating to a new category, we always expand
252
+ // see https://github.com/facebook/docusaurus/issues/10854#issuecomment-2609616182
253
+ updateCollapsed(false);
254
+ }
255
+ } else {
256
+ e.preventDefault();
257
+ updateCollapsed();
258
+ }
259
+ }
260
+ };
261
+
165
262
  return (
166
263
  <li
167
264
  className={clsx(
@@ -178,43 +275,18 @@ export default function DocSidebarItemCategory({
178
275
  'menu__list-item-collapsible--active': isCurrentPage,
179
276
  })}>
180
277
  <Link
181
- className={clsx('menu__link', {
278
+ className={clsx(styles.categoryLink, 'menu__link', {
182
279
  'menu__link--sublist': collapsible,
183
280
  'menu__link--sublist-caret': !href && collapsible,
184
281
  'menu__link--active': isActive,
185
282
  })}
186
- onClick={
187
- collapsible
188
- ? (e) => {
189
- onItemClick?.(item);
190
- if (href) {
191
- // When already on the category's page, we collapse it
192
- // We don't use "isActive" because it would collapse the
193
- // category even when we browse a children element
194
- // See https://github.com/facebook/docusaurus/issues/11213
195
- if (isCurrentPage) {
196
- e.preventDefault();
197
- updateCollapsed();
198
- } else {
199
- // When navigating to a new category, we always expand
200
- // see https://github.com/facebook/docusaurus/issues/10854#issuecomment-2609616182
201
- updateCollapsed(false);
202
- }
203
- } else {
204
- e.preventDefault();
205
- updateCollapsed();
206
- }
207
- }
208
- : () => {
209
- onItemClick?.(item);
210
- }
211
- }
283
+ onClick={handleItemClick}
212
284
  aria-current={isCurrentPage ? 'page' : undefined}
213
285
  role={collapsible && !href ? 'button' : undefined}
214
286
  aria-expanded={collapsible && !href ? !collapsed : undefined}
215
287
  href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
216
288
  {...props}>
217
- {label}
289
+ <CategoryLinkLabel label={label} />
218
290
  </Link>
219
291
  {href && collapsible && (
220
292
  <CollapseButton
@@ -0,0 +1,26 @@
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
+ .categoryLink {
9
+ overflow: hidden;
10
+ }
11
+
12
+ /*
13
+ TODO merge this logic back in Infima?
14
+ */
15
+ :global(.menu__link--sublist-caret)::after {
16
+ margin-left: var(--ifm-menu-link-padding-vertical);
17
+ }
18
+
19
+ .categoryLinkLabel {
20
+ flex: 1;
21
+ overflow: hidden;
22
+ display: -webkit-box;
23
+ line-clamp: 2;
24
+ -webkit-box-orient: vertical;
25
+ -webkit-line-clamp: 2;
26
+ }
@@ -16,6 +16,14 @@ import type {Props} from '@theme/DocSidebarItem/Link';
16
16
 
17
17
  import styles from './styles.module.css';
18
18
 
19
+ function LinkLabel({label}: {label: string}) {
20
+ return (
21
+ <span title={label} className={styles.linkLabel}>
22
+ {label}
23
+ </span>
24
+ );
25
+ }
26
+
19
27
  export default function DocSidebarItemLink({
20
28
  item,
21
29
  onItemClick,
@@ -51,7 +59,7 @@ export default function DocSidebarItemLink({
51
59
  onClick: onItemClick ? () => onItemClick(item) : undefined,
52
60
  })}
53
61
  {...props}>
54
- {label}
62
+ <LinkLabel label={label} />
55
63
  {!isInternalLink && <IconExternalLink />}
56
64
  </Link>
57
65
  </li>
@@ -8,3 +8,11 @@
8
8
  .menuExternalLink {
9
9
  align-items: center;
10
10
  }
11
+
12
+ .linkLabel {
13
+ overflow: hidden;
14
+ display: -webkit-box;
15
+ line-clamp: 2;
16
+ -webkit-box-orient: vertical;
17
+ -webkit-line-clamp: 2;
18
+ }
@@ -54,7 +54,8 @@ export default function Heading({as: As, id, ...props}: Props): ReactNode {
54
54
  className="hash-link"
55
55
  to={`#${id}`}
56
56
  aria-label={anchorTitle}
57
- title={anchorTitle}>
57
+ title={anchorTitle}
58
+ translate="no">
58
59
  &#8203;
59
60
  </Link>
60
61
  </As>
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import React, {type ReactNode} from 'react';
9
+ import {translate} from '@docusaurus/Translate';
9
10
  import type {Props} from '@theme/Icon/ExternalLink';
10
11
 
11
12
  import styles from './styles.module.css';
@@ -22,7 +23,11 @@ export default function IconExternalLink({
22
23
  <svg
23
24
  width={width}
24
25
  height={height}
25
- aria-hidden="true"
26
+ aria-label={translate({
27
+ id: 'theme.IconExternalLink.ariaLabel',
28
+ message: '(opens in new tab)',
29
+ description: 'The ARIA label for the external link icon',
30
+ })}
26
31
  className={styles.iconExternalLink}>
27
32
  <use href={svgSprite} />
28
33
  </svg>
@@ -5,7 +5,9 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import type {SVGProps, ReactNode} from 'react';
8
+ import type {SVGProps, ReactNode, CSSProperties} from 'react';
9
+ import clsx from 'clsx';
10
+ import styles from './styles.module.css';
9
11
 
10
12
  // SVG Source: https://svgl.app/
11
13
  function Bluesky(props: SVGProps<SVGSVGElement>): ReactNode {
@@ -16,11 +18,10 @@ function Bluesky(props: SVGProps<SVGSVGElement>): ReactNode {
16
18
  height="1em"
17
19
  preserveAspectRatio="xMidYMid"
18
20
  viewBox="0 0 256 226"
19
- {...props}>
20
- <path
21
- fill="#1185FE"
22
- d="M55.491 15.172c29.35 22.035 60.917 66.712 72.509 90.686 11.592-23.974 43.159-68.651 72.509-90.686C221.686-.727 256-13.028 256 26.116c0 7.818-4.482 65.674-7.111 75.068-9.138 32.654-42.436 40.983-72.057 35.942 51.775 8.812 64.946 38 36.501 67.187-54.021 55.433-77.644-13.908-83.696-31.676-1.11-3.257-1.63-4.78-1.637-3.485-.008-1.296-.527.228-1.637 3.485-6.052 17.768-29.675 87.11-83.696 31.676-28.445-29.187-15.274-58.375 36.5-67.187-29.62 5.041-62.918-3.288-72.056-35.942C4.482 91.79 0 33.934 0 26.116 0-13.028 34.314-.727 55.491 15.172Z"
23
- />
21
+ style={{'--dark': '#0085ff', '--light': '#0085ff'} as CSSProperties}
22
+ {...props}
23
+ className={clsx(props.className, styles.blueskySvg)}>
24
+ <path d="M55.491 15.172c29.35 22.035 60.917 66.712 72.509 90.686 11.592-23.974 43.159-68.651 72.509-90.686C221.686-.727 256-13.028 256 26.116c0 7.818-4.482 65.674-7.111 75.068-9.138 32.654-42.436 40.983-72.057 35.942 51.775 8.812 64.946 38 36.501 67.187-54.021 55.433-77.644-13.908-83.696-31.676-1.11-3.257-1.63-4.78-1.637-3.485-.008-1.296-.527.228-1.637 3.485-6.052 17.768-29.675 87.11-83.696 31.676-28.445-29.187-15.274-58.375 36.5-67.187-29.62 5.041-62.918-3.288-72.056-35.942C4.482 91.79 0 33.934 0 26.116 0-13.028 34.314-.727 55.491 15.172Z" />
24
25
  </svg>
25
26
  );
26
27
  }