@elastic/eui-docusaurus-theme 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
- import { DemoSourceMeta } from '../demo';
1
+ import { DemoSourceMeta, ExtraFiles } from '../demo';
2
2
  export interface DemoActionsBarProps {
3
3
  activeSource: DemoSourceMeta | null;
4
4
  sources: DemoSourceMeta[];
5
+ extraFiles?: ExtraFiles;
5
6
  isSourceOpen: boolean;
6
7
  setSourceOpen(isOpen: boolean): void;
7
8
  onClickReloadExample(): void;
8
9
  onClickCopyToClipboard(): void;
9
10
  }
10
- export declare const DemoActionsBar: ({ isSourceOpen, setSourceOpen, activeSource, sources, onClickReloadExample, onClickCopyToClipboard, }: DemoActionsBarProps) => import("@emotion/react/jsx-runtime").JSX.Element;
11
+ export declare const DemoActionsBar: ({ isSourceOpen, setSourceOpen, activeSource, sources, extraFiles, onClickReloadExample, onClickCopyToClipboard, }: DemoActionsBarProps) => import("@emotion/react/jsx-runtime").JSX.Element;
@@ -28,7 +28,7 @@ const getDemoActionsBarStyles = (euiTheme) => {
28
28
  `,
29
29
  };
30
30
  };
31
- export const DemoActionsBar = ({ isSourceOpen, setSourceOpen, activeSource, sources, onClickReloadExample, onClickCopyToClipboard, }) => {
31
+ export const DemoActionsBar = ({ isSourceOpen, setSourceOpen, activeSource, sources, extraFiles, onClickReloadExample, onClickCopyToClipboard, }) => {
32
32
  const styles = useEuiMemoizedStyles(getDemoActionsBarStyles);
33
- return (_jsxs(EuiFlexGroup, { alignItems: "center", css: styles.actionsBar, gutterSize: "s", children: [_jsx(EuiButton, { css: styles.button, onClick: () => setSourceOpen(!isSourceOpen), size: "s", color: "text", minWidth: false, children: isSourceOpen ? 'Hide source' : 'Show source' }), extraActions.map((ActionComponent) => (_jsx(ActionComponent, { sources: sources, activeSource: activeSource }))), _jsx(EuiToolTip, { content: "Copy to clipboard", children: _jsx(EuiButtonIcon, { size: "s", iconType: "copyClipboard", color: "text", onClick: onClickCopyToClipboard, "aria-label": "Copy code to clipboard" }) }), _jsx(EuiToolTip, { content: "Reload example", children: _jsx(EuiButtonIcon, { size: "s", iconType: "refresh", color: "text", onClick: onClickReloadExample, "aria-label": "Reload example" }) })] }));
33
+ return (_jsxs(EuiFlexGroup, { alignItems: "center", css: styles.actionsBar, gutterSize: "s", children: [_jsx(EuiButton, { css: styles.button, onClick: () => setSourceOpen(!isSourceOpen), size: "s", color: "text", minWidth: false, children: isSourceOpen ? 'Hide source' : 'Show source' }), extraActions.map((ActionComponent) => (_jsx(ActionComponent, { sources: sources, extraFiles: extraFiles, activeSource: activeSource }))), _jsx(EuiToolTip, { content: "Copy to clipboard", children: _jsx(EuiButtonIcon, { size: "s", iconType: "copyClipboard", color: "text", onClick: onClickCopyToClipboard, "aria-label": "Copy code to clipboard" }) }), _jsx(EuiToolTip, { content: "Reload example", children: _jsx(EuiButtonIcon, { size: "s", iconType: "refresh", color: "text", onClick: onClickReloadExample, "aria-label": "Reload example" }) })] }));
34
34
  };
@@ -5,7 +5,7 @@
5
5
  * in compliance with, at your election, the Elastic License 2.0 or the Server
6
6
  * Side Public License, v 1.
7
7
  */
8
- const IMPORT_REGEX = /^import [^'"]* from ['"]([^.'"\n ][^'"\n ]*)['"];?/gm;
8
+ const IMPORT_REGEX = /^import [^'"]* from ['"]([^'"\n ]*)['"];?/gm;
9
9
  const DEFAULT_EXPORT_REGEX = /export default /;
10
10
  const COMPONENT_ONLY_REGEX = /^\(?</;
11
11
  /**
@@ -61,11 +61,14 @@ const processTsxSource = (source) => {
61
61
  // to support that setting via tsconfig.json
62
62
  return `/** @jsxImportSource @emotion/react */\n${source}`;
63
63
  };
64
- export const createOpenInCodeSandboxAction = ({ files = {}, dependencies }) => ({ activeSource }) => {
64
+ export const createOpenInCodeSandboxAction = ({ files = {}, dependencies }) => ({ extraFiles, activeSource }) => {
65
65
  const parameters = useMemo(() => {
66
66
  const source = activeSource?.code || '';
67
67
  // Compute list of extra files that may be passed
68
- const extraFiles = Object.entries(files).reduce((acc, [file, content]) => {
68
+ const codeSandboxFiles = Object.entries({
69
+ ...files,
70
+ ...extraFiles,
71
+ }).reduce((acc, [file, content]) => {
69
72
  acc[file] = { content };
70
73
  return acc;
71
74
  }, {});
@@ -83,9 +86,9 @@ export const createOpenInCodeSandboxAction = ({ files = {}, dependencies }) => (
83
86
  },
84
87
  },
85
88
  },
86
- ...extraFiles,
89
+ ...codeSandboxFiles,
87
90
  },
88
91
  });
89
- }, [activeSource]);
92
+ }, [activeSource, extraFiles]);
90
93
  return (_jsxs("form", { action: "https://codesandbox.io/api/v1/sandboxes/define", method: "POST", target: "_blank", children: [_jsx("input", { type: "hidden", name: "parameters", value: parameters }), _jsx("input", { type: "hidden", name: "query", value: `module=/demo.tsx&view=split` }), _jsx(EuiToolTip, { content: "Open in CodeSandbox", children: _jsx(EuiButtonIcon, { type: "submit", size: "s", iconType: CodeSandboxIcon, color: "text", "aria-label": "Open in CodeSandbox" }) })] }));
91
94
  };
@@ -5,19 +5,45 @@ export interface DemoSourceMeta {
5
5
  isActive: boolean;
6
6
  filename?: string;
7
7
  }
8
+ export type ExtraFiles = Record<string, string>;
8
9
  export interface DemoProps extends PropsWithChildren {
9
10
  /**
10
11
  * Whether the source code editor is open by default
11
12
  */
12
13
  isSourceOpen?: boolean;
13
14
  /**
14
- * Allows to extend the default scope of the rendered demo and pass additional
15
- * properties available within the demo.
15
+ * Allows to pass additional variables available within the demo.
16
+ * The key is the variable name and the value is the variable itself (component, function, object, etc).
16
17
  *
17
- * The default scope exposes all React and EUI exports.
18
+ * @example
19
+ * ````mdx
20
+ * ```mdx-code-block
21
+ * import { MyComponent } from './my_component';
22
+ * ```
23
+ *
24
+ * <Demo scope={{ MyComponent }}>
25
+ * ```tsx
26
+ * export default () => <MyComponent />
27
+ * ```
28
+ * </Demo>
29
+ * ````
18
30
  */
19
31
  scope?: Record<string, unknown>;
32
+ /**
33
+ * Allows to pass extra files that will be added to the Codesandbox instance.
34
+ * The key is the filename and the value is the serialized file content.
35
+ *
36
+ * @example
37
+ * ````mdx
38
+ * ```mdx-code-block
39
+ * import iconSvgSource from '!raw-loader!./icon.svg';
40
+ * ```
41
+ *
42
+ * <Demo extraFiles={{ 'icon.svg': iconSvgSource }} />
43
+ * ````
44
+ */
45
+ extraFiles?: ExtraFiles;
20
46
  previewPadding?: DemoPreviewProps['padding'];
21
47
  previewWrapper?: DemoPreviewProps['wrapperComponent'];
22
48
  }
23
- export declare const Demo: ({ children, scope, isSourceOpen: _isSourceOpen, previewPadding, previewWrapper, }: DemoProps) => import("@emotion/react/jsx-runtime").JSX.Element;
49
+ export declare const Demo: ({ children, scope, extraFiles, isSourceOpen: _isSourceOpen, previewPadding, previewWrapper, }: DemoProps) => import("@emotion/react/jsx-runtime").JSX.Element;
@@ -31,7 +31,7 @@ const getDemoStyles = (euiTheme) => ({
31
31
  word-break: break-word;
32
32
  `,
33
33
  });
34
- export const Demo = ({ children, scope, isSourceOpen: _isSourceOpen = false, previewPadding, previewWrapper, }) => {
34
+ export const Demo = ({ children, scope, extraFiles, isSourceOpen: _isSourceOpen = false, previewPadding, previewWrapper, }) => {
35
35
  const styles = useEuiMemoizedStyles(getDemoStyles);
36
36
  const [sources, setSources] = useState([]);
37
37
  const [isSourceOpen, setIsSourceOpen] = useState(_isSourceOpen);
@@ -52,7 +52,7 @@ export const Demo = ({ children, scope, isSourceOpen: _isSourceOpen = false, pre
52
52
  const onClickReloadExample = useCallback(() => {
53
53
  setLiveProviderKey((liveProviderKey) => liveProviderKey + 1);
54
54
  }, []);
55
- return (_jsx("div", { css: styles.demo, children: _jsxs(DemoContext.Provider, { value: { sources, addSource }, children: [_jsxs(LiveProvider, { code: activeSource?.code || '', transformCode: demoCodeTransformer, theme: prismThemes.dracula, scope: finalScope, children: [_jsx(DemoPreview, { padding: previewPadding, wrapperComponent: previewWrapper }), _jsx(DemoActionsBar, { isSourceOpen: isSourceOpen, setSourceOpen: setIsSourceOpen, activeSource: activeSource, sources: sources, onClickCopyToClipboard: onClickCopyToClipboard, onClickReloadExample: onClickReloadExample }), isSourceOpen && _jsx(DemoEditor, {})] }, liveProviderKey), Children.map(children, (child, index) => {
55
+ return (_jsx("div", { css: styles.demo, children: _jsxs(DemoContext.Provider, { value: { sources, addSource }, children: [_jsxs(LiveProvider, { code: activeSource?.code || '', transformCode: demoCodeTransformer, theme: prismThemes.dracula, scope: finalScope, children: [_jsx(DemoPreview, { padding: previewPadding, wrapperComponent: previewWrapper }), _jsx(DemoActionsBar, { isSourceOpen: isSourceOpen, setSourceOpen: setIsSourceOpen, activeSource: activeSource, extraFiles: extraFiles, sources: sources, onClickCopyToClipboard: onClickCopyToClipboard, onClickReloadExample: onClickReloadExample }), isSourceOpen && _jsx(DemoEditor, {})] }, liveProviderKey), Children.map(children, (child, index) => {
56
56
  if (isElement(child) && child.type === DemoSource) {
57
57
  return child;
58
58
  }
@@ -1,8 +1,9 @@
1
1
  import { ComponentType } from 'react';
2
- import { DemoSourceMeta } from '../../components/demo/demo';
2
+ import { DemoSourceMeta, ExtraFiles } from '../../components/demo/demo';
3
3
  export type ActionComponentProps = {
4
4
  activeSource: DemoSourceMeta | null;
5
5
  sources: DemoSourceMeta[];
6
+ extraFiles?: ExtraFiles;
6
7
  };
7
8
  export type ActionComponent = ComponentType<ActionComponentProps>;
8
9
  export declare const extraActions: ActionComponent[];
@@ -3,13 +3,16 @@ import Link from '@docusaurus/Link';
3
3
  import useBaseUrl from '@docusaurus/useBaseUrl';
4
4
  import { translate } from '@docusaurus/Translate';
5
5
  import { EuiIcon, useEuiMemoizedStyles } from '@elastic/eui';
6
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6
7
  import { getItemStyles } from '../item.styles';
7
8
  export default function HomeBreadcrumbItem() {
8
9
  const homeHref = useBaseUrl('/');
10
+ const { siteConfig } = useDocusaurusContext();
11
+ const { title } = siteConfig;
9
12
  const styles = useEuiMemoizedStyles(getItemStyles);
10
13
  return (_jsxs("li", { className: "breadcrumbs__item", css: styles.item, children: [_jsx(Link, { "aria-label": translate({
11
14
  id: 'theme.docs.breadcrumbs.home',
12
15
  message: 'Home page',
13
16
  description: 'The ARIA label for the home page in the breadcrumbs',
14
- }), className: "breadcrumbs__link", href: homeHref, children: "EUI" }), _jsx(EuiIcon, { type: "arrowRight", size: "s", css: styles.icon })] }));
17
+ }), className: "breadcrumbs__link", href: homeHref, children: title }), _jsx(EuiIcon, { type: "arrowRight", size: "s", css: styles.icon })] }));
15
18
  }
@@ -0,0 +1,5 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { PropSidebarItemCategory, PropSidebarItemLink } from '@docusaurus/plugin-content-docs';
3
+ export default function DocCard({ item, }: {
4
+ item: PropSidebarItemCategory | PropSidebarItemLink;
5
+ }): ReactNode;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
+ import { useDocById, findFirstSidebarItemLink, } from '@docusaurus/plugin-content-docs/client';
3
+ import { usePluralForm } from '@docusaurus/theme-common';
4
+ import isInternalUrl from '@docusaurus/isInternalUrl';
5
+ import { translate } from '@docusaurus/Translate';
6
+ import { EuiCard, EuiIcon } from '@elastic/eui';
7
+ function useCategoryItemsPlural() {
8
+ const { selectMessage } = usePluralForm();
9
+ return (count) => selectMessage(count, translate({
10
+ message: '1 item|{count} items',
11
+ id: 'theme.docs.DocCard.categoryDescription.plurals',
12
+ description: 'The default description for a category card in the generated index about how many items this category includes',
13
+ }, { count }));
14
+ }
15
+ function CardLayout({ href, icon, title, description, }) {
16
+ return (_jsx(EuiCard, { icon: _jsx(EuiIcon, { size: "l", type: icon }), title: title, description: description || '', titleSize: "xs", layout: "horizontal", href: href }));
17
+ }
18
+ function CardCategory({ item }) {
19
+ const href = findFirstSidebarItemLink(item);
20
+ const categoryItemsPlural = useCategoryItemsPlural();
21
+ // Unexpected: categories that don't have a link have been filtered upfront
22
+ if (!href) {
23
+ return null;
24
+ }
25
+ return (_jsx(CardLayout, { href: href,
26
+ // Coincidentally, `folderOpen` is the same icon in EUI icon library
27
+ icon: "folderOpen", title: item.label, description: item.description ?? categoryItemsPlural(item.items.length) }));
28
+ }
29
+ function CardLink({ item }) {
30
+ // We update Docusaurus `link` icon to EUI `popout` icon
31
+ const icon = isInternalUrl(item.href) ? 'document' : 'popout';
32
+ const doc = useDocById(item.docId ?? undefined);
33
+ return (_jsx(CardLayout, { href: item.href, icon: icon, title: item.label, description: item.description ?? doc?.description }));
34
+ }
35
+ export default function DocCard({ item, }) {
36
+ switch (item.type) {
37
+ case 'link':
38
+ return _jsx(CardLink, { item: item });
39
+ case 'category':
40
+ return _jsx(CardCategory, { item: item });
41
+ default:
42
+ throw new Error(`unknown item type ${JSON.stringify(item)}`);
43
+ }
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elastic/eui-docusaurus-theme",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "EUI theme for Docusaurus",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "scripts": {
@@ -40,8 +40,8 @@
40
40
  "@docusaurus/theme-common": "^3.7.0",
41
41
  "@docusaurus/utils-validation": "^3.7.0",
42
42
  "@elastic/datemath": "^5.0.3",
43
- "@elastic/eui": "^109.1.0",
44
- "@elastic/eui-theme-borealis": "^5.0.0",
43
+ "@elastic/eui": "^112.1.0",
44
+ "@elastic/eui-theme-borealis": "^5.4.0",
45
45
  "@emotion/css": "^11.11.2",
46
46
  "@emotion/react": "^11.11.4",
47
47
  "@types/react-window": "^1.8.8",
@@ -17,11 +17,12 @@ import {
17
17
  } from '@elastic/eui';
18
18
  import { css } from '@emotion/react';
19
19
  import { extraActions } from '@theme/Demo/actions';
20
- import { DemoSourceMeta } from '../demo';
20
+ import { DemoSourceMeta, ExtraFiles } from '../demo';
21
21
 
22
22
  export interface DemoActionsBarProps {
23
23
  activeSource: DemoSourceMeta | null;
24
24
  sources: DemoSourceMeta[];
25
+ extraFiles?: ExtraFiles;
25
26
  isSourceOpen: boolean;
26
27
  setSourceOpen(isOpen: boolean): void;
27
28
  onClickReloadExample(): void;
@@ -53,6 +54,7 @@ export const DemoActionsBar = ({
53
54
  setSourceOpen,
54
55
  activeSource,
55
56
  sources,
57
+ extraFiles,
56
58
  onClickReloadExample,
57
59
  onClickCopyToClipboard,
58
60
  }: DemoActionsBarProps) => {
@@ -70,7 +72,11 @@ export const DemoActionsBar = ({
70
72
  {isSourceOpen ? 'Hide source' : 'Show source'}
71
73
  </EuiButton>
72
74
  {extraActions.map((ActionComponent) => (
73
- <ActionComponent sources={sources} activeSource={activeSource} />
75
+ <ActionComponent
76
+ sources={sources}
77
+ extraFiles={extraFiles}
78
+ activeSource={activeSource}
79
+ />
74
80
  ))}
75
81
  <EuiToolTip content="Copy to clipboard">
76
82
  <EuiButtonIcon
@@ -6,7 +6,7 @@
6
6
  * Side Public License, v 1.
7
7
  */
8
8
 
9
- const IMPORT_REGEX = /^import [^'"]* from ['"]([^.'"\n ][^'"\n ]*)['"];?/gm;
9
+ const IMPORT_REGEX = /^import [^'"]* from ['"]([^'"\n ]*)['"];?/gm;
10
10
  const DEFAULT_EXPORT_REGEX = /export default /;
11
11
  const COMPONENT_ONLY_REGEX = /^\(?</;
12
12
 
@@ -74,17 +74,20 @@ const processTsxSource = (source: string) => {
74
74
 
75
75
  export const createOpenInCodeSandboxAction =
76
76
  ({ files = {}, dependencies }: Options): ActionComponent =>
77
- ({ activeSource }) => {
77
+ ({ extraFiles, activeSource }) => {
78
78
  const parameters: string = useMemo(() => {
79
79
  const source = activeSource?.code || '';
80
80
 
81
81
  // Compute list of extra files that may be passed
82
- const extraFiles = Object.entries(files).reduce(
82
+ const codeSandboxFiles = Object.entries({
83
+ ...files,
84
+ ...extraFiles,
85
+ }).reduce(
83
86
  (acc, [file, content]) => {
84
87
  acc[file] = { content };
85
88
  return acc;
86
89
  },
87
- {} as Record<string, { content: string }>
90
+ {} as Record<string, { content: unknown }>
88
91
  );
89
92
 
90
93
  return getParameters({
@@ -101,10 +104,10 @@ export const createOpenInCodeSandboxAction =
101
104
  },
102
105
  },
103
106
  },
104
- ...extraFiles,
107
+ ...codeSandboxFiles,
105
108
  },
106
109
  } as any);
107
- }, [activeSource]);
110
+ }, [activeSource, extraFiles]);
108
111
 
109
112
  return (
110
113
  <form
@@ -38,18 +38,45 @@ export interface DemoSourceMeta {
38
38
  filename?: string;
39
39
  }
40
40
 
41
+ export type ExtraFiles = Record<string, string>;
42
+
41
43
  export interface DemoProps extends PropsWithChildren {
42
44
  /**
43
45
  * Whether the source code editor is open by default
44
46
  */
45
47
  isSourceOpen?: boolean;
46
48
  /**
47
- * Allows to extend the default scope of the rendered demo and pass additional
48
- * properties available within the demo.
49
+ * Allows to pass additional variables available within the demo.
50
+ * The key is the variable name and the value is the variable itself (component, function, object, etc).
51
+ *
52
+ * @example
53
+ * ````mdx
54
+ * ```mdx-code-block
55
+ * import { MyComponent } from './my_component';
56
+ * ```
49
57
  *
50
- * The default scope exposes all React and EUI exports.
58
+ * <Demo scope={{ MyComponent }}>
59
+ * ```tsx
60
+ * export default () => <MyComponent />
61
+ * ```
62
+ * </Demo>
63
+ * ````
51
64
  */
52
65
  scope?: Record<string, unknown>;
66
+ /**
67
+ * Allows to pass extra files that will be added to the Codesandbox instance.
68
+ * The key is the filename and the value is the serialized file content.
69
+ *
70
+ * @example
71
+ * ````mdx
72
+ * ```mdx-code-block
73
+ * import iconSvgSource from '!raw-loader!./icon.svg';
74
+ * ```
75
+ *
76
+ * <Demo extraFiles={{ 'icon.svg': iconSvgSource }} />
77
+ * ````
78
+ */
79
+ extraFiles?: ExtraFiles;
53
80
  previewPadding?: DemoPreviewProps['padding'];
54
81
  previewWrapper?: DemoPreviewProps['wrapperComponent'];
55
82
  }
@@ -69,6 +96,7 @@ const getDemoStyles = (euiTheme: UseEuiTheme) => ({
69
96
  export const Demo = ({
70
97
  children,
71
98
  scope,
99
+ extraFiles,
72
100
  isSourceOpen: _isSourceOpen = false,
73
101
  previewPadding,
74
102
  previewWrapper,
@@ -123,6 +151,7 @@ export const Demo = ({
123
151
  isSourceOpen={isSourceOpen}
124
152
  setSourceOpen={setIsSourceOpen}
125
153
  activeSource={activeSource}
154
+ extraFiles={extraFiles}
126
155
  sources={sources}
127
156
  onClickCopyToClipboard={onClickCopyToClipboard}
128
157
  onClickReloadExample={onClickReloadExample}
@@ -8,11 +8,12 @@
8
8
 
9
9
  import { ComponentType } from 'react';
10
10
 
11
- import { DemoSourceMeta } from '../../components/demo/demo';
11
+ import { DemoSourceMeta, ExtraFiles } from '../../components/demo/demo';
12
12
 
13
13
  export type ActionComponentProps = {
14
14
  activeSource: DemoSourceMeta | null;
15
15
  sources: DemoSourceMeta[];
16
+ extraFiles?: ExtraFiles;
16
17
  };
17
18
 
18
19
  export type ActionComponent = ComponentType<ActionComponentProps>;
@@ -11,11 +11,14 @@ import Link from '@docusaurus/Link';
11
11
  import useBaseUrl from '@docusaurus/useBaseUrl';
12
12
  import { translate } from '@docusaurus/Translate';
13
13
  import { EuiIcon, useEuiMemoizedStyles } from '@elastic/eui';
14
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
14
15
 
15
16
  import { getItemStyles } from '../item.styles';
16
17
 
17
18
  export default function HomeBreadcrumbItem(): JSX.Element {
18
19
  const homeHref = useBaseUrl('/');
20
+ const { siteConfig } = useDocusaurusContext();
21
+ const { title } = siteConfig;
19
22
 
20
23
  const styles = useEuiMemoizedStyles(getItemStyles);
21
24
 
@@ -30,7 +33,7 @@ export default function HomeBreadcrumbItem(): JSX.Element {
30
33
  className="breadcrumbs__link"
31
34
  href={homeHref}
32
35
  >
33
- EUI
36
+ {title}
34
37
  </Link>
35
38
  <EuiIcon type="arrowRight" size="s" css={styles.icon} />
36
39
  </li>
@@ -0,0 +1,104 @@
1
+ import { type ReactNode } from 'react';
2
+ import {
3
+ useDocById,
4
+ findFirstSidebarItemLink,
5
+ } from '@docusaurus/plugin-content-docs/client';
6
+ import { usePluralForm } from '@docusaurus/theme-common';
7
+ import isInternalUrl from '@docusaurus/isInternalUrl';
8
+ import { translate } from '@docusaurus/Translate';
9
+
10
+ import type {
11
+ PropSidebarItemCategory,
12
+ PropSidebarItemLink,
13
+ } from '@docusaurus/plugin-content-docs';
14
+
15
+ import { EuiCard, EuiIcon } from '@elastic/eui';
16
+
17
+ function useCategoryItemsPlural() {
18
+ const { selectMessage } = usePluralForm();
19
+ return (count: number) =>
20
+ selectMessage(
21
+ count,
22
+ translate(
23
+ {
24
+ message: '1 item|{count} items',
25
+ id: 'theme.docs.DocCard.categoryDescription.plurals',
26
+ description:
27
+ 'The default description for a category card in the generated index about how many items this category includes',
28
+ },
29
+ { count }
30
+ )
31
+ );
32
+ }
33
+
34
+ function CardLayout({
35
+ href,
36
+ icon,
37
+ title,
38
+ description,
39
+ }: {
40
+ href: string;
41
+ icon: string;
42
+ title: string;
43
+ description?: string;
44
+ }): ReactNode {
45
+ return (
46
+ <EuiCard
47
+ icon={<EuiIcon size="l" type={icon} />}
48
+ title={title}
49
+ description={description || ''}
50
+ titleSize="xs"
51
+ layout="horizontal"
52
+ href={href}
53
+ />
54
+ );
55
+ }
56
+
57
+ function CardCategory({ item }: { item: PropSidebarItemCategory }): ReactNode {
58
+ const href = findFirstSidebarItemLink(item);
59
+ const categoryItemsPlural = useCategoryItemsPlural();
60
+
61
+ // Unexpected: categories that don't have a link have been filtered upfront
62
+ if (!href) {
63
+ return null;
64
+ }
65
+
66
+ return (
67
+ <CardLayout
68
+ href={href}
69
+ // Coincidentally, `folderOpen` is the same icon in EUI icon library
70
+ icon="folderOpen"
71
+ title={item.label}
72
+ description={item.description ?? categoryItemsPlural(item.items.length)}
73
+ />
74
+ );
75
+ }
76
+
77
+ function CardLink({ item }: { item: PropSidebarItemLink }): ReactNode {
78
+ // We update Docusaurus `link` icon to EUI `popout` icon
79
+ const icon = isInternalUrl(item.href) ? 'document' : 'popout';
80
+ const doc = useDocById(item.docId ?? undefined);
81
+ return (
82
+ <CardLayout
83
+ href={item.href}
84
+ icon={icon}
85
+ title={item.label}
86
+ description={item.description ?? doc?.description}
87
+ />
88
+ );
89
+ }
90
+
91
+ export default function DocCard({
92
+ item,
93
+ }: {
94
+ item: PropSidebarItemCategory | PropSidebarItemLink;
95
+ }): ReactNode {
96
+ switch (item.type) {
97
+ case 'link':
98
+ return <CardLink item={item} />;
99
+ case 'category':
100
+ return <CardCategory item={item} />;
101
+ default:
102
+ throw new Error(`unknown item type ${JSON.stringify(item)}`);
103
+ }
104
+ }
@@ -585,11 +585,15 @@ declare module '@theme/Demo/default_scope' {
585
585
 
586
586
  declare module '@theme/Demo/actions' {
587
587
  import type { ComponentType } from 'react';
588
- import type { DemoSourceMeta } from '@elastic/eui-docusaurus-theme/components/demo/demo';
588
+ import type {
589
+ DemoSourceMeta,
590
+ ExtraFiles,
591
+ } from '@elastic/eui-docusaurus-theme/components/demo/demo';
589
592
 
590
593
  export type ActionComponentProps = {
591
594
  activeSource: DemoSourceMeta;
592
595
  sources: DemoSourceMeta[];
596
+ extraFiles?: ExtraFiles;
593
597
  };
594
598
 
595
599
  export type ActionComponent = ComponentType<ActionComponentProps>;