@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.
- package/README.md +18 -44
- package/lib/.tsbuildinfo-client +1 -1
- package/lib/components/demo/actions_bar/actions_bar.d.ts +3 -2
- package/lib/components/demo/actions_bar/actions_bar.js +2 -2
- package/lib/components/demo/code_transformer.js +1 -1
- package/lib/components/demo/codesandbox/open_action.js +7 -4
- package/lib/components/demo/demo.d.ts +30 -4
- package/lib/components/demo/demo.js +2 -2
- package/lib/components/version_switcher/index.d.ts +8 -1
- package/lib/components/version_switcher/index.js +35 -7
- package/lib/theme/Demo/actions.d.ts +2 -1
- package/package.json +3 -3
- package/src/components/demo/actions_bar/actions_bar.tsx +8 -2
- package/src/components/demo/code_transformer.ts +1 -1
- package/src/components/demo/codesandbox/open_action.tsx +8 -5
- package/src/components/demo/demo.tsx +32 -3
- package/src/components/version_switcher/index.tsx +43 -6
- package/src/theme/Demo/actions.tsx +2 -1
- package/src/theme/theme.d.ts +5 -1
|
@@ -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 ['"]([
|
|
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
|
|
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
|
-
...
|
|
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
|
|
15
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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:
|
|
43
|
-
const version =
|
|
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 =
|
|
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.
|
|
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": "^
|
|
44
|
-
"@elastic/eui-theme-borealis": "^
|
|
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
|
|
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 ['"]([
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
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
|
|
48
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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={
|
|
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 =
|
|
156
|
+
const version = allVersions[index];
|
|
120
157
|
const isCurrentVersion = version === currentVersion;
|
|
121
158
|
const screenReaderVersion = pronounceVersion(version!);
|
|
122
159
|
|
|
123
|
-
const url =
|
|
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>;
|
package/src/theme/theme.d.ts
CHANGED
|
@@ -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 {
|
|
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>;
|