@elastic/eui-docusaurus-theme 2.1.0 → 2.3.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
  }
@@ -19,5 +19,12 @@ export type VersionSwitcherProps = {
19
19
  * List of available versions to switch to
20
20
  */
21
21
  versions: string[];
22
+ /**
23
+ * URL to fetch the latest list of versions from.
24
+ * When provided, versions are fetched client-side from this URL,
25
+ * falling back to the static `versions` prop if the fetch fails.
26
+ * This ensures all deployed versions are always visible in the switcher.
27
+ */
28
+ versionsUrl?: string;
22
29
  };
23
- export declare const VersionSwitcher: ({ ariaLabel, currentVersion, extraAction, previousVersionUrl, versions, }: VersionSwitcherProps) => import("@emotion/react/jsx-runtime").JSX.Element | null;
30
+ export declare const VersionSwitcher: ({ ariaLabel, currentVersion, extraAction, previousVersionUrl, versions, versionsUrl, }: VersionSwitcherProps) => import("@emotion/react/jsx-runtime").JSX.Element | null;
@@ -6,7 +6,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "@emotion/react/jsx-runtime";
6
6
  * in compliance with, at your election, the Elastic License 2.0 or the Server
7
7
  * Side Public License, v 1.
8
8
  */
9
- import { useState } from 'react';
9
+ import { useEffect, useState } from 'react';
10
10
  import { css } from '@emotion/react';
11
11
  import { FixedSizeList } from 'react-window';
12
12
  import { EuiButtonEmpty, euiFocusRing, EuiListGroupItem, EuiPopover, useEuiMemoizedStyles, } from '@elastic/eui';
@@ -33,18 +33,46 @@ const getStyles = (euiThemeContext) => {
33
33
  const pronounceVersion = (version) => {
34
34
  return `version ${version.replaceAll('.', ' point ')}`;
35
35
  };
36
- export const VersionSwitcher = ({ ariaLabel, currentVersion, extraAction, previousVersionUrl, versions, }) => {
36
+ export const VersionSwitcher = ({ ariaLabel, currentVersion, extraAction, previousVersionUrl, versions, versionsUrl, }) => {
37
37
  const [isPopoverOpen, setPopoverOpen] = useState(false);
38
+ const [allVersions, setAllVersions] = useState(versions);
38
39
  const styles = useEuiMemoizedStyles(getStyles);
39
- if (!versions)
40
+ // Fetch the latest versions list from the main deployment
41
+ // to ensure the switcher always shows all available versions,
42
+ // even on older documentation deployments
43
+ useEffect(() => {
44
+ if (!versionsUrl)
45
+ return;
46
+ fetch(versionsUrl)
47
+ .then((response) => {
48
+ if (!response.ok)
49
+ throw new Error('Failed to fetch versions');
50
+ return response.json();
51
+ })
52
+ .then((data) => {
53
+ if (data?.euiVersions?.length) {
54
+ // Ensure the current version is always included
55
+ const fetchedVersions = data.euiVersions;
56
+ if (!fetchedVersions.includes(currentVersion)) {
57
+ fetchedVersions.push(currentVersion);
58
+ }
59
+ setAllVersions(fetchedVersions);
60
+ }
61
+ })
62
+ .catch(() => {
63
+ // Silently fall back to statically bundled versions
64
+ });
65
+ }, [versionsUrl, currentVersion]);
66
+ if (!allVersions?.length)
40
67
  return null;
68
+ const latestVersion = allVersions[0];
41
69
  const button = (_jsxs(EuiButtonEmpty, { size: "s", color: "text", iconType: "arrowDown", iconSide: "right", css: styles.button, onClick: () => setPopoverOpen((isOpen) => !isOpen), "aria-label": `${pronounceVersion(currentVersion)}. Click to switch versions`, children: ["v", currentVersion] }));
42
- return (_jsx(EuiPopover, { isOpen: isPopoverOpen, closePopover: () => setPopoverOpen(false), button: button, repositionOnScroll: true, panelPaddingSize: "xs", "aria-label": ariaLabel, children: _jsx(FixedSizeList, { className: "eui-yScroll", itemCount: versions.length, itemSize: 24, height: 200, width: 120, innerElementType: "ul", children: ({ index, style }) => {
43
- const version = versions[index];
70
+ return (_jsx(EuiPopover, { isOpen: isPopoverOpen, closePopover: () => setPopoverOpen(false), button: button, repositionOnScroll: true, panelPaddingSize: "xs", "aria-label": ariaLabel, children: _jsx(FixedSizeList, { className: "eui-yScroll", itemCount: allVersions.length, itemSize: 24, height: 200, width: 120, innerElementType: "ul", children: ({ index, style }) => {
71
+ const version = allVersions[index];
44
72
  const isCurrentVersion = version === currentVersion;
45
73
  const screenReaderVersion = pronounceVersion(version);
46
- const url = isCurrentVersion
47
- ? '/'
74
+ const url = version === latestVersion
75
+ ? `${previousVersionUrl}/`
48
76
  : `${previousVersionUrl}/v${version}/`;
49
77
  return (_jsx(EuiListGroupItem, { css: styles.listItem, style: style, size: "xs", label: `v${version}`, "aria-label": screenReaderVersion, href: url, isActive: isCurrentVersion, color: isCurrentVersion ? 'primary' : 'text', extraAction: extraAction?.(version) }));
50
78
  } }) }));
@@ -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[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elastic/eui-docusaurus-theme",
3
- "version": "2.1.0",
3
+ "version": "2.3.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.2.0",
44
- "@elastic/eui-theme-borealis": "^5.1.0",
43
+ "@elastic/eui": "^113.0.0",
44
+ "@elastic/eui-theme-borealis": "^6.0.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}
@@ -6,7 +6,7 @@
6
6
  * Side Public License, v 1.
7
7
  */
8
8
 
9
- import { ComponentProps, useState } from 'react';
9
+ import { ComponentProps, useEffect, useState } from 'react';
10
10
  import { css } from '@emotion/react';
11
11
  import { FixedSizeList } from 'react-window';
12
12
  import {
@@ -68,6 +68,13 @@ export type VersionSwitcherProps = {
68
68
  * List of available versions to switch to
69
69
  */
70
70
  versions: string[];
71
+ /**
72
+ * URL to fetch the latest list of versions from.
73
+ * When provided, versions are fetched client-side from this URL,
74
+ * falling back to the static `versions` prop if the fetch fails.
75
+ * This ensures all deployed versions are always visible in the switcher.
76
+ */
77
+ versionsUrl?: string;
71
78
  };
72
79
 
73
80
  export const VersionSwitcher = ({
@@ -76,11 +83,41 @@ export const VersionSwitcher = ({
76
83
  extraAction,
77
84
  previousVersionUrl,
78
85
  versions,
86
+ versionsUrl,
79
87
  }: VersionSwitcherProps) => {
80
88
  const [isPopoverOpen, setPopoverOpen] = useState(false);
89
+ const [allVersions, setAllVersions] = useState(versions);
81
90
  const styles = useEuiMemoizedStyles(getStyles);
82
91
 
83
- if (!versions) return null;
92
+ // Fetch the latest versions list from the main deployment
93
+ // to ensure the switcher always shows all available versions,
94
+ // even on older documentation deployments
95
+ useEffect(() => {
96
+ if (!versionsUrl) return;
97
+
98
+ fetch(versionsUrl)
99
+ .then((response) => {
100
+ if (!response.ok) throw new Error('Failed to fetch versions');
101
+ return response.json();
102
+ })
103
+ .then((data) => {
104
+ if (data?.euiVersions?.length) {
105
+ // Ensure the current version is always included
106
+ const fetchedVersions: string[] = data.euiVersions;
107
+ if (!fetchedVersions.includes(currentVersion)) {
108
+ fetchedVersions.push(currentVersion);
109
+ }
110
+ setAllVersions(fetchedVersions);
111
+ }
112
+ })
113
+ .catch(() => {
114
+ // Silently fall back to statically bundled versions
115
+ });
116
+ }, [versionsUrl, currentVersion]);
117
+
118
+ if (!allVersions?.length) return null;
119
+
120
+ const latestVersion = allVersions[0];
84
121
 
85
122
  const button = (
86
123
  <EuiButtonEmpty
@@ -109,19 +146,19 @@ export const VersionSwitcher = ({
109
146
  >
110
147
  <FixedSizeList
111
148
  className="eui-yScroll"
112
- itemCount={versions.length}
149
+ itemCount={allVersions.length}
113
150
  itemSize={24}
114
151
  height={200}
115
152
  width={120}
116
153
  innerElementType="ul"
117
154
  >
118
155
  {({ index, style }) => {
119
- const version = versions[index];
156
+ const version = allVersions[index];
120
157
  const isCurrentVersion = version === currentVersion;
121
158
  const screenReaderVersion = pronounceVersion(version!);
122
159
 
123
- const url = isCurrentVersion
124
- ? '/'
160
+ const url = version === latestVersion
161
+ ? `${previousVersionUrl}/`
125
162
  : `${previousVersionUrl}/v${version}/`;
126
163
 
127
164
  return (
@@ -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>;
@@ -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>;