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 = ( + + ); + + return ( + { + setPopoverOpen(false); + onClose(); + }} + panelPaddingSize="none" + offset={4} + anchorPosition={anchorPosition} + > + + + ); + }; 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`] = `
    -
    +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +
  • - + - +