@docusaurus/theme-classic 3.9.2 → 3.10.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 (51) hide show
  1. package/README.md +1 -1
  2. package/lib/getSwizzleConfig.js +7 -0
  3. package/lib/theme/Admonition/Layout/index.js +5 -4
  4. package/lib/theme/CodeBlock/Buttons/CopyButton/index.js +15 -1
  5. package/lib/theme/CodeBlock/Line/index.js +13 -5
  6. package/lib/theme/DocCard/Description/index.d.ts +9 -0
  7. package/lib/theme/DocCard/Description/index.js +23 -0
  8. package/lib/theme/DocCard/Description/styles.module.css +10 -0
  9. package/lib/theme/DocCard/Heading/Icon/index.d.ts +9 -0
  10. package/lib/theme/DocCard/Heading/Icon/index.js +18 -0
  11. package/lib/theme/DocCard/Heading/Icon/styles.module.css +11 -0
  12. package/lib/theme/DocCard/Heading/Text/index.d.ts +9 -0
  13. package/lib/theme/DocCard/Heading/Text/index.js +22 -0
  14. package/lib/theme/DocCard/Heading/Text/styles.module.css +10 -0
  15. package/lib/theme/DocCard/Heading/index.d.ts +9 -0
  16. package/lib/theme/DocCard/Heading/index.js +24 -0
  17. package/lib/theme/DocCard/Heading/styles.module.css +11 -0
  18. package/lib/theme/DocCard/Layout/index.d.ts +9 -0
  19. package/lib/theme/DocCard/Layout/index.js +42 -0
  20. package/lib/theme/DocCard/{styles.module.css → Layout/styles.module.css} +0 -8
  21. package/lib/theme/DocCard/index.js +24 -56
  22. package/lib/theme/Layout/index.js +0 -2
  23. package/lib/theme/Navbar/MobileSidebar/Layout/index.js +1 -0
  24. package/lib/theme/NavbarItem/DropdownNavbarItem/Desktop/index.js +1 -1
  25. package/lib/theme/SiteMetadata/index.js +1 -2
  26. package/lib/theme/TabItem/index.d.ts +1 -1
  27. package/lib/theme/TabItem/index.js +15 -1
  28. package/lib/theme/Tabs/index.js +24 -39
  29. package/package.json +16 -16
  30. package/src/getSwizzleConfig.ts +7 -0
  31. package/src/theme/Admonition/Layout/index.tsx +6 -4
  32. package/src/theme/CodeBlock/Buttons/CopyButton/index.tsx +16 -1
  33. package/src/theme/CodeBlock/Line/index.tsx +14 -8
  34. package/src/theme/DocCard/Description/index.tsx +27 -0
  35. package/src/theme/DocCard/Description/styles.module.css +10 -0
  36. package/src/theme/DocCard/Heading/Icon/index.tsx +22 -0
  37. package/src/theme/DocCard/Heading/Icon/styles.module.css +11 -0
  38. package/src/theme/DocCard/Heading/Text/index.tsx +27 -0
  39. package/src/theme/DocCard/Heading/Text/styles.module.css +10 -0
  40. package/src/theme/DocCard/Heading/index.tsx +28 -0
  41. package/src/theme/DocCard/Heading/styles.module.css +11 -0
  42. package/src/theme/DocCard/Layout/index.tsx +55 -0
  43. package/src/theme/DocCard/{styles.module.css → Layout/styles.module.css} +0 -8
  44. package/src/theme/DocCard/index.tsx +28 -79
  45. package/src/theme/Layout/index.tsx +0 -3
  46. package/src/theme/Navbar/MobileSidebar/Layout/index.tsx +2 -1
  47. package/src/theme/NavbarItem/DropdownNavbarItem/Desktop/index.tsx +1 -1
  48. package/src/theme/SiteMetadata/index.tsx +1 -2
  49. package/src/theme/TabItem/index.tsx +28 -3
  50. package/src/theme/Tabs/index.tsx +31 -50
  51. package/src/theme-classic.d.ts +69 -2
@@ -4,17 +4,20 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import React, {cloneElement} from 'react';
7
+ import React from 'react';
8
8
  import clsx from 'clsx';
9
9
  import {ThemeClassNames} from '@docusaurus/theme-common';
10
10
  import {
11
11
  useScrollPositionBlocker,
12
+ useTabsContextValue,
12
13
  useTabs,
13
14
  sanitizeTabsChildren,
15
+ TabsProvider,
14
16
  } from '@docusaurus/theme-common/internal';
15
17
  import useIsBrowser from '@docusaurus/useIsBrowser';
16
18
  import styles from './styles.module.css';
17
- function TabList({className, block, selectedValue, selectValue, tabValues}) {
19
+ function TabList({className}) {
20
+ const {selectedValue, selectValue, tabValues, block} = useTabs();
18
21
  const tabRefs = [];
19
22
  const {blockElementScrollPositionUntilNextRender} =
20
23
  useScrollPositionBlocker();
@@ -67,8 +70,8 @@ function TabList({className, block, selectedValue, selectValue, tabValues}) {
67
70
  tabIndex={selectedValue === value ? 0 : -1}
68
71
  aria-selected={selectedValue === value}
69
72
  key={value}
70
- ref={(tabControl) => {
71
- tabRefs.push(tabControl);
73
+ ref={(ref) => {
74
+ tabRefs.push(ref);
72
75
  }}
73
76
  onKeyDown={handleKeydown}
74
77
  onClick={handleTabChange}
@@ -82,35 +85,10 @@ function TabList({className, block, selectedValue, selectValue, tabValues}) {
82
85
  </ul>
83
86
  );
84
87
  }
85
- function TabContent({lazy, children, selectedValue}) {
86
- const childTabs = (Array.isArray(children) ? children : [children]).filter(
87
- Boolean,
88
- );
89
- if (lazy) {
90
- const selectedTabItem = childTabs.find(
91
- (tabItem) => tabItem.props.value === selectedValue,
92
- );
93
- if (!selectedTabItem) {
94
- // fail-safe or fail-fast? not sure what's best here
95
- return null;
96
- }
97
- return cloneElement(selectedTabItem, {
98
- className: clsx('margin-top--md', selectedTabItem.props.className),
99
- });
100
- }
101
- return (
102
- <div className="margin-top--md">
103
- {childTabs.map((tabItem, i) =>
104
- cloneElement(tabItem, {
105
- key: i,
106
- hidden: tabItem.props.value !== selectedValue,
107
- }),
108
- )}
109
- </div>
110
- );
88
+ function TabContent({children}) {
89
+ return <div className="margin-top--md">{children}</div>;
111
90
  }
112
- function TabsComponent(props) {
113
- const tabs = useTabs(props);
91
+ function TabsContainer({className, children}) {
114
92
  return (
115
93
  <div
116
94
  className={clsx(
@@ -120,20 +98,27 @@ function TabsComponent(props) {
120
98
  'tabs-container',
121
99
  styles.tabList,
122
100
  )}>
123
- <TabList {...tabs} {...props} />
124
- <TabContent {...tabs} {...props} />
101
+ <TabList
102
+ // Surprising but historical
103
+ // className is applied on TabList, not on TabsContainer
104
+ className={className}
105
+ />
106
+ <TabContent>{children}</TabContent>
125
107
  </div>
126
108
  );
127
109
  }
128
110
  export default function Tabs(props) {
129
111
  const isBrowser = useIsBrowser();
112
+ const value = useTabsContextValue(props);
130
113
  return (
131
- <TabsComponent
114
+ <TabsProvider
115
+ value={value}
132
116
  // Remount tabs after hydration
133
117
  // Temporary fix for https://github.com/facebook/docusaurus/issues/5653
134
- key={String(isBrowser)}
135
- {...props}>
136
- {sanitizeTabsChildren(props.children)}
137
- </TabsComponent>
118
+ key={String(isBrowser)}>
119
+ <TabsContainer className={props.className}>
120
+ {sanitizeTabsChildren(props.children)}
121
+ </TabsContainer>
122
+ </TabsProvider>
138
123
  );
139
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-classic",
3
- "version": "3.9.2",
3
+ "version": "3.10.1",
4
4
  "description": "Classic theme for Docusaurus",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/theme-classic.d.ts",
@@ -20,21 +20,22 @@
20
20
  "copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
21
21
  },
22
22
  "dependencies": {
23
- "@docusaurus/core": "3.9.2",
24
- "@docusaurus/logger": "3.9.2",
25
- "@docusaurus/mdx-loader": "3.9.2",
26
- "@docusaurus/module-type-aliases": "3.9.2",
27
- "@docusaurus/plugin-content-blog": "3.9.2",
28
- "@docusaurus/plugin-content-docs": "3.9.2",
29
- "@docusaurus/plugin-content-pages": "3.9.2",
30
- "@docusaurus/theme-common": "3.9.2",
31
- "@docusaurus/theme-translations": "3.9.2",
32
- "@docusaurus/types": "3.9.2",
33
- "@docusaurus/utils": "3.9.2",
34
- "@docusaurus/utils-common": "3.9.2",
35
- "@docusaurus/utils-validation": "3.9.2",
23
+ "@docusaurus/core": "3.10.1",
24
+ "@docusaurus/logger": "3.10.1",
25
+ "@docusaurus/mdx-loader": "3.10.1",
26
+ "@docusaurus/module-type-aliases": "3.10.1",
27
+ "@docusaurus/plugin-content-blog": "3.10.1",
28
+ "@docusaurus/plugin-content-docs": "3.10.1",
29
+ "@docusaurus/plugin-content-pages": "3.10.1",
30
+ "@docusaurus/theme-common": "3.10.1",
31
+ "@docusaurus/theme-translations": "3.10.1",
32
+ "@docusaurus/types": "3.10.1",
33
+ "@docusaurus/utils": "3.10.1",
34
+ "@docusaurus/utils-common": "3.10.1",
35
+ "@docusaurus/utils-validation": "3.10.1",
36
36
  "@mdx-js/react": "^3.0.0",
37
37
  "clsx": "^2.0.0",
38
+ "copy-text-to-clipboard": "^3.2.0",
38
39
  "infima": "0.2.0-alpha.45",
39
40
  "lodash": "^4.17.21",
40
41
  "nprogress": "^0.2.0",
@@ -51,7 +52,6 @@
51
52
  "@types/prismjs": "^1.26.0",
52
53
  "@types/rtlcss": "^3.5.1",
53
54
  "fs-extra": "^11.1.1",
54
- "react-test-renderer": "^18.0.0",
55
55
  "utility-types": "^3.10.0"
56
56
  },
57
57
  "peerDependencies": {
@@ -61,5 +61,5 @@
61
61
  "engines": {
62
62
  "node": ">=20.0"
63
63
  },
64
- "gitHead": "abfbe5621b08407bc3dcbe6111ff118d4c22f7a1"
64
+ "gitHead": "41c1a458ecb07d61b6df2761ea4bc1b13db49d12"
65
65
  }
@@ -368,6 +368,13 @@ export default function getSwizzleConfig(): SwizzleConfig {
368
368
  description:
369
369
  'The component used to render <img> tags and Markdown images in MDX',
370
370
  },
371
+ 'MDXComponents/Li': {
372
+ actions: {
373
+ eject: 'safe',
374
+ wrap: 'safe',
375
+ },
376
+ description: 'The component used to render <li> tags in MDX',
377
+ },
371
378
  'MDXComponents/Pre': {
372
379
  actions: {
373
380
  eject: 'safe',
@@ -17,7 +17,8 @@ function AdmonitionContainer({
17
17
  type,
18
18
  className,
19
19
  children,
20
- }: Pick<Props, 'type' | 'className'> & {children: ReactNode}) {
20
+ id,
21
+ }: Pick<Props, 'type' | 'className' | 'id'> & {children: ReactNode}) {
21
22
  return (
22
23
  <div
23
24
  className={clsx(
@@ -25,7 +26,8 @@ function AdmonitionContainer({
25
26
  ThemeClassNames.common.admonitionType(type),
26
27
  styles.admonition,
27
28
  className,
28
- )}>
29
+ )}
30
+ id={id}>
29
31
  {children}
30
32
  </div>
31
33
  );
@@ -47,9 +49,9 @@ function AdmonitionContent({children}: Pick<Props, 'children'>) {
47
49
  }
48
50
 
49
51
  export default function AdmonitionLayout(props: Props): ReactNode {
50
- const {type, icon, title, children, className} = props;
52
+ const {type, icon, title, children, className, id} = props;
51
53
  return (
52
- <AdmonitionContainer type={type} className={className}>
54
+ <AdmonitionContainer type={type} className={className} id={id}>
53
55
  {title || icon ? <AdmonitionHeading title={title} icon={icon} /> : null}
54
56
  <AdmonitionContent>{children}</AdmonitionContent>
55
57
  </AdmonitionContainer>
@@ -44,6 +44,19 @@ function ariaLabel(isCopied: boolean) {
44
44
  });
45
45
  }
46
46
 
47
+ async function copyToClipboard(text: string) {
48
+ // The clipboard API is only defined in secure contexts (HTTPS / localhost).
49
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
50
+ if (navigator.clipboard) {
51
+ return navigator.clipboard.writeText(text);
52
+ }
53
+ // Fall back to copy-text-to-clipboard for non-secure contexts (e.g. HTTP
54
+ // on a local network). The fallback is lazily loaded to avoid bundle
55
+ // overhead for the common HTTPS case.
56
+ const {default: copy} = await import('copy-text-to-clipboard');
57
+ return copy(text);
58
+ }
59
+
47
60
  function useCopyButton() {
48
61
  const {
49
62
  metadata: {code},
@@ -52,12 +65,14 @@ function useCopyButton() {
52
65
  const copyTimeout = useRef<number | undefined>(undefined);
53
66
 
54
67
  const copyCode = useCallback(() => {
55
- navigator.clipboard.writeText(code).then(() => {
68
+ copyToClipboard(code).then(() => {
56
69
  setIsCopied(true);
57
70
  copyTimeout.current = window.setTimeout(() => {
58
71
  setIsCopied(false);
59
72
  }, 1000);
60
73
  });
74
+ // Errors are intentionally not caught so they remain unhandled and can
75
+ // be captured by observability tools (e.g. Sentry, PostHog).
61
76
  }, [code]);
62
77
 
63
78
  useEffect(() => () => window.clearTimeout(copyTimeout.current), []);
@@ -14,16 +14,23 @@ import styles from './styles.module.css';
14
14
 
15
15
  type Token = Props['line'][number];
16
16
 
17
- // Replaces '\n' by ''
18
- // Historical code, not sure why we even need this :/
17
+ // This <br/ seems useful when the line has no content to prevent collapsing.
18
+ // For code blocks with "diff" languages, this makes the empty lines collapse to
19
+ // zero height lines, which is undesirable.
20
+ // See also https://github.com/facebook/docusaurus/pull/11565
21
+ function LineBreak() {
22
+ return <br />;
23
+ }
24
+
25
+ // Replaces single lines with '\n' by '' so that we don't end up with
26
+ // duplicate line breaks (the '\n' + the artificial <br/> above)
27
+ // see also https://github.com/facebook/docusaurus/pull/11565
19
28
  function fixLineBreak(line: Token[]) {
20
29
  const singleLineBreakToken =
21
30
  line.length === 1 && line[0]!.content === '\n' ? line[0] : undefined;
22
-
23
31
  if (singleLineBreakToken) {
24
32
  return [{...singleLineBreakToken, content: ''}];
25
33
  }
26
-
27
34
  return line;
28
35
  }
29
36
 
@@ -35,7 +42,6 @@ export default function CodeBlockLine({
35
42
  getTokenProps,
36
43
  }: Props): ReactNode {
37
44
  const line = fixLineBreak(lineProp);
38
-
39
45
  const lineProps = getLineProps({
40
46
  line,
41
47
  className: clsx(classNames, showLineNumbers && styles.codeLine),
@@ -51,7 +57,7 @@ export default function CodeBlockLine({
51
57
  });
52
58
 
53
59
  return (
54
- <span {...lineProps}>
60
+ <div {...lineProps}>
55
61
  {showLineNumbers ? (
56
62
  <>
57
63
  <span className={styles.codeLineNumber} />
@@ -60,7 +66,7 @@ export default function CodeBlockLine({
60
66
  ) : (
61
67
  lineTokens
62
68
  )}
63
- <br />
64
- </span>
69
+ <LineBreak />
70
+ </div>
65
71
  );
66
72
  }
@@ -0,0 +1,27 @@
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} from 'react';
9
+ import clsx from 'clsx';
10
+ import {ThemeClassNames} from '@docusaurus/theme-common';
11
+ import type {Props} from '@theme/DocCard/Description';
12
+
13
+ import styles from './styles.module.css';
14
+
15
+ export default function DocCardDescription({description}: Props): ReactNode {
16
+ return (
17
+ <p
18
+ className={clsx(
19
+ 'text--truncate',
20
+ ThemeClassNames.docs.docCard.description,
21
+ styles.cardDescription,
22
+ )}
23
+ title={description}>
24
+ {description}
25
+ </p>
26
+ );
27
+ }
@@ -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
+ .cardDescription {
9
+ font-size: 0.8rem;
10
+ }
@@ -0,0 +1,22 @@
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} from 'react';
9
+ import clsx from 'clsx';
10
+ import {ThemeClassNames} from '@docusaurus/theme-common';
11
+ import type {Props} from '@theme/DocCard/Heading/Icon';
12
+
13
+ import styles from './styles.module.css';
14
+
15
+ export default function DocCardHeadingIcon({icon}: Props): ReactNode {
16
+ return (
17
+ <span
18
+ className={clsx(ThemeClassNames.docs.docCard.icon, styles.cardTitleIcon)}>
19
+ {icon}
20
+ </span>
21
+ );
22
+ }
@@ -0,0 +1,11 @@
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
+ .cardTitleIcon {
9
+ font-size: 1.6rem;
10
+ margin-right: 0.6rem;
11
+ }
@@ -0,0 +1,27 @@
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} from 'react';
9
+ import clsx from 'clsx';
10
+ import {ThemeClassNames} from '@docusaurus/theme-common';
11
+ import type {Props} from '@theme/DocCard/Heading/Text';
12
+
13
+ import styles from './styles.module.css';
14
+
15
+ export default function DocCardHeadingText({title}: Props): ReactNode {
16
+ return (
17
+ <span
18
+ className={clsx(
19
+ 'text--truncate',
20
+
21
+ ThemeClassNames.docs.docCard.title,
22
+ styles.cardTitleText,
23
+ )}>
24
+ {title}
25
+ </span>
26
+ );
27
+ }
@@ -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
+ .cardTitleText {
9
+ font-size: 1.2rem;
10
+ }
@@ -0,0 +1,28 @@
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} from 'react';
9
+ import clsx from 'clsx';
10
+ import {ThemeClassNames} from '@docusaurus/theme-common';
11
+ import Heading from '@theme/Heading';
12
+ import Icon from '@theme/DocCard/Heading/Icon';
13
+ import Text from '@theme/DocCard/Heading/Text';
14
+ import type {Props} from '@theme/DocCard/Heading';
15
+
16
+ import styles from './styles.module.css';
17
+
18
+ export default function DocCardHeading({item, title, icon}: Props): ReactNode {
19
+ return (
20
+ <Heading
21
+ as="h2"
22
+ className={clsx(ThemeClassNames.docs.docCard.heading, styles.cardTitle)}
23
+ title={title}>
24
+ {icon && <Icon item={item} icon={icon} />}
25
+ <Text item={item} title={title} />
26
+ </Heading>
27
+ );
28
+ }
@@ -0,0 +1,11 @@
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
+ .cardTitle {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ }
@@ -0,0 +1,55 @@
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} from 'react';
9
+ import clsx from 'clsx';
10
+ import Link from '@docusaurus/Link';
11
+ import {ThemeClassNames} from '@docusaurus/theme-common';
12
+ import Heading from '@theme/DocCard/Heading';
13
+ import Description from '@theme/DocCard/Description';
14
+ import type {Props} from '@theme/DocCard/Layout';
15
+
16
+ import styles from './styles.module.css';
17
+
18
+ function Container({
19
+ className,
20
+ href,
21
+ children,
22
+ }: {
23
+ className?: string;
24
+ href: string;
25
+ children: ReactNode;
26
+ }): ReactNode {
27
+ return (
28
+ <Link
29
+ href={href}
30
+ className={clsx(
31
+ 'card padding--lg',
32
+ ThemeClassNames.docs.docCard.container,
33
+ styles.cardContainer,
34
+ className,
35
+ )}>
36
+ {children}
37
+ </Link>
38
+ );
39
+ }
40
+
41
+ export default function DocCardLayout({
42
+ item,
43
+ className,
44
+ href,
45
+ icon,
46
+ title,
47
+ description,
48
+ }: Props): ReactNode {
49
+ return (
50
+ <Container href={href} className={className}>
51
+ <Heading item={item} icon={icon} title={title} />
52
+ {description && <Description item={item} description={description} />}
53
+ </Container>
54
+ );
55
+ }
@@ -24,11 +24,3 @@
24
24
  .cardContainer *:last-child {
25
25
  margin-bottom: 0;
26
26
  }
27
-
28
- .cardTitle {
29
- font-size: 1.2rem;
30
- }
31
-
32
- .cardDescription {
33
- font-size: 0.8rem;
34
- }
@@ -6,122 +6,71 @@
6
6
  */
7
7
 
8
8
  import React, {type ReactNode} from 'react';
9
- import clsx from 'clsx';
10
- import Link from '@docusaurus/Link';
11
9
  import {
12
10
  useDocById,
13
11
  findFirstSidebarItemLink,
14
12
  } from '@docusaurus/plugin-content-docs/client';
15
- import {usePluralForm} from '@docusaurus/theme-common';
13
+ import {
14
+ extractLeadingEmoji,
15
+ useDocCardDescriptionCategoryItemsPlural,
16
+ } from '@docusaurus/theme-common/internal';
16
17
  import isInternalUrl from '@docusaurus/isInternalUrl';
17
- import {translate} from '@docusaurus/Translate';
18
+ import Layout from '@theme/DocCard/Layout';
18
19
 
19
20
  import type {Props} from '@theme/DocCard';
20
- import Heading from '@theme/Heading';
21
21
  import type {
22
22
  PropSidebarItemCategory,
23
23
  PropSidebarItemLink,
24
24
  } from '@docusaurus/plugin-content-docs';
25
25
 
26
- import styles from './styles.module.css';
27
-
28
- function useCategoryItemsPlural() {
29
- const {selectMessage} = usePluralForm();
30
- return (count: number) =>
31
- selectMessage(
32
- count,
33
- translate(
34
- {
35
- message: '1 item|{count} items',
36
- id: 'theme.docs.DocCard.categoryDescription.plurals',
37
- description:
38
- 'The default description for a category card in the generated index about how many items this category includes',
39
- },
40
- {count},
41
- ),
42
- );
43
- }
44
-
45
- function CardContainer({
46
- className,
47
- href,
48
- children,
49
- }: {
50
- className?: string;
51
- href: string;
52
- children: ReactNode;
53
- }): ReactNode {
54
- return (
55
- <Link
56
- href={href}
57
- className={clsx('card padding--lg', styles.cardContainer, className)}>
58
- {children}
59
- </Link>
60
- );
26
+ function getFallbackEmojiIcon(
27
+ item: PropSidebarItemLink | PropSidebarItemCategory,
28
+ ): string {
29
+ if (item.type === 'category') {
30
+ return '🗃';
31
+ }
32
+ return isInternalUrl(item.href) ? '📄️' : '🔗';
61
33
  }
62
34
 
63
- function CardLayout({
64
- className,
65
- href,
66
- icon,
67
- title,
68
- description,
69
- }: {
70
- className?: string;
71
- href: string;
72
- icon: ReactNode;
73
- title: string;
74
- description?: string;
75
- }): ReactNode {
76
- return (
77
- <CardContainer href={href} className={className}>
78
- <Heading
79
- as="h2"
80
- className={clsx('text--truncate', styles.cardTitle)}
81
- title={title}>
82
- {icon} {title}
83
- </Heading>
84
- {description && (
85
- <p
86
- className={clsx('text--truncate', styles.cardDescription)}
87
- title={description}>
88
- {description}
89
- </p>
90
- )}
91
- </CardContainer>
92
- );
35
+ function getIconTitleProps(
36
+ item: PropSidebarItemLink | PropSidebarItemCategory,
37
+ ): {icon: ReactNode; title: string} {
38
+ const extracted = extractLeadingEmoji(item.label);
39
+ const emoji = extracted.emoji ?? getFallbackEmojiIcon(item);
40
+ return {
41
+ icon: emoji,
42
+ title: extracted.rest.trim(),
43
+ };
93
44
  }
94
45
 
95
46
  function CardCategory({item}: {item: PropSidebarItemCategory}): ReactNode {
96
47
  const href = findFirstSidebarItemLink(item);
97
- const categoryItemsPlural = useCategoryItemsPlural();
48
+ const categoryItemsPlural = useDocCardDescriptionCategoryItemsPlural();
98
49
 
99
50
  // Unexpected: categories that don't have a link have been filtered upfront
100
51
  if (!href) {
101
52
  return null;
102
53
  }
103
-
104
54
  return (
105
- <CardLayout
55
+ <Layout
56
+ item={item}
106
57
  className={item.className}
107
58
  href={href}
108
- icon="🗃️"
109
- title={item.label}
110
59
  description={item.description ?? categoryItemsPlural(item.items.length)}
60
+ {...getIconTitleProps(item)}
111
61
  />
112
62
  );
113
63
  }
114
64
 
115
65
  function CardLink({item}: {item: PropSidebarItemLink}): ReactNode {
116
- const icon = isInternalUrl(item.href) ? '📄️' : '🔗';
117
66
  const doc = useDocById(item.docId ?? undefined);
118
67
  return (
119
- <CardLayout
68
+ <Layout
69
+ item={item}
120
70
  className={item.className}
121
71
  href={item.href}
122
- icon={icon}
123
- title={item.label}
124
72
  description={item.description ?? doc?.description}
73
+ {...getIconTitleProps(item)}
125
74
  />
126
75
  );
127
76
  }