diff --git a/.eslintignore b/.eslintignore
index 8073a845040..dd647b1a6ba 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,6 +1,7 @@
# Force include
!.*.js
!.buildkite
+!.storybook
!packages/charts/.*.js
# Ignore
diff --git a/.storybook/backgrounds.ts b/.storybook/backgrounds.ts
new file mode 100644
index 00000000000..e8f6b2b436b
--- /dev/null
+++ b/.storybook/backgrounds.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import euiBorealisDarkVars from '@elastic/eui-theme-borealis/lib/eui_theme_borealis_dark.json';
+import euiBorealisLightVars from '@elastic/eui-theme-borealis/lib/eui_theme_borealis_light.json';
+
+interface BackgroundsOption {
+ name: string;
+ value: string;
+}
+type BackgroundsOptions = Record
+
+export const backgroundsOptions = {
+ emptyShadeDark: { name: 'Empty Shade - Dark', value: euiBorealisDarkVars.euiColorEmptyShade },
+ emptyShadeLight: { name: 'Empty Shade - Light', value: euiBorealisLightVars.euiColorEmptyShade },
+ black: { name: 'Black', value: '#000' },
+ white: { name: 'White', value: '#fff' },
+ red: { name: 'Red', value: '#f04d9a' },
+ blue: { name: 'Blue', value: '#14abf5' },
+ yellow: { name: 'Yellow', value: '#fec709' },
+ green: { name: 'Green', value: '#00c1b4' },
+ gray: { name: 'Gray', value: 'rgb(237, 240, 245)' },
+} satisfies BackgroundsOptions
+
+export type BackgroundKey = keyof (typeof backgroundsOptions);
diff --git a/.storybook/decorator.tsx b/.storybook/decorator.tsx
new file mode 100644
index 00000000000..13eeb63e352
--- /dev/null
+++ b/.storybook/decorator.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiProvider, EuiMarkdownFormat, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
+import classNames from 'classnames';
+import type { CSSProperties, FC, PropsWithChildren } from 'react';
+import React, { useEffect } from 'react';
+import type { DecoratorFunction } from 'storybook/internal/types';
+import events from 'storybook/internal/core-events';
+
+import type { StoryGlobals, StoryParameters } from './types';
+import { ThemeId, ThemeIdProvider, BackgroundIdProvider } from '@ech/sb';
+
+import './load_icons';
+import { getThemeFromTitle } from './themes';
+import type { BackgroundKey } from './backgrounds';
+import { addons } from 'storybook/internal/preview-api';
+
+const ResizeWrapper: FC> = ({ resize, children }) =>
+ resize ? (
+
+ {children}
+
+ ) : (
+ <>{children}>
+ );
+
+export const StoryDecorator: DecoratorFunction = (Story, context) => {
+ if (!Story) return No Story
;
+
+ const globals = (context.globals as StoryGlobals) ?? {};
+
+ const parameters = (context.parameters as StoryParameters) ?? {};
+
+ const themeId = getThemeFromTitle(globals.theme) ?? ThemeId.Light;
+ const backgroundId = globals.backgrounds.value as BackgroundKey;
+ const {
+ showHeader = false,
+ showChartTitle = false,
+ showChartDescription = false,
+ showChartBoundary = false,
+ } = globals.toggles ?? {};
+ const { markdown, resize } = parameters;
+ const colorMode = themeId.includes('light') ? 'light' : 'dark';
+
+ const channel = addons.getChannel();
+
+ // useEffect(() => {
+ // const event = events.ARGTYPES_INFO_REQUEST
+ // channel.on(event, (payload) => {
+ // console.log(event, payload);
+ // });
+ // }, [])
+ useEffect(() => {
+ const eventss = [
+ events.ARGTYPES_INFO_REQUEST,
+ events.ARGTYPES_INFO_RESPONSE,
+ events.STORY_ARGS_UPDATED,
+ events.RESET_STORY_ARGS,
+ events.UPDATE_STORY_ARGS,
+ ]
+ Object.values(eventss).forEach((event) => {
+ channel.on(event, (payload) => {
+ console.log(event, payload);
+ });
+ });
+ }, [])
+
+ return (
+
+
+
+
+ {showHeader && !showChartTitle && !showChartDescription && (
+
+
+
+
+ {context.title}
+
+
+
+
+
+ {context.name}
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ {markdown && (
+
+ {markdown}
+
+ )}
+
+
+
+
+ );
+};
diff --git a/.storybook/load_icons.ts b/.storybook/load_icons.ts
new file mode 100644
index 00000000000..87adfec368c
--- /dev/null
+++ b/.storybook/load_icons.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+// @ts-nocheck - EUI icon assets have no types
+// see https://github.com/elastic/eui/issues/5463
+
+import { icon as visualizeAppIcon } from '@elastic/eui/es/components/icon/assets/app_visualize';
+import { icon as arrowDownIcon } from '@elastic/eui/es/components/icon/assets/arrow_down';
+import { icon as arrowLeftIcon } from '@elastic/eui/es/components/icon/assets/arrow_left';
+import { icon as arrowRightIcon } from '@elastic/eui/es/components/icon/assets/arrow_right';
+import { icon as arrowUpIcon } from '@elastic/eui/es/components/icon/assets/arrow_up';
+import { icon as checkIcon } from '@elastic/eui/es/components/icon/assets/check';
+import { icon as filterIcon } from '@elastic/eui/es/components/icon/assets/filter';
+import { icon as iInCircleIcon } from '@elastic/eui/es/components/icon/assets/iInCircle';
+import { icon as pencilIcon } from '@elastic/eui/es/components/icon/assets/pencil';
+import { icon as starFilledIcon } from '@elastic/eui/es/components/icon/assets/star_filled';
+import { icon as tokenKeyIcon } from '@elastic/eui/es/components/icon/assets/tokenKey';
+import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon';
+
+appendIconComponentCache({
+ arrowUp: arrowUpIcon,
+ arrowLeft: arrowLeftIcon,
+ arrowDown: arrowDownIcon,
+ arrowRight: arrowRightIcon,
+ iInCircle: iInCircleIcon,
+ tokenKey: tokenKeyIcon,
+ filter: filterIcon,
+ starFilled: starFilledIcon,
+ pencil: pencilIcon,
+ visualizeApp: visualizeAppIcon,
+ check: checkIcon,
+});
diff --git a/.storybook/main.ts b/.storybook/main.ts
new file mode 100644
index 00000000000..2adb4766844
--- /dev/null
+++ b/.storybook/main.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import path from 'path';
+
+import type { StorybookConfig } from '@storybook/react-vite';
+import { mergeConfig } from 'vite';
+
+const config: StorybookConfig = {
+ stories: ['./stories/**/*.mdx', './stories/**/*.story.@(js|jsx|mjs|ts|tsx)'],
+ addons: [
+ // '@storybook/addon-docs',
+ '@storybook/addon-a11y',
+ 'storybook-addon-toggles',
+ '@storybook/addon-themes',
+ ],
+ framework: {
+ name: '@storybook/react-vite',
+ options: {
+ strictMode: false, // disable react strict mode
+ },
+ },
+ features: {
+ outline: false,
+ interactions: false,
+ },
+ viteFinal: (sbViteConfig) => {
+ return mergeConfig(sbViteConfig, {
+ resolve: {
+ alias: [
+ {
+ find: /^@elastic\/charts\/(.*)/,
+ replacement: path.resolve(__dirname, '../packages/charts/$1'),
+ },
+ {
+ find: '@elastic/charts',
+ replacement: path.resolve(__dirname, '../packages/charts/src/index.ts'),
+ },
+ {
+ // Need to resolve `@elastic/` sass imports in `@elastic/` packages from eui
+ find: 'node_modules/@elastic',
+ replacement: path.resolve(__dirname, '../node_modules/@elastic'),
+ },
+ {
+ // Alias for shared storybook utils, components and types
+ find: '@ech/sb',
+ replacement: path.resolve(__dirname, './shared'),
+ },
+ ],
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ quietDeps: true, // silence divider deprecation warning messages
+ },
+ },
+ },
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('development'),
+ },
+ });
+ },
+};
+
+export default config;
diff --git a/.storybook/manager.ts b/.storybook/manager.ts
new file mode 100644
index 00000000000..8c42e86ece8
--- /dev/null
+++ b/.storybook/manager.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { addons } from 'storybook/manager-api';
+import { create } from 'storybook/theming';
+
+// Detect system theme preference
+const getSystemTheme = () => {
+ if (typeof window !== 'undefined' && window.matchMedia) {
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+ return 'light';
+};
+
+// Create themes for both light and dark
+const lightTheme = create({
+ base: 'light',
+ brandTitle: 'Elastic Charts',
+ brandUrl: 'https://github.com/elastic/elastic-charts',
+ brandImage: 'logo-name.svg',
+});
+
+const darkTheme = create({
+ base: 'dark',
+ brandTitle: 'Elastic Charts',
+ brandUrl: 'https://github.com/elastic/elastic-charts',
+ brandImage: 'logo-name.svg',
+});
+
+// Set initial theme
+addons.setConfig({
+ theme: getSystemTheme() === 'dark' ? darkTheme : lightTheme,
+});
+
+// Listen for system theme changes
+if (typeof window !== 'undefined' && window.matchMedia) {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ const handleThemeChange = (e: MediaQueryListEvent) => {
+ addons.setConfig({
+ theme: e.matches ? darkTheme : lightTheme,
+ });
+ };
+
+ mediaQuery.addEventListener('change', handleThemeChange);
+}
diff --git a/.storybook/package.json b/.storybook/package.json
new file mode 100644
index 00000000000..8759496b9d1
--- /dev/null
+++ b/.storybook/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "charts-storybook-2",
+ "description": "Storybook demo for @elastic/charts library",
+ "license": "Apache-2.0",
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "start": "storybook dev -p 9001 -c ../.storybook --ci --no-version-updates",
+ "build": "rm -rf ../.out && storybook build -c ../.storybook -o ../.out",
+ "build:firebase": "storybook build -c ../.storybook -o ../e2e_server/public",
+ "typecheck": "tsc -p ./tsconfig.json --noEmit"
+ },
+ "devDependencies": {
+ "@storybook/addon-a11y": "^9.0.0",
+ "@storybook/addon-docs": "^9.0.0",
+ "@storybook/react-vite": "^9.0.0",
+ "@storybook/addon-themes": "^9.0.0",
+ "storybook-addon-toggles": "^0.0.8",
+ "storybook": "^9.0.0",
+ "typescript": "^5.5.3",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/.storybook/parameters.ts b/.storybook/parameters.ts
new file mode 100644
index 00000000000..2523554a0b7
--- /dev/null
+++ b/.storybook/parameters.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Parameters as SBParameters } from '@storybook/addons';
+import type { TogglesParameter } from 'storybook-addon-toggles';
+
+import { backgroundsOptions } from './backgrounds';
+
+type Parameters = SBParameters & TogglesParameter;
+
+export const parameters: Parameters = {
+ backgrounds: {
+ options: backgroundsOptions,
+ },
+ toggles: {
+ options: [
+ {
+ id: 'showHeader',
+ title: 'Show story header',
+ defaultValue: true,
+ disabled: {
+ showChartTitle: true,
+ showChartDescription: true,
+ },
+ },
+ {
+ id: 'showChartTitle',
+ title: 'Show chart title',
+ defaultValue: false,
+ },
+ {
+ id: 'showChartDescription',
+ title: 'Show chart description',
+ defaultValue: false,
+ },
+ {
+ id: 'showChartBoundary',
+ title: 'Show chart boundary',
+ defaultValue: false,
+ },
+ ],
+ },
+
+ viewport: {
+ options: {
+ vrt: {
+ // to match vrt default viewport to help with mouse positioning
+ // See e2e/playwright.config.ts#L20
+ name: 'VRT Viewport',
+ styles: {
+ width: '785px',
+ height: '1000px',
+ },
+ },
+ },
+ },
+};
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
new file mode 100644
index 00000000000..5767e43786f
--- /dev/null
+++ b/.storybook/preview.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { withThemeByClassName } from '@storybook/addon-themes';
+import type { Preview } from '@storybook/react-vite';
+
+import './style.scss';
+
+import { StoryDecorator } from './decorator';
+import { parameters } from './parameters';
+import { getThemeOptions } from './themes';
+
+const preview: Preview = {
+ parameters: {
+ ...parameters,
+ react: {
+ strictMode: false, // Disable Strict Mode
+ },
+ },
+ decorators: [
+ withThemeByClassName(getThemeOptions('light')),
+ StoryDecorator,
+ ],
+};
+
+export default preview;
diff --git a/.storybook/shared/components/get_color_picker.tsx b/.storybook/shared/components/get_color_picker.tsx
new file mode 100644
index 00000000000..813bba782b9
--- /dev/null
+++ b/.storybook/shared/components/get_color_picker.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { PopoverAnchorPosition } from '@elastic/eui';
+import { EuiButton, EuiButtonEmpty, EuiColorPicker, EuiFlexItem, EuiSpacer, EuiWrappingPopover } from '@elastic/eui';
+import type { FC } from 'react';
+import React from 'react';
+
+import type { LegendColorPickerProps } from '@elastic/charts';
+
+export const getColorPicker =
+ (anchorPosition: PopoverAnchorPosition = 'leftCenter'): FC =>
+ ({ anchor, color, onClose, onChange }) => (
+
+
+
+
+ {
+ onChange(null);
+ anchor.focus();
+ onClose();
+ }}
+ title="Clear color selection"
+ >
+ Clear color
+
+
+
+ Done
+
+
+ );
diff --git a/.storybook/shared/components/get_legend_action.tsx b/.storybook/shared/components/get_legend_action.tsx
new file mode 100644
index 00000000000..73cb9ccd424
--- /dev/null
+++ b/.storybook/shared/components/get_legend_action.tsx
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { PopoverAnchorPosition, EuiContextMenuPanelDescriptor } from '@elastic/eui';
+import { EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
+import React, { useState } from 'react';
+
+import type { LegendAction, XYChartSeriesIdentifier } from '@elastic/charts';
+import { useLegendAction } from '@elastic/charts';
+
+export const getLegendAction =
+ (anchorPosition: PopoverAnchorPosition = 'leftCenter'): LegendAction =>
+ ({ series, label }) => {
+ const [popoverOpen, setPopoverOpen] = useState(false);
+ const [ref, onClose] = useLegendAction();
+
+ const getPanels = (series: XYChartSeriesIdentifier[]): EuiContextMenuPanelDescriptor[] => [
+ {
+ id: 0,
+ title: label,
+ items: [
+ {
+ name: 'Alert series specId',
+ icon: ,
+ onClick: () => {
+ setPopoverOpen(false);
+ setTimeout(() => {
+ window.alert(`Selected series: ${series.map(({ specId }) => specId).join(', ')}`);
+ }, 100);
+ },
+ },
+ {
+ name: 'Alert series keys',
+ icon: ,
+ onClick: () => {
+ setPopoverOpen(false);
+ setTimeout(() => {
+ window.alert(
+ `Selected series: [${series.map(({ seriesKeys }) => seriesKeys.join(', ')).join(' -- ')}]`,
+ );
+ }, 100);
+ },
+ },
+ {
+ name: 'Filter series',
+ icon: ,
+ onClick: () => {
+ setPopoverOpen(false);
+ setTimeout(() => {
+ window.alert('Series Filtered!');
+ }, 100);
+ },
+ },
+ {
+ name: 'Like series',
+ icon: ,
+ onClick: () => {
+ setPopoverOpen(false);
+ setTimeout(() => {
+ window.alert('Series liked!!!');
+ }, 100);
+ },
+ },
+ ],
+ },
+ ];
+
+ const Button = (
+ setPopoverOpen(!popoverOpen)}
+ >
+
+
+ );
+
+ return (
+
+ );
+ };
diff --git a/.storybook/shared/components/index.ts b/.storybook/shared/components/index.ts
new file mode 100644
index 00000000000..d3796c89ad3
--- /dev/null
+++ b/.storybook/shared/components/index.ts
@@ -0,0 +1,2 @@
+export * from './get_color_picker'
+export * from './get_legend_action'
diff --git a/.storybook/shared/index.ts b/.storybook/shared/index.ts
new file mode 100644
index 00000000000..1464f012256
--- /dev/null
+++ b/.storybook/shared/index.ts
@@ -0,0 +1,2 @@
+export * from './components'
+export * from './utils'
diff --git a/.storybook/shared/utils/index.ts b/.storybook/shared/utils/index.ts
new file mode 100644
index 00000000000..b9949cf0d65
--- /dev/null
+++ b/.storybook/shared/utils/index.ts
@@ -0,0 +1 @@
+export * from './use_base_theme'
diff --git a/.storybook/shared/utils/use_base_theme.ts b/.storybook/shared/utils/use_base_theme.ts
new file mode 100644
index 00000000000..d0623a05a17
--- /dev/null
+++ b/.storybook/shared/utils/use_base_theme.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { createContext, useContext } from 'react';
+import type { $Values } from 'utility-types';
+
+import type { Theme } from '@elastic/charts';
+import {
+ LIGHT_THEME,
+ DARK_THEME,
+ LEGACY_DARK_THEME,
+ LEGACY_LIGHT_THEME,
+ AMSTERDAM_LIGHT_THEME,
+ AMSTERDAM_DARK_THEME,
+} from '@elastic/charts';
+import { mergePartial } from '@elastic/charts/src/utils/common';
+import { backgroundsOptions, type BackgroundKey } from '../../backgrounds';
+import { LEGACY_CHART_MARGINS } from '@elastic/charts/src';
+
+/**
+ * Available themes
+ * @internal
+ */
+export const ThemeId = Object.freeze({
+ Light: 'light' as const,
+ Dark: 'dark' as const,
+ // TODO remove legacy themes
+ LegacyAmsterdamLight: 'legacy-amsterdam-light' as const,
+ LegacyAmsterdamDark: 'legacy-amsterdam-dark' as const,
+ LegacyLight: 'legacy-light' as const,
+ LegacyDark: 'legacy-dark' as const,
+});
+/** @internal */
+export type ThemeId = $Values;
+
+const ThemeContext = createContext(ThemeId.Light);
+const BackgroundContext = createContext(undefined);
+
+export const ThemeIdProvider = ThemeContext.Provider;
+export const BackgroundIdProvider = BackgroundContext.Provider;
+
+const themeMap = {
+ [ThemeId.Light]: LIGHT_THEME,
+ [ThemeId.Dark]: DARK_THEME,
+ [ThemeId.LegacyAmsterdamLight]: AMSTERDAM_LIGHT_THEME,
+ [ThemeId.LegacyAmsterdamDark]: AMSTERDAM_DARK_THEME,
+ [ThemeId.LegacyLight]: LEGACY_LIGHT_THEME,
+ [ThemeId.LegacyDark]: LEGACY_DARK_THEME,
+};
+
+const getBackground = (backgroundId?: BackgroundKey) => {
+ if (!backgroundId) return
+
+ return backgroundsOptions[backgroundId]?.value
+};
+
+export const useThemeId = (): ThemeId => {
+ return useContext(ThemeContext);
+};
+
+export const useBaseTheme = (): Theme => {
+ const themeId = useThemeId();
+ const backgroundId = useContext(BackgroundContext);
+ const theme = themeMap[themeId] ?? LIGHT_THEME;
+ const backgroundColor = getBackground(backgroundId);
+
+ return mergePartial(theme, {
+ // Keep this just for consistency for the first pass of theme changes
+ chartMargins: LEGACY_CHART_MARGINS,
+ background: { color: backgroundColor },
+ });
+};
diff --git a/.storybook/stories/9_color_picker.story.tsx b/.storybook/stories/9_color_picker.story.tsx
new file mode 100644
index 00000000000..5b74c7b1597
--- /dev/null
+++ b/.storybook/stories/9_color_picker.story.tsx
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import React, { useState, useMemo } from 'react';
+import { fn } from 'storybook/test';
+
+import type { LegendColorPicker, Color, SeriesKey } from '@elastic/charts';
+import { Axis, BarSeries, Chart, Position, ScaleType, Settings, toEntries } from '@elastic/charts';
+import { BARCHART_1Y1G } from '@elastic/charts/src/utils/data_samples/test_dataset';
+
+import { getColorPicker, useBaseTheme, getLegendAction } from '@ech/sb';
+
+export const Component = ({ onChangeAction, showLegendAction = false }: any, { chartProps }: any) => {
+ const [colors, setColors] = useState>({});
+
+ const CustomColorPicker: LegendColorPicker = useMemo(
+ () =>
+ ({ anchor, color, onClose, seriesIdentifiers, onChange }) => {
+ const handleClose = () => {
+ onClose();
+ setColors((prevColors) => ({
+ ...prevColors,
+ ...toEntries(seriesIdentifiers, 'key', color),
+ }));
+ };
+ const handleChange = (c: Color | null) => {
+ setColors((prevColors) => ({
+ ...prevColors,
+ ...toEntries(seriesIdentifiers, 'key', c),
+ }));
+ onChange(c);
+ onChangeAction(c);
+ };
+
+ return getColorPicker()({
+ anchor,
+ color,
+ onClose: handleClose,
+ onChange: handleChange,
+ seriesIdentifiers,
+ });
+ },
+ [setColors, onChangeAction],
+ );
+ CustomColorPicker.displayName = 'CustomColorPicker';
+ return (
+
+
+
+ Number(d).toFixed(2)} />
+
+ colors[key] ?? null}
+ />
+
+ );
+};
+
+const meta = {
+ title: 'Specifications/Color Picker',
+ component: Component,
+ parameters: {
+ markdown:
+ 'Elastic charts will maintain the color selection in memory beyond chart updates. However, to persist colors beyond browser refresh the consumer would need to manage the color state and use the color prop on the SeriesSpec to assign a color via a SeriesColorAccessor.\n\n __Note:__ the context menu, color picker and popover are supplied by [eui](https://elastic.github.io/eui/#).',
+ },
+ argTypes: {
+ showLegendAction: {
+ control: { type: 'boolean' },
+ description: 'Show legend action',
+ },
+ },
+ args: {
+ showLegendAction: false,
+ onChangeAction: fn(),
+ },
+} satisfies Meta;
+
+export default meta;
+// type Story = StoryObj;
diff --git a/.storybook/stories/bar/1_basic.story.tsx b/.storybook/stories/bar/1_basic.story.tsx
new file mode 100644
index 00000000000..75812b02eeb
--- /dev/null
+++ b/.storybook/stories/bar/1_basic.story.tsx
@@ -0,0 +1,118 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import React from 'react';
+
+import { BarSeries, Chart, ScaleType, Settings } from '@elastic/charts';
+import { useBaseTheme } from '@ech/sb';
+// import { text, select, boolean, resetKnobs } from '../../controls++/knobs';
+// import type { ChartsStory } from '../../types';
+
+export const Component = (args: any, { chartProps, ...context }: any) => {
+ // export const Component: ChartsStory = (args, { title, description }) => {
+ const { toggleBarSpec = true } = args;
+ // text('text', 'default');
+
+ const data1 = [
+ { x: 0, y: 2 },
+ { x: 1, y: 7 },
+ { x: 2, y: 3 },
+ { x: 3, y: 6 },
+ ];
+ const data2 = data1.map((datum) => ({ ...datum, y: datum.y - 1 }));
+ const data = toggleBarSpec ? data1 : data2;
+ const specId = toggleBarSpec ? 'bars1' : 'bars2';
+
+ console.log(context);
+
+ return (
+
+
+
+
+ );
+};
+
+export const Component2 = (args: any, { chartProps, ...context }: any) => {
+ // export const Component: ChartsStory = (args, { title, description }) => {
+ const { toggleBarSpec = true } = args;
+ // text('text', 'default');
+
+ const data1 = [
+ { x: 0, y: 2 },
+ { x: 1, y: 7 },
+ { x: 2, y: 3 },
+ { x: 3, y: 6 },
+ ];
+ const data2 = data1.map((datum) => ({ ...datum, y: datum.y - 1 }));
+ const data = toggleBarSpec ? data1 : data2;
+ const specId = toggleBarSpec ? 'bars1' : 'bars2';
+
+ console.log(context);
+
+ return (
+
+
+
+
+ );
+};
+
+// TODO type Meta with params
+const meta = {
+ title: 'Specifications/Bar',
+ component: Component,
+ // parameters: {
+ // backgrounds: {
+ // disable: true,
+ // },
+ // },
+ argTypes: {
+ prop1: {
+ type: "boolean",
+ table: {
+ category: "foo",
+ // subcategory: "bar",
+ },
+ },
+ toggleBarSpec: {
+ control: { type: 'boolean' },
+ description: 'Toggle between two different bar specifications',
+ },
+ },
+ // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
+ args: {
+ // onClick: fn(),
+ toggleBarSpec: true,
+ },
+ // globals: {
+ // backgrounds: {
+ // value: undefined
+ // }
+ // }
+} satisfies Meta;
+
+export default meta;
+// type Story = StoryObj;
diff --git a/.storybook/style.scss b/.storybook/style.scss
new file mode 100644
index 00000000000..cab605ba75e
--- /dev/null
+++ b/.storybook/style.scss
@@ -0,0 +1,234 @@
+/* atkinson-hyperlegible-regular - latin */
+@font-face {
+ font-family: 'Atkinson Hyperlegible';
+ font-style: normal;
+ font-weight: 400;
+ src:
+ local(''),
+ url('../public/fonts/atkinson-hyperlegible-v1-latin-regular.woff2') format('woff2'),
+ /* Chrome 26+, Opera 23+, Firefox 39+ */ url('../public/fonts/atkinson-hyperlegible-v1-latin-regular.woff')
+ format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* atkinson-hyperlegible-700 - latin */
+@font-face {
+ font-family: 'Atkinson Hyperlegible';
+ font-style: normal;
+ font-weight: 700;
+ src:
+ local(''),
+ url('../public/fonts/atkinson-hyperlegible-v1-latin-700.woff2') format('woff2'),
+ /* Chrome 26+, Opera 23+, Firefox 39+ */ url('../public/fonts/atkinson-hyperlegible-v1-latin-700.woff')
+ format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+html {
+ font-size: 14px !important;
+
+ &:not(.legacy) {
+ &.light-theme {
+ @import '@elastic/charts/src/styles/theme_only_light';
+
+ body,
+ .echChart {
+ background: #ffffff !important;
+ }
+
+ .euiText {
+ color: $euiTitleColor;
+ }
+ }
+
+ &.dark-theme {
+ @import '@elastic/charts/src/styles/theme_only_dark';
+
+ body,
+ .echChart {
+ background: #0b1628 !important;
+ }
+
+ .euiText {
+ color: $euiTitleColor;
+ }
+ }
+ }
+
+ &.legacy {
+ &.light-theme {
+ @import '@elastic/charts/src/styles/legacy/theme_only_light';
+
+ body,
+ .echChart {
+ background: white !important;
+ }
+
+ &.amsterdam {
+ @import '@elastic/eui/src/themes/amsterdam/colors_light';
+
+ body,
+ .echChart {
+ background: $euiColorEmptyShade !important;
+ }
+
+ .euiText {
+ color: $euiTitleColor;
+ }
+ }
+ }
+
+ &.dark-theme {
+ @import '@elastic/charts/src/styles/legacy/theme_only_dark';
+
+ body,
+ .echChart {
+ background: black !important;
+ }
+
+ &.amsterdam {
+ @import '@elastic/eui/src/themes/amsterdam/colors_dark';
+
+ body,
+ .echChart {
+ background: $euiColorEmptyShade !important;
+ }
+
+ .euiText {
+ color: $euiTitleColor;
+ }
+ }
+ }
+ }
+
+ &.disable-animations {
+ *,
+ *::after,
+ *::before {
+ transition-delay: 0s !important;
+ transition-duration: 0s !important;
+ animation-delay: -0.0001s !important;
+ animation-duration: 0s !important;
+ animation-play-state: paused !important;
+ caret-color: transparent !important;
+ }
+
+ .echLegend .echLegendListContainer :focus {
+ animation-duration: 0s !important; // remove focus animation but keep border
+ }
+
+ .euiSaturation:focus .euiSaturation__indicator {
+ animation: none !important; // fix color picker dot
+ }
+ }
+
+ &.echVisualTesting {
+ &,
+ body,
+ #storybook-root {
+ background: blanchedalmond !important;
+ }
+
+ .story-header {
+ display: none;
+ }
+
+ #storybook-root {
+ // This is meant to provide space above and below the chart for tooltip placement
+ width: 785px;
+ height: 1000px;
+ display: flex;
+ align-items: center;
+ }
+ }
+}
+
+body {
+ min-height: 100%;
+}
+
+#storybook-root {
+ z-index: 200;
+ position: relative;
+}
+
+#story-root {
+ padding: 20px;
+ // This allows for the resize stories to grow inside the story-root
+ min-width: 100%;
+ min-height: 400px;
+ position: relative;
+ z-index: 500;
+ box-sizing: border-box;
+
+ &.showChartBoundary {
+ background-color: blanchedalmond;
+ }
+
+ &.resizeHeight {
+ // This is to trigger the non-resize stories to fill the story-root
+ height: 0;
+ }
+}
+
+#story-resize-wrapper {
+ resize: both;
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+ max-width: 100%;
+ max-height: 80vh;
+
+ &.e2e-server {
+ // The resize on the bullet-as-metric causes slight shift and consistent flakiness
+ resize: none;
+ }
+}
+
+.story-header {
+ padding: 20px 40px 16px !important;
+}
+
+.sb-show-main {
+ padding: 0 !important;
+}
+
+.echChart {
+ box-sizing: border-box;
+}
+
+.echInvisible {
+ visibility: hidden;
+}
+
+.markdown {
+ padding: 24px !important;
+}
+
+#story-root + div table {
+ border: 1px solid gray;
+}
+
+#story-root + div table th {
+ border: 1px solid gray;
+ padding: 5px;
+}
+
+#story-root + div table td {
+ border: 1px solid gray;
+ padding: 5px;
+}
+
+.Pane.vertical.Pane1 {
+ background: red;
+}
+
+// for using EuiWrappingPopover in stories
+.euiPopover__anchor {
+ width: 100%;
+}
+
+.resizable {
+ resize: both;
+ overflow: auto;
+ width: 500px;
+ height: 600px;
+}
diff --git a/.storybook/themes.ts b/.storybook/themes.ts
new file mode 100644
index 00000000000..a8b1d330248
--- /dev/null
+++ b/.storybook/themes.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { ClassNameStrategyConfiguration } from '@storybook/addon-themes';
+import { ThemeId } from '@ech/sb';
+
+interface ThemeOptions {
+ id: ThemeId;
+ title: string;
+ class: string;
+}
+
+/*
+ * These properties are meant to work with `@storybook/addon-themes` in a way similar to our legacy
+ * custom `storybook-addon-theme-toggle` addon with better UI that afforded by the simple setup.
+ */
+
+export const themeOptions = {
+ light: {
+ id: ThemeId.Light,
+ title: 'Light',
+ class: 'light-theme',
+ },
+ dark: {
+ id: ThemeId.Dark,
+ title: 'Dark',
+ class: 'dark-theme',
+ },
+ legacyAmsterdamLight: {
+ id: ThemeId.LegacyAmsterdamLight,
+ title: 'Legacy Amsterdam Light',
+ class: 'light-theme legacy amsterdam',
+ },
+ legacyAmsterdamDark: {
+ id: ThemeId.LegacyAmsterdamDark,
+ title: 'Legacy Amsterdam Dark',
+ class: 'dark-theme legacy amsterdam',
+ },
+ legacyLight: {
+ id: ThemeId.LegacyLight,
+ title: 'Legacy Light',
+ class: 'light-theme legacy',
+ },
+ legacyDark: {
+ id: ThemeId.LegacyDark,
+ title: 'Legacy Dark',
+ class: 'dark-theme legacy',
+ },
+} satisfies Record;
+
+const themes: ClassNameStrategyConfiguration['themes'] = Object.fromEntries(Object.entries(themeOptions).map(([, theme]) => [theme.title, theme.class]))
+
+const themeLookupMap = new Map(Object.entries(themeOptions).map(([, { title, id }]) => [title, id]))
+
+type ThemeKey = keyof (typeof themeOptions);
+
+export function getThemeOptions(defaultTheme: ThemeKey): ClassNameStrategyConfiguration {
+ return {
+ themes,
+ defaultTheme: themeOptions[defaultTheme].title,
+ }
+}
+
+export function getThemeFromTitle(title: string = ''): ThemeId | undefined {
+ return themeLookupMap.get(title);
+}
diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json
new file mode 100644
index 00000000000..4737fa9b003
--- /dev/null
+++ b/.storybook/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../tsconfig",
+ "include": ["../packages/charts/src/**/*", "./**/*"],
+ "exclude": ["../**/*.test.*"],
+ "compilerOptions": {
+ "target": "es2023", // Node 20 according to https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping#node-20
+ "moduleDetection": "force",
+ "moduleResolution": "bundler",
+ "module": "preserve",
+ "isolatedModules": true,
+ "verbatimModuleSyntax": true,
+ "noImplicitOverride": true,
+ "lib": [
+ "es2023",
+ "dom",
+ "dom.iterable"
+ ],
+ "paths": {
+ "@ech/sb/*": ["./shared/*"],
+ "@ech/sb": ["./shared"]
+ }
+ }
+}
diff --git a/.storybook/types.ts b/.storybook/types.ts
new file mode 100644
index 00000000000..59d77304162
--- /dev/null
+++ b/.storybook/types.ts
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Parameters as SBParameters } from '@storybook/addons';
+import type { ArgTypes, Args, StoryContext as SBStoryContext } from '@storybook/react';
+import type { CSSProperties, ReactElement } from 'react';
+import type { StoryBackgroundParameter, BackgroundGlobals } from 'storybook-addon-background-toggle';
+import type { StoryThemeParameter, ThemeGlobals } from 'storybook-addon-theme-toggle';
+import type { StoryTogglesParameter, TogglesGlobals } from 'storybook-addon-toggles';
+
+interface BackgroundsGlobals {
+ backgrounds: {
+ grid: boolean;
+ value: string;
+ }
+}
+
+/**
+ * Parameter accessible at the story level
+ */
+export type StoryParameters = SBParameters &
+ StoryThemeParameter &
+ StoryBackgroundParameter &
+ StoryTogglesParameter & {
+ /**
+ * Renders markdown content below story
+ */
+ markdown?: string;
+ /**
+ * Used to enable and style resize wrapper under `#story-root`
+ */
+ resize?: boolean | CSSProperties;
+ };
+export type StoryGlobals = ThemeGlobals & BackgroundsGlobals & TogglesGlobals;
+
+export type StoryContext = Omit & {
+ /**
+ * global values used across stories
+ */
+ globals: StoryGlobals;
+ /**
+ * Title of story, only valid when globals.toggles.showChartTitle is true
+ */
+ title?: string;
+ /**
+ * Description of story, only valid when globals.toggles.showChartDescription is true
+ */
+ description?: string;
+};
+
+type ReactReturnType = ReactElement;
+
+/**
+ * Custom duplicate of the storybook `Story` type in order to have better types that storybook does not support.
+ */
+export interface ChartsStory {
+ (args: Args, context: StoryContext): ReactReturnType;
+ /**
+ * Override the display name in the UI
+ */
+ storyName?: string;
+ /**
+ * Dynamic data that are provided (and possibly updated by) Storybook and its addons.
+ * @see [Arg story inputs](https://storybook.js.org/docs/react/api/csf#args-story-inputs)
+ */
+ args?: Partial;
+ /**
+ * ArgTypes encode basic metadata for args, such as `name`, `description`, `defaultValue` for an arg. These get automatically filled in by Storybook Docs.
+ * @see [Control annotations](https://github.com/storybookjs/storybook/blob/91e9dee33faa8eff0b342a366845de7100415367/addons/controls/README.md#control-annotations)
+ */
+ argTypes?: ArgTypes;
+ /**
+ * Custom metadata for a story.
+ * @see [Parameters](https://storybook.js.org/docs/basics/writing-stories/#parameters)
+ */
+ parameters?: StoryParameters;
+ /**
+ * Wrapper components or Storybook decorators that wrap a story.
+ *
+ * Decorators defined in Meta will be applied to every story variation.
+ * @see [Decorators](https://storybook.js.org/docs/addons/introduction/#1-decorators)
+ */
+ decorators?: unknown;
+}
diff --git a/.storybook/vite.config-old.ts b/.storybook/vite.config-old.ts
new file mode 100644
index 00000000000..e899d69cd07
--- /dev/null
+++ b/.storybook/vite.config-old.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import path from 'path';
+
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ resolve: {
+ alias: [
+ {
+ find: /^@elastic\/charts\/(.*)/,
+ replacement: path.resolve(__dirname, '../packages/charts/$1'),
+ },
+ {
+ find: '@elastic/charts',
+ replacement: path.resolve(__dirname, '../packages/charts/src/index.ts'),
+ },
+ {
+ // Need to resolve `@elastic/` sass imports in `@elastic/` packages from eui
+ find: 'node_modules/@elastic',
+ replacement: path.resolve(__dirname, '../node_modules/@elastic'),
+ },
+ ],
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ quietDeps: true, // silence divider deprecation warning messages
+ },
+ },
+ },
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify('development'),
+ },
+});
diff --git a/.storybook/vitest.setup.ts b/.storybook/vitest.setup.ts
new file mode 100644
index 00000000000..44922d55e49
--- /dev/null
+++ b/.storybook/vitest.setup.ts
@@ -0,0 +1,7 @@
+import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
+import { setProjectAnnotations } from '@storybook/react-vite';
+import * as projectAnnotations from './preview';
+
+// This is an important step to apply the right configuration when testing your stories.
+// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
+setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
\ No newline at end of file
diff --git a/e2e_server/server/generate/vrt_page_template.js b/e2e_server/server/generate/vrt_page_template.js
index 0f0d872f82d..bc2e41a26bb 100644
--- a/e2e_server/server/generate/vrt_page_template.js
+++ b/e2e_server/server/generate/vrt_page_template.js
@@ -16,7 +16,7 @@ function indexTemplate() {
return `
import '../../storybook/style.scss';
import React from 'react';
-import ReactDOM from 'react-dom';
+import { createRoot } from 'react-dom/client';
import { VRTPage } from './vrt_page';
import { appendIconComponentCache } from '@elastic/eui/es/components/icon/icon';
@@ -35,7 +35,11 @@ appendIconComponentCache({
const path = new URL(window.location.toString()).searchParams.get('path');
document.getElementsByTagName('body')[0].style.overflow = path ? 'hidden' : 'scroll';
-ReactDOM.render( , document.getElementById('root') as HTMLElement);
+
+const container = document.getElementById('root')!;
+const root = createRoot(container);
+
+root.render( );
`.trim();
}
diff --git a/package.json b/package.json
index acf783e5ab9..33ed9207297 100644
--- a/package.json
+++ b/package.json
@@ -4,10 +4,12 @@
"author": "Elastic DataVis",
"license": "SEE LICENSE IN LICENSE.txt",
"private": "true",
+ "type": "module",
"repository": "git@github.com:elastic/elastic-charts.git",
"workspaces": {
"packages": [
"packages/*",
+ ".storybook",
"storybook"
]
},
@@ -37,6 +39,7 @@
"prettier:check": "prettier --check \"**/*.{json,html,css,scss}\"",
"prettier:fix": "prettier --w \"**/*.{json,html,css,scss}\"",
"playground": "export NODE_OPTIONS=--openssl-legacy-provider; cd playground && RNG_SEED='elastic-charts' webpack serve",
+ "playground2": "export NODE_OPTIONS=--openssl-legacy-provider; cd playground && RNG_SEED='elastic-charts' vite",
"pq": "pretty-quick",
"semantic-release": "semantic-release --debug",
"start": "yarn storybook",
@@ -73,8 +76,8 @@
"@elastic/eui": "^99.2.0",
"@elastic/eui-theme-borealis": "^3.3.0",
"@elastic/eui-theme-common": "^3.1.0",
- "@emotion/react": "^11.10.5",
"@emotion/css": "^11.10.5",
+ "@emotion/react": "^11.10.5",
"@mdx-js/loader": "^1.6.6",
"@microsoft/api-documenter": "^7.25.4",
"@microsoft/api-extractor": "^7.47.0",
@@ -96,28 +99,28 @@
"@types/d3-scale": "^3.3.0",
"@types/d3-shape": "^2.0.0",
"@types/enzyme": "^3.9.0",
- "@types/enzyme-adapter-react-16": "^1.0.5",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.14.121",
"@types/luxon": "^1.25.0",
"@types/marked": "^2.0.1",
"@types/moment-timezone": "^0.5.30",
- "@types/react-dom": "^16.9.8",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"@types/react-redux": "^7.1.20",
"@types/seedrandom": "^2.4.28",
"@types/url-parse": "^1.4.3",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
- "babel-loader": "^8.3.0",
"autoprefixer": "^9.0.0",
+ "babel-loader": "^8.3.0",
"backport": "^5.6.6",
"change-case": "^4.1.2",
"commitizen": "^4.2.3",
"cross-env": "^7.0.2",
"cz-conventional-changelog": "^3.3.0",
"enzyme": "^3.11.0",
- "enzyme-adapter-react-16": "^1.15.5",
+ "@cfaester/enzyme-adapter-react-18": "^0.8.0",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^18.0.0",
diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md
index b1bb671aa2d..c4ba638c837 100644
--- a/packages/charts/api/charts.api.md
+++ b/packages/charts/api/charts.api.md
@@ -16,7 +16,6 @@ import type { Optional } from 'utility-types';
import type { OptionalKeys } from 'utility-types';
import type { PropsWithChildren as PropsWithChildren_2 } from 'react';
import { default as React_2 } from 'react';
-import type { ReactChild } from 'react';
import type { ReactElement } from 'react';
import type { ReactNode } from 'react';
import type { Required as Required_2 } from 'utility-types';
@@ -2848,7 +2847,7 @@ export interface SettingsSpec extends Spec, LegendSpec {
externalPointerEvents: ExternalPointerEventsSettings;
locale: string;
minBrushDelta?: number;
- noResults?: ComponentType | ReactChild;
+ noResults?: ComponentType | ReactNode;
onAnnotationClick?: AnnotationClickListener;
// (undocumented)
onBrushEnd?: BrushEndListener;
diff --git a/packages/charts/package.json b/packages/charts/package.json
index 632cf6f8651..c1540ab0e37 100644
--- a/packages/charts/package.json
+++ b/packages/charts/package.json
@@ -46,7 +46,7 @@
"immer": "^9.0.21",
"prop-types": "^15.7.2",
"re-reselect": "^4.0.1",
- "react-redux": "^7.2.8",
+ "react-redux": "^8.1.3",
"redux": "^4.2.1",
"ts-debounce": "^4.0.0",
"utility-types": "^3.10.0",
@@ -60,8 +60,8 @@
"react-dom": "^16.12 || ^17.0 || ^18.0"
},
"devDependencies": {
- "@types/react": "^16",
- "@types/react-dom": "^16"
+ "@types/react": "^18",
+ "@types/react-dom": "^18"
},
"browserslist": [
"last 2 versions",
diff --git a/packages/charts/src/chart_types/metric/renderer/dom/text.tsx b/packages/charts/src/chart_types/metric/renderer/dom/text.tsx
index 14ca79d48b3..2db91f8ade3 100644
--- a/packages/charts/src/chart_types/metric/renderer/dom/text.tsx
+++ b/packages/charts/src/chart_types/metric/renderer/dom/text.tsx
@@ -213,7 +213,7 @@ export const MetricText: React.FC = ({
width: sizes.valuePartFontSize,
height: sizes.valuePartFontSize,
color: datum.valueColor ?? colors.highContrast,
- verticalAlign: 'middle',
+ verticalAlign: 'middle' as const,
})}
)}
diff --git a/packages/charts/src/chart_types/specs.ts b/packages/charts/src/chart_types/specs.ts
index 17d5cd54d79..3f5a8747ae5 100644
--- a/packages/charts/src/chart_types/specs.ts
+++ b/packages/charts/src/chart_types/specs.ts
@@ -7,21 +7,23 @@
*/
export {
- AreaSeries,
- AreaSeriesProps,
Axis,
- AxisProps,
+ AreaSeries,
BarSeries,
- BarSeriesProps,
BubbleSeries,
- BubbleSeriesProps,
HistogramBarSeries,
- HistogramBarSeriesProps,
LineAnnotation,
- LineAnnotationProps,
LineSeries,
- LineSeriesProps,
RectAnnotation,
+} from './xy_chart/specs';
+export type {
+ AxisProps,
+ AreaSeriesProps,
+ BarSeriesProps,
+ BubbleSeriesProps,
+ HistogramBarSeriesProps,
+ LineAnnotationProps,
+ LineSeriesProps,
RectAnnotationProps,
} from './xy_chart/specs';
@@ -33,10 +35,11 @@ export * from './goal_chart/specs';
export { Partition } from './partition_chart/specs';
-export { Heatmap, HeatmapSpec, RasterTimeScale, TimeScale, LinearScale, OrdinalScale } from './heatmap/specs';
+export { Heatmap } from './heatmap/specs';
+export type { HeatmapSpec, RasterTimeScale, TimeScale, LinearScale, OrdinalScale } from './heatmap/specs';
-export {
- Metric,
+export { Metric } from './metric/specs';
+export type {
MetricSpecProps,
MetricSpec,
MetricBase,
@@ -51,5 +54,6 @@ export {
SecondaryMetricProps,
} from './metric/specs';
-export { Bullet, BulletProps, BulletSpec, BulletDatum, BulletSubtype, BulletValueLabels } from './bullet_graph/spec';
-export { BulletStyle } from './bullet_graph/theme';
+export { Bullet } from './bullet_graph/spec';
+export type { BulletProps, BulletSpec, BulletDatum, BulletSubtype, BulletValueLabels } from './bullet_graph/spec';
+export type { BulletStyle } from './bullet_graph/theme';
diff --git a/packages/charts/src/chart_types/wordcloud/specs/index.ts b/packages/charts/src/chart_types/wordcloud/specs/index.ts
index dc151c4a76d..1cdc8ba7f80 100644
--- a/packages/charts/src/chart_types/wordcloud/specs/index.ts
+++ b/packages/charts/src/chart_types/wordcloud/specs/index.ts
@@ -38,4 +38,4 @@ export const Wordcloud = specComponentFactory()(
/** @public */
export type WordcloudProps = ComponentProps;
-export { WordModel, WeightFn, OutOfRoomCallback } from '../layout/types/viewmodel_types';
+export type { WordModel, WeightFn, OutOfRoomCallback } from '../layout/types/viewmodel_types';
diff --git a/packages/charts/src/components/__snapshots__/chart.test.tsx.snap b/packages/charts/src/components/__snapshots__/chart.test.tsx.snap
index 8223ead44c2..ae668e816b3 100644
--- a/packages/charts/src/components/__snapshots__/chart.test.tsx.snap
+++ b/packages/charts/src/components/__snapshots__/chart.test.tsx.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Chart should render the legend name test 1`] = `
"
@@ -30,7 +30,7 @@ exports[`Chart should render the legend name test 1`] = `
-
+
@@ -40,7 +40,7 @@ exports[`Chart should render the legend name test 1`] = `
-
+
diff --git a/packages/charts/src/components/error_boundary.test.tsx b/packages/charts/src/components/error_boundary.test.tsx
index 7804045b8af..3d6ecc950cc 100644
--- a/packages/charts/src/components/error_boundary.test.tsx
+++ b/packages/charts/src/components/error_boundary.test.tsx
@@ -7,11 +7,15 @@
*/
import { mount } from 'enzyme';
+import type { PropsWithChildren } from 'react';
import React from 'react';
import { Chart } from './chart';
-class SimpleErrorBoundary extends React.Component<{ onError?: (error: Error) => void }, { hasError: boolean }> {
+type Props = PropsWithChildren<{ onError?: (error: Error) => void }>;
+type State = { hasError: boolean };
+
+class SimpleErrorBoundary extends React.Component
{
onError?: (error: Error) => void;
constructor(props: { onError: (error: Error) => void }) {
super(props);
diff --git a/packages/charts/src/components/index.ts b/packages/charts/src/components/index.ts
index 0298911561b..c4bdd186f97 100644
--- a/packages/charts/src/components/index.ts
+++ b/packages/charts/src/components/index.ts
@@ -7,4 +7,6 @@
*/
export * from './chart';
-export { Placement, TooltipPortalSettings } from './portal';
+
+export { Placement } from './portal';
+export type { TooltipPortalSettings } from './portal';
diff --git a/packages/charts/src/components/legend/__snapshots__/legend.test.tsx.snap b/packages/charts/src/components/legend/__snapshots__/legend.test.tsx.snap
index 421ff8ec957..2a94b2a4864 100644
--- a/packages/charts/src/components/legend/__snapshots__/legend.test.tsx.snap
+++ b/packages/charts/src/components/legend/__snapshots__/legend.test.tsx.snap
@@ -5,7 +5,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -15,7 +15,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -27,7 +27,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -37,7 +37,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -49,7 +49,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -59,7 +59,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -71,7 +71,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -81,7 +81,7 @@ exports[`Legend #legendColorPicker should match snapshot after onChange is calle
-
+
@@ -97,7 +97,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -107,7 +107,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -119,7 +119,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -129,7 +129,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -141,7 +141,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -151,7 +151,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -163,7 +163,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -173,7 +173,7 @@ exports[`Legend #legendColorPicker should match snapshot after onClose is called
-
+
@@ -203,7 +203,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -213,7 +213,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -238,7 +238,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -248,7 +248,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -260,7 +260,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -270,7 +270,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -282,7 +282,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
@@ -292,7 +292,7 @@ exports[`Legend #legendColorPicker should render colorPicker when color is click
-
+
diff --git a/packages/charts/src/components/legend/custom_legend.tsx b/packages/charts/src/components/legend/custom_legend.tsx
index c92490d5354..06208729e5d 100644
--- a/packages/charts/src/components/legend/custom_legend.tsx
+++ b/packages/charts/src/components/legend/custom_legend.tsx
@@ -9,8 +9,7 @@
import React from 'react';
import { connect } from 'react-redux';
-import type { CustomLegendProps } from '../../specs';
-import { CustomLegend as CustomLegendComponent } from '../../specs';
+import type { CustomLegendProps, CustomLegend as CustomLegendComponent } from '../../specs';
import type { GlobalChartState } from '../../state/chart_state';
import { getPointerValueSelector } from '../../state/selectors/get_pointer_value';
@@ -18,11 +17,11 @@ interface Props extends CustomLegendProps {
component: CustomLegendComponent;
}
-const CustomLegendComponent: React.FC
= ({ component: Component, ...props }) => ;
+const CustomLegendWrapper: React.FC = ({ component: Component, ...props }) => ;
const mapStateToProps = (state: GlobalChartState) => ({
pointerValue: getPointerValueSelector(state),
});
/** @internal */
-export const CustomLegend = connect(mapStateToProps)(CustomLegendComponent);
+export const CustomLegend = connect(mapStateToProps)(CustomLegendWrapper);
diff --git a/packages/charts/src/components/no_results.tsx b/packages/charts/src/components/no_results.tsx
index b6f5d7b96dd..01ba2b4db8f 100644
--- a/packages/charts/src/components/no_results.tsx
+++ b/packages/charts/src/components/no_results.tsx
@@ -17,7 +17,9 @@ interface NoResultsProps {
/** @internal */
export const NoResults: FC = ({ renderFn }) => (
- null}>
- {renderFn ??
No data to display
}
+
+
+ {typeof renderFn === 'function' ? React.createElement(renderFn) : renderFn ??
No data to display
}
+
);
diff --git a/packages/charts/src/index.ts b/packages/charts/src/index.ts
index d06d72b6d01..750d6b38dd5 100644
--- a/packages/charts/src/index.ts
+++ b/packages/charts/src/index.ts
@@ -7,15 +7,15 @@
*/
export * from './components';
-export { ChartType } from './chart_types';
-export { ChartSize, ChartSizeArray, ChartSizeObject } from './utils/chart_size';
+export type { ChartType } from './chart_types';
+export type { ChartSize, ChartSizeArray, ChartSizeObject } from './utils/chart_size';
-export { SpecId, GroupId, AxisId, AnnotationId } from './utils/ids';
+export type { SpecId, GroupId, AxisId, AnnotationId } from './utils/ids';
// Everything related to the specs types and react-components
export * from './specs';
export * from './specs/spec_type'; // kept as long-winded import on separate line otherwise import circularity emerges
-export {
+export type {
DebugState,
DebugStateLine,
DebugStateValue,
@@ -33,27 +33,27 @@ export {
PointerValue,
} from './state/types';
export { toEntries } from './utils/common';
-export { CurveType } from './utils/curves';
-export { ContinuousDomain, OrdinalDomain, GenericDomain, Range } from './utils/domain';
-export { Dimensions, SimplePadding, Padding, PerSideDistance, Margins } from './utils/dimensions';
+export type { CurveType } from './utils/curves';
+export type { ContinuousDomain, OrdinalDomain, GenericDomain, Range } from './utils/domain';
+export type { Dimensions, SimplePadding, Padding, PerSideDistance, Margins } from './utils/dimensions';
export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/data/formatters';
-export { SeriesCompareFn } from './utils/series_sort';
-export { SeriesIdentifier, SeriesKey } from './common/series_id';
-export { XYChartSeriesIdentifier, DataSeriesDatum, FilledValues } from './chart_types/xy_chart/utils/series';
-export {
+export type { SeriesCompareFn } from './utils/series_sort';
+export type { SeriesIdentifier, SeriesKey } from './common/series_id';
+export type { XYChartSeriesIdentifier, DataSeriesDatum, FilledValues } from './chart_types/xy_chart/utils/series';
+export type {
AnnotationTooltipFormatter,
CustomAnnotationTooltip,
ComponentWithAnnotationDatum,
} from './chart_types/xy_chart/annotations/types';
-export { GeometryValue, BandedAccessorType } from './utils/geometry';
-export { LegendPath, LegendPathElement } from './state/actions/legend';
-export { LegendItemValue, LegendValue } from './common/legend';
-export { CategoryKey, CategoryLabel } from './common/category';
-export { Layer as PartitionLayer, PartitionProps } from './chart_types/partition_chart/specs/index';
-export { FillLabelConfig as PartitionFillLabel, PartitionStyle } from './utils/themes/partition';
-export { PartitionLayout } from './chart_types/partition_chart/layout/types/config_types';
+export type { GeometryValue, BandedAccessorType } from './utils/geometry';
+export type { LegendPath, LegendPathElement } from './state/actions/legend';
+export type { LegendItemValue, LegendValue } from './common/legend';
+export type { CategoryKey, CategoryLabel } from './common/category';
+export type { Layer as PartitionLayer, PartitionProps } from './chart_types/partition_chart/specs/index';
+export type { FillLabelConfig as PartitionFillLabel, PartitionStyle } from './utils/themes/partition';
+export type { PartitionLayout } from './chart_types/partition_chart/layout/types/config_types';
-export {
+export type {
Accessor,
AccessorFn,
IndexedAccessorFn,
@@ -65,10 +65,10 @@ export * from './components/tooltip';
// scales
export { ScaleType } from './scales/constants';
-export { ScaleContinuousType, ScaleOrdinalType, ScaleBandType, LogScaleOptions } from './scales';
+export type { ScaleContinuousType, ScaleOrdinalType, ScaleBandType, LogScaleOptions } from './scales';
// TODO move animation to its own package
-export {
+export type {
AnimationOptions,
AnimatedValue,
AnimationSpeed,
@@ -86,17 +86,17 @@ export { AMSTERDAM_LIGHT_THEME } from './utils/themes/amsterdam_light_theme';
export { AMSTERDAM_DARK_THEME } from './utils/themes/amsterdam_dark_theme';
// wordcloud
-export { WordcloudViewModel } from './chart_types/wordcloud/layout/types/viewmodel_types';
+export type { WordcloudViewModel } from './chart_types/wordcloud/layout/types/viewmodel_types';
// partition
export * from './chart_types/partition_chart/layout/types/viewmodel_types';
export * from './chart_types/partition_chart/layout/utils/group_by_rollup';
-export { AnimKeyframe } from './chart_types/partition_chart/layout/types/config_types';
+export type { AnimKeyframe } from './chart_types/partition_chart/layout/types/config_types';
// heatmap
-export { Cell } from './chart_types/heatmap/layout/types/viewmodel_types';
-export { HeatmapCellDatum } from './chart_types/heatmap/layout/viewmodel/viewmodel';
-export {
+export type { Cell } from './chart_types/heatmap/layout/types/viewmodel_types';
+export type { HeatmapCellDatum } from './chart_types/heatmap/layout/viewmodel/viewmodel';
+export type {
ColorBand,
HeatmapBandsColorScale,
HeatmapProps,
@@ -104,36 +104,32 @@ export {
} from './chart_types/heatmap/specs/heatmap';
// utilities
-export {
+export type {
Datum,
- Position,
Rendering,
Rotation,
- VerticalAlignment,
- HorizontalAlignment,
RecursivePartial,
NonAny,
IsAny,
IsUnknown,
- ColorVariant,
LabelAccessor,
ShowAccessor,
ValueAccessor,
ValueFormatter,
- LayoutDirection,
} from './utils/common';
+export { Position, VerticalAlignment, HorizontalAlignment, ColorVariant, LayoutDirection } from './utils/common';
export { DataGenerator } from './utils/data_generators/data_generator';
export * from './utils/themes/merge_utils';
export * from './utils/use_legend_action';
export { MODEL_KEY, defaultPartitionValueFormatter } from './chart_types/partition_chart/layout/config';
-export { LegendStrategy } from './chart_types/partition_chart/layout/utils/highlighted_geoms';
-export { Pixels, Ratio, TimeMs } from './common/geometry';
-export { AdditiveNumber } from './utils/accessor';
-export { FontStyle, FONT_STYLES } from './common/text_utils';
-export { Color } from './common/colors';
-export { RGB, A, RgbaTuple } from './common/color_library_wrappers';
-export { Predicate } from './common/predicate';
-export { SmallMultiplesDatum } from './common/panel_utils';
+export type { LegendStrategy } from './chart_types/partition_chart/layout/utils/highlighted_geoms';
+export type { Pixels, Ratio, TimeMs } from './common/geometry';
+export type { AdditiveNumber } from './utils/accessor';
+export type { FontStyle, FONT_STYLES } from './common/text_utils';
+export type { Color } from './common/colors';
+export type { RGB, A, RgbaTuple } from './common/color_library_wrappers';
+export type { Predicate } from './common/predicate';
+export type { SmallMultiplesDatum } from './common/panel_utils';
export type {
ESCalendarInterval,
@@ -145,15 +141,15 @@ export type { UnixTimestamp } from './utils/chrono/types';
export { roundDateToESInterval } from './utils/chrono/elasticsearch';
// data utils
-export { GroupKeysOrKeyFn, GroupByKeyFn } from './chart_types/xy_chart/utils/group_data_series';
+export type { GroupKeysOrKeyFn, GroupByKeyFn } from './chart_types/xy_chart/utils/group_data_series';
export { computeRatioByGroups } from './utils/data/data_processing';
-export { TimeFunction } from './utils/time_functions';
+export type { TimeFunction } from './utils/time_functions';
export * from './chart_types/flame_chart/flame_api';
export * from './chart_types/timeslip/timeslip_api';
-export { LegacyAnimationConfig } from './common/animation';
+export type { LegacyAnimationConfig } from './common/animation';
// Bullet
-export {
+export type {
ColorBandValue,
ColorBandConfig,
ColorBandSimpleConfig,
diff --git a/packages/charts/src/scales/index.ts b/packages/charts/src/scales/index.ts
index ba11e9a25e4..7a5646ac51e 100644
--- a/packages/charts/src/scales/index.ts
+++ b/packages/charts/src/scales/index.ts
@@ -28,4 +28,4 @@ export { ScaleBand } from './scale_band';
/** @internal */
export { ScaleContinuous } from './scale_continuous';
-export { LogScaleOptions } from './types';
+export type { LogScaleOptions } from './types';
diff --git a/packages/charts/src/specs/settings.tsx b/packages/charts/src/specs/settings.tsx
index 1ddf4d79eca..ccc59d7a403 100644
--- a/packages/charts/src/specs/settings.tsx
+++ b/packages/charts/src/specs/settings.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import type { ComponentProps, ComponentType, ReactChild } from 'react';
+import type { ComponentProps, ComponentType, ReactNode } from 'react';
import type { CustomXDomain, GroupByAccessor } from '.';
import type { BrushAxis } from './brush_axis';
@@ -633,7 +633,7 @@ export interface SettingsSpec extends Spec, LegendSpec {
/**
* Render component for no results UI
*/
- noResults?: ComponentType | ReactChild;
+ noResults?: ComponentType | ReactNode;
/**
* User can specify the heading level for the label
* @defaultValue 'p'
diff --git a/packages/charts/src/state/chart_state.ts b/packages/charts/src/state/chart_state.ts
index d0a37641509..de06e6c5c9d 100644
--- a/packages/charts/src/state/chart_state.ts
+++ b/packages/charts/src/state/chart_state.ts
@@ -176,6 +176,9 @@ export const createChartStore = (chartId: string, title?: string, description?:
// TODO https://github.com/elastic/elastic-charts/issues/2078
serializableCheck: false,
}),
+ devTools: {
+ name: `@elastic/charts - ${chartId}`,
+ },
});
};
diff --git a/playground/index.html b/playground/index.html
index fedeb21bb38..a3c06ff06a0 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -51,6 +51,6 @@
-
+