diff --git a/packages/grafana-ui/.storybook/config.ts b/packages/grafana-ui/.storybook/config.ts index 9e50c6b501a..434e717bbab 100644 --- a/packages/grafana-ui/.storybook/config.ts +++ b/packages/grafana-ui/.storybook/config.ts @@ -1,10 +1,15 @@ -import { configure } from '@storybook/react'; +import { configure, addDecorator } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs'; +import { withTheme } from '../src/utils/storybook/withTheme'; import '../../../public/sass/grafana.light.scss'; // automatically import all files ending in *.stories.tsx const req = require.context('../src/components', true, /.story.tsx$/); +addDecorator(withKnobs); +addDecorator(withTheme); + function loadStories() { req.keys().forEach(req); } diff --git a/packages/grafana-ui/.storybook/webpack.config.js b/packages/grafana-ui/.storybook/webpack.config.js index 44de73a1e18..307a1142a7d 100644 --- a/packages/grafana-ui/.storybook/webpack.config.js +++ b/packages/grafana-ui/.storybook/webpack.config.js @@ -1,7 +1,6 @@ const path = require('path'); module.exports = (baseConfig, env, config) => { - config.module.rules.push({ test: /\.(ts|tsx)$/, use: [ @@ -33,7 +32,12 @@ module.exports = (baseConfig, env, config) => { config: { path: __dirname + '../../../../scripts/webpack/postcss.config.js' }, }, }, - { loader: 'sass-loader', options: { sourceMap: false } }, + { + loader: 'sass-loader', + options: { + sourceMap: false + }, + }, ], }); @@ -52,5 +56,9 @@ module.exports = (baseConfig, env, config) => { }); config.resolve.extensions.push('.ts', '.tsx'); + + // Remove pure js loading rules as Storybook's Babel config is causing problems when mixing ES6 and CJS + // More about the problem we encounter: https://github.com/webpack/webpack/issues/4039 + config.module.rules = config.module.rules.filter(rule => rule.test.toString() !== /\.(mjs|jsx?)$/.toString()); return config; }; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx index 19ae2fda978..1fb31e86d72 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx @@ -1,46 +1,43 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { withKnobs, boolean } from '@storybook/addon-knobs'; +import { boolean } from '@storybook/addon-knobs'; import { SeriesColorPicker, ColorPicker } from './ColorPicker'; import { action } from '@storybook/addon-actions'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { UseState } from '../../utils/storybook/UseState'; -import { getThemeKnob } from '../../utils/storybook/themeKnob'; +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; const getColorPickerKnobs = () => { return { - selectedTheme: getThemeKnob(), enableNamedColors: boolean('Enable named colors', false), }; }; const ColorPickerStories = storiesOf('UI/ColorPicker/Pickers', module); -ColorPickerStories.addDecorator(withCenteredStory).addDecorator(withKnobs); +ColorPickerStories.addDecorator(withCenteredStory); ColorPickerStories.add('default', () => { - const { selectedTheme, enableNamedColors } = getColorPickerKnobs(); + const { enableNamedColors } = getColorPickerKnobs(); + return ( {(selectedColor, updateSelectedColor) => { - return ( - { - action('Color changed')(color); - updateSelectedColor(color); - }} - theme={selectedTheme || undefined} - /> - ); + return renderComponentWithTheme(ColorPicker, { + enableNamedColors, + color: selectedColor, + onChange: (color: any) => { + action('Color changed')(color); + updateSelectedColor(color); + }, + }); }} ); }); ColorPickerStories.add('Series color picker', () => { - const { selectedTheme, enableNamedColors } = getColorPickerKnobs(); + const { enableNamedColors } = getColorPickerKnobs(); return ( @@ -52,7 +49,6 @@ ColorPickerStories.add('Series color picker', () => { onToggleAxis={() => {}} color={selectedColor} onChange={color => updateSelectedColor(color)} - theme={selectedTheme || undefined} >
Open color picker
diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index b6cf176a24b..a48ecc44c45 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,12 +1,12 @@ import React, { Component, createRef } from 'react'; import PopperController from '../Tooltip/PopperController'; -import Popper, { RenderPopperArrowFn } from '../Tooltip/Popper'; +import Popper from '../Tooltip/Popper'; import { ColorPickerPopover } from './ColorPickerPopover'; -import { Themeable, GrafanaTheme } from '../../types'; +import { Themeable } from '../../types'; import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; import propDeprecationWarning from '../../utils/propDeprecationWarning'; - +import { withTheme } from '../../themes/ThemeContext'; type ColorPickerChangeHandler = (color: string) => void; export interface ColorPickerProps extends Themeable { @@ -18,7 +18,6 @@ export interface ColorPickerProps extends Themeable { */ onColorChange?: ColorPickerChangeHandler; enableNamedColors?: boolean; - withArrow?: boolean; children?: JSX.Element; } @@ -32,7 +31,6 @@ export const warnAboutColorPickerPropsDeprecation = (componentName: string, prop export const colorPickerFactory = ( popover: React.ComponentType, displayName = 'ColorPicker', - renderPopoverArrowFunction?: RenderPopperArrowFn ) => { return class ColorPicker extends Component { static displayName = displayName; @@ -50,17 +48,7 @@ export const colorPickerFactory = ( ...this.props, onChange: this.handleColorChange, }); - const { theme, withArrow, children } = this.props; - - const renderArrow: RenderPopperArrowFn = ({ arrowProps, placement }) => { - return ( -
- ); - }; + const { theme, children } = this.props; return ( @@ -72,7 +60,6 @@ export const colorPickerFactory = ( {...popperProps} referenceElement={this.pickerTriggerRef.current} wrapperClassName="ColorPicker" - renderArrow={withArrow && (renderPopoverArrowFunction || renderArrow)} onMouseLeave={hidePopper} onMouseEnter={showPopper} /> @@ -95,7 +82,7 @@ export const colorPickerFactory = (
@@ -110,5 +97,5 @@ export const colorPickerFactory = ( }; }; -export const ColorPicker = colorPickerFactory(ColorPickerPopover, 'ColorPicker'); -export const SeriesColorPicker = colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker'); +export const ColorPicker = withTheme(colorPickerFactory(ColorPickerPopover, 'ColorPicker')); +export const SeriesColorPicker = withTheme(colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker')); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx index dc51819a413..d749588ee31 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx @@ -1,40 +1,27 @@ -import React from 'react'; import { storiesOf } from '@storybook/react'; import { ColorPickerPopover } from './ColorPickerPopover'; -import { withKnobs } from '@storybook/addon-knobs'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { getThemeKnob } from '../../utils/storybook/themeKnob'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; - +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; const ColorPickerPopoverStories = storiesOf('UI/ColorPicker/Popovers', module); -ColorPickerPopoverStories.addDecorator(withCenteredStory).addDecorator(withKnobs); +ColorPickerPopoverStories.addDecorator(withCenteredStory); ColorPickerPopoverStories.add('default', () => { - const selectedTheme = getThemeKnob(); - - return ( - { - console.log(color); - }} - theme={selectedTheme || undefined} - /> - ); + return renderComponentWithTheme(ColorPickerPopover, { + color: '#BC67E6', + onChange: (color: any) => { + console.log(color); + }, + }); }); ColorPickerPopoverStories.add('SeriesColorPickerPopover', () => { - const selectedTheme = getThemeKnob(); - - return ( - { - console.log(color); - }} - theme={selectedTheme || undefined} - /> - ); + return renderComponentWithTheme(SeriesColorPickerPopover, { + color: '#BC67E6', + onChange: (color: any) => { + console.log(color); + }, + }); }); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx index 28d66e7af86..444f0e658c8 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx @@ -4,7 +4,8 @@ import { ColorPickerPopover } from './ColorPickerPopover'; import { getColorDefinitionByName, getNamedColorPalette } from '../../utils/namedColorsPalette'; import { ColorSwatch } from './NamedColorsGroup'; import { flatten } from 'lodash'; -import { GrafanaTheme } from '../../types'; +import { GrafanaThemeType } from '../../types'; +import { getTheme } from '../../themes'; const allColors = flatten(Array.from(getNamedColorPalette().values())); @@ -14,7 +15,7 @@ describe('ColorPickerPopover', () => { describe('rendering', () => { it('should render provided color as selected if color provided by name', () => { - const wrapper = mount( {}} />); + const wrapper = mount( {}} theme={getTheme()}/>); const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false); @@ -24,7 +25,7 @@ describe('ColorPickerPopover', () => { }); it('should render provided color as selected if color provided by hex', () => { - const wrapper = mount( {}} />); + const wrapper = mount( {}} theme={getTheme()} />); const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false); @@ -45,7 +46,7 @@ describe('ColorPickerPopover', () => { it('should pass hex color value to onChange prop by default', () => { wrapper = mount( - + ); const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name); @@ -61,7 +62,7 @@ describe('ColorPickerPopover', () => { enableNamedColors color={BasicGreen.variants.dark} onChange={onChangeSpy} - theme={GrafanaTheme.Light} + theme={getTheme(GrafanaThemeType.Light)} /> ); const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index d2937a1caba..b4c77a5e373 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { NamedColorsPalette } from './NamedColorsPalette'; import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { ColorPickerProps, warnAboutColorPickerPropsDeprecation } from './ColorPicker'; -import { GrafanaTheme } from '../../types'; import { PopperContentProps } from '../Tooltip/PopperController'; import SpectrumPalette from './SpectrumPalette'; +import { GrafanaThemeType } from '@grafana/ui'; export interface Props extends ColorPickerProps, PopperContentProps { customPickers?: T; @@ -43,7 +43,7 @@ export class ColorPickerPopover extends React if (enableNamedColors) { return changeHandler(color); } - changeHandler(getColorFromHexRgbOrName(color, theme)); + changeHandler(getColorFromHexRgbOrName(color, theme.type)); }; handleTabChange = (tab: PickerType | keyof T) => { @@ -58,7 +58,9 @@ export class ColorPickerPopover extends React case 'spectrum': return ; case 'palette': - return ; + return ( + + ); default: return this.renderCustomPicker(activePicker); } @@ -88,11 +90,7 @@ export class ColorPickerPopover extends React <> {Object.keys(customPickers).map(key => { return ( -
+
{customPickers[key].name}
); @@ -103,21 +101,14 @@ export class ColorPickerPopover extends React render() { const { theme } = this.props; - const colorPickerTheme = theme || GrafanaTheme.Dark; - + const colorPickerTheme = theme.type || GrafanaThemeType.Dark; return (
-
+
Colors
-
+
Custom
{this.renderCustomPickerTabs()} @@ -128,3 +119,4 @@ export class ColorPickerPopover extends React ); } } + diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx index 91c4f21642a..2b5f7bb9b57 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx @@ -1,8 +1,9 @@ import React, { FunctionComponent } from 'react'; -import { Themeable, GrafanaTheme } from '../../types'; +import { Themeable } from '../../types'; import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette'; import { Color } from 'csstype'; import { find, upperFirst } from 'lodash'; +import { selectThemeVariant } from '../../themes/selectThemeVariant'; type ColorChangeHandler = (color: ColorDefinition) => void; @@ -28,7 +29,15 @@ export const ColorSwatch: FunctionComponent = ({ }) => { const isSmall = variant === ColorSwatchVariant.Small; const swatchSize = isSmall ? '16px' : '32px'; - const selectedSwatchBorder = theme === GrafanaTheme.Light ? '#ffffff' : '#1A1B1F'; + + const selectedSwatchBorder = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.black, + }, + theme.type + ); + const swatchStyles = { width: swatchSize, height: swatchSize, @@ -76,7 +85,7 @@ const NamedColorsGroup: FunctionComponent = ({ key={primaryColor.name} isSelected={primaryColor.name === selectedColor} variant={ColorSwatchVariant.Large} - color={getColorForTheme(primaryColor, theme)} + color={getColorForTheme(primaryColor, theme.type)} label={upperFirst(primaryColor.hue)} onClick={() => onColorSelect(primaryColor)} theme={theme} @@ -95,7 +104,7 @@ const NamedColorsGroup: FunctionComponent = ({ onColorSelect(color)} theme={theme} /> diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx index af5de3b2a2d..f4901b28bfd 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { NamedColorsPalette } from './NamedColorsPalette'; import { getColorName, getColorDefinitionByName } from '../../utils/namedColorsPalette'; -import { withKnobs, select } from '@storybook/addon-knobs'; +import { select } from '@storybook/addon-knobs'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; import { UseState } from '../../utils/storybook/UseState'; const BasicGreen = getColorDefinitionByName('green'); @@ -12,7 +13,7 @@ const LightBlue = getColorDefinitionByName('light-blue'); const NamedColorsPaletteStories = storiesOf('UI/ColorPicker/Palettes/NamedColorsPalette', module); -NamedColorsPaletteStories.addDecorator(withKnobs).addDecorator(withCenteredStory); +NamedColorsPaletteStories.addDecorator(withCenteredStory); NamedColorsPaletteStories.add('Named colors swatch - support for named colors', () => { const selectedColor = select( @@ -28,7 +29,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors', return ( {(selectedColor, updateSelectedColor) => { - return ; + return renderComponentWithTheme(NamedColorsPalette, { + color: selectedColor, + onChange: updateSelectedColor, + }); }} ); @@ -45,7 +49,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors', return ( {(selectedColor, updateSelectedColor) => { - return ; + return renderComponentWithTheme(NamedColorsPalette, { + color: getColorName(selectedColor), + onChange: updateSelectedColor, + }); }} ); diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx index 171d26f5c56..7a1ba95e81d 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx @@ -3,7 +3,8 @@ import { mount, ReactWrapper } from 'enzyme'; import { NamedColorsPalette } from './NamedColorsPalette'; import { ColorSwatch } from './NamedColorsGroup'; import { getColorDefinitionByName } from '../../utils'; -import { GrafanaTheme } from '../../types'; +import { getTheme } from '../../themes'; +import { GrafanaThemeType } from '../../types'; describe('NamedColorsPalette', () => { @@ -17,18 +18,18 @@ describe('NamedColorsPalette', () => { }); it('should render provided color variant specific for theme', () => { - wrapper = mount( {}} />); + wrapper = mount( {}} />); selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark); wrapper.unmount(); - wrapper = mount( {}} />); + wrapper = mount( {}} />); selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.light); }); it('should render dar variant of provided color when theme not provided', () => { - wrapper = mount( {}} />); + wrapper = mount( {}} theme={getTheme()}/>); selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark); }); diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 3fa7a1f4a45..4cb8c15c002 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -4,6 +4,7 @@ import { ColorPickerPopover } from './ColorPickerPopover'; import { ColorPickerProps } from './ColorPicker'; import { PopperContentProps } from '../Tooltip/PopperController'; import { Switch } from '../Switch/Switch'; +import { withTheme } from '../../themes/ThemeContext'; export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperContentProps { yaxis?: number; @@ -12,7 +13,6 @@ export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperC export const SeriesColorPickerPopover: FunctionComponent = props => { const { yaxis, onToggleAxis, color, ...colorPickerProps } = props; - return ( { - const selectedTheme = getThemeKnob(); - return ( {(selectedColor, updateSelectedColor) => { - return ; + return renderComponentWithTheme(SpectrumPalette, { color: selectedColor, onChange: updateSelectedColor }); }} ); diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx index cf001cf5629..0661821f3da 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx @@ -13,7 +13,7 @@ export interface SpectrumPaletteProps extends Themeable { onChange: (color: string) => void; } -const renderPointer = (theme?: GrafanaTheme) => (props: SpectrumPalettePointerProps) => ( +const renderPointer = (theme: GrafanaTheme) => (props: SpectrumPalettePointerProps) => ( ); @@ -92,7 +92,7 @@ const SpectrumPalette: React.FunctionComponent = ({ color, }} theme={theme} /> - +
); }; diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx index d0b2cbc4bff..7e3b2cf06a3 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import { GrafanaTheme, Themeable } from '../../types'; +import { Themeable } from '../../types'; +import { selectThemeVariant } from '../../themes/selectThemeVariant'; export interface SpectrumPalettePointerProps extends Themeable { direction?: string; } -const SpectrumPalettePointer: React.FunctionComponent = ({ - theme, - direction, -}) => { +const SpectrumPalettePointer: React.FunctionComponent = ({ theme, direction }) => { const styles = { picker: { width: '16px', @@ -17,7 +15,14 @@ const SpectrumPalettePointer: React.FunctionComponent ({ plot: jest.fn(), @@ -24,6 +25,7 @@ const setup = (propOverrides?: object) => { width: 300, value: 25, decimals: 0, + theme: getTheme() }; Object.assign(props, propOverrides); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 04d89bf3f57..a7435a56b3c 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -1,13 +1,14 @@ import React, { PureComponent } from 'react'; import $ from 'jquery'; -import { ValueMapping, Threshold, BasicGaugeColor, GrafanaTheme } from '../../types'; +import { ValueMapping, Threshold, BasicGaugeColor, GrafanaThemeType } from '../../types'; import { getMappedValue } from '../../utils/valueMappings'; import { getColorFromHexRgbOrName, getValueFormat } from '../../utils'; +import { Themeable } from '../../index'; type TimeSeriesValue = string | number | null; -export interface Props { +export interface Props extends Themeable { decimals: number; height: number; valueMappings: ValueMapping[]; @@ -22,7 +23,6 @@ export interface Props { unit: string; width: number; value: number; - theme?: GrafanaTheme; } const FONT_SCALE = 1; @@ -41,7 +41,7 @@ export class Gauge extends PureComponent { thresholds: [], unit: 'none', stat: 'avg', - theme: GrafanaTheme.Dark, + theme: GrafanaThemeType.Dark, }; componentDidMount() { @@ -77,19 +77,19 @@ export class Gauge extends PureComponent { const { thresholds, theme } = this.props; if (thresholds.length === 1) { - return getColorFromHexRgbOrName(thresholds[0].color, theme); + return getColorFromHexRgbOrName(thresholds[0].color, theme.type); } const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0]; if (atThreshold) { - return getColorFromHexRgbOrName(atThreshold.color, theme); + return getColorFromHexRgbOrName(atThreshold.color, theme.type); } const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value); if (belowThreshold.length > 0) { const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0]; - return getColorFromHexRgbOrName(nearestThreshold.color, theme); + return getColorFromHexRgbOrName(nearestThreshold.color, theme.type); } return BasicGaugeColor.Red; @@ -104,13 +104,13 @@ export class Gauge extends PureComponent { return [ ...thresholdsSortedByIndex.map(threshold => { if (threshold.index === 0) { - return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme) }; + return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme.type) }; } const previousThreshold = thresholdsSortedByIndex[threshold.index - 1]; - return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme) }; + return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme.type) }; }), - { value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme) }, + { value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme.type) }, ]; } @@ -126,7 +126,8 @@ export class Gauge extends PureComponent { const formattedValue = this.formatValue(value) as string; const dimension = Math.min(width, height * 1.3); - const backgroundColor = theme === GrafanaTheme.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; + const backgroundColor = theme.type === GrafanaThemeType.Light ? 'rgb(230,230,230)' : theme.colors.dark3; + const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1; const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio; const thresholdMarkersWidth = gaugeWidth / 5; diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index c15f66cca54..b2a2e07c58d 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -1,11 +1,11 @@ import React, { PureComponent } from 'react'; -import { Threshold, Themeable } from '../../types'; +import { Threshold } from '../../types'; import { ColorPicker } from '../ColorPicker/ColorPicker'; import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; import { colors } from '../../utils'; -import { getColorFromHexRgbOrName } from '@grafana/ui'; +import { getColorFromHexRgbOrName, ThemeContext } from '@grafana/ui'; -export interface Props extends Themeable { +export interface Props { thresholds: Threshold[]; onChange: (thresholds: Threshold[]) => void; } @@ -164,7 +164,10 @@ export class ThresholdsEditor extends PureComponent {
{threshold.color && (
- this.onChangeThresholdColor(threshold, color)} /> + this.onChangeThresholdColor(threshold, color)} + />
)}
@@ -188,27 +191,35 @@ export class ThresholdsEditor extends PureComponent { render() { const { thresholds } = this.state; - const { theme } = this.props; return ( - -
- {thresholds.map((threshold, index) => { - return ( -
-
this.onAddThreshold(threshold.index + 1)}> - -
-
-
{this.renderInput(threshold)}
+ + {theme => { + return ( + +
+ {thresholds.map((threshold, index) => { + return ( +
+
this.onAddThreshold(threshold.index + 1)} + > + +
+
+
{this.renderInput(threshold)}
+
+ ); + })}
- ); - })} -
-
+ + ); + }} +
); } } diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 5cd677761b0..dc435a8844d 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -14,8 +14,8 @@ export { FormLabel } from './FormLabel/FormLabel'; export { FormField } from './FormField/FormField'; export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder'; -export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; -export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover'; +export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; +export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor'; export { Graph } from './Graph/Graph'; export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts index 974d976bbef..216f2f13bad 100644 --- a/packages/grafana-ui/src/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -1,3 +1,5 @@ export * from './components'; export * from './types'; export * from './utils'; +export * from './themes'; +export * from './themes/ThemeContext'; diff --git a/packages/grafana-ui/src/themes/ThemeContext.tsx b/packages/grafana-ui/src/themes/ThemeContext.tsx new file mode 100644 index 00000000000..a61a71d8af6 --- /dev/null +++ b/packages/grafana-ui/src/themes/ThemeContext.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { GrafanaThemeType, Themeable } from '../types'; +import { getTheme } from './index'; + +type Omit = Pick>; +type Subtract = Omit; + +// Use Grafana Dark theme by default +export const ThemeContext = React.createContext(getTheme(GrafanaThemeType.Dark)); + +export const withTheme =

(Component: React.ComponentType

) => { + const WithTheme: React.FunctionComponent> = props => { + // @ts-ignore + return {theme => }; + }; + + WithTheme.displayName = `WithTheme(${Component.displayName})`; + + return WithTheme; +}; diff --git a/packages/grafana-ui/src/themes/dark.ts b/packages/grafana-ui/src/themes/dark.ts new file mode 100644 index 00000000000..deae022f63a --- /dev/null +++ b/packages/grafana-ui/src/themes/dark.ts @@ -0,0 +1,69 @@ +import tinycolor from 'tinycolor2'; +import defaultTheme from './default'; +import { GrafanaTheme, GrafanaThemeType } from '../types/theme'; + +const basicColors = { + black: '#000000', + white: '#ffffff', + dark1: '#141414', + dark2: '#1f1f20', + dark3: '#262628', + dark4: '#333333', + dark5: '#444444', + gray1: '#555555', + gray2: '#8e8e8e', + gray3: '#b3b3b3', + gray4: '#d8d9da', + gray5: '#ececec', + gray6: '#f4f5f8', + gray7: '#fbfbfb', + grayBlue: '#212327', + blue: '#33b5e5', + blueDark: '#005f81', + blueLight: '#00a8e6', // not used in dark theme + green: '#299c46', + red: '#d44a3a', + yellow: '#ecbb13', + pink: '#ff4444', + purple: '#9933cc', + variable: '#32d1df', + orange: '#eb7b18', +}; + +const darkTheme: GrafanaTheme = { + ...defaultTheme, + type: GrafanaThemeType.Dark, + name: 'Grafana Dark', + colors: { + ...basicColors, + inputBlack: '#09090b', + queryRed: '#e24d42', + queryGreen: '#74e680', + queryPurple: '#fe85fc', + queryKeyword: '#66d9ef', + queryOrange: 'eb7b18', + online: '#10a345', + warn: '#f79520', + critical: '#ed2e18', + bodyBg: '#171819', + pageBg: '#161719', + bodyColor: basicColors.gray4, + textColor: basicColors.gray4, + textColorStrong: basicColors.white, + textColorWeak: basicColors.gray2, + textColorEmphasis: basicColors.gray5, + textColorFaint: basicColors.dark5, + linkColor: new tinycolor(basicColors.white).darken(11).toString(), + linkColorDisabled: new tinycolor(basicColors.white).darken(11).toString(), + linkColorHover: basicColors.white, + linkColorExternal: basicColors.blue, + headingColor: new tinycolor(basicColors.white).darken(11).toString(), + }, + background: { + dropdown: basicColors.dark3, + scrollbar: '#aeb5df', + scrollbar2: '#3a3a3a', + }, +}; + +export default darkTheme; diff --git a/packages/grafana-ui/src/themes/default.ts b/packages/grafana-ui/src/themes/default.ts new file mode 100644 index 00000000000..bf318f526e7 --- /dev/null +++ b/packages/grafana-ui/src/themes/default.ts @@ -0,0 +1,62 @@ + + +const theme = { + name: 'Grafana Default', + typography: { + fontFamily: { + sansSerif: "'Roboto', Helvetica, Arial, sans-serif;", + serif: "Georgia, 'Times New Roman', Times, serif;", + monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace;" + }, + size: { + base: '13px', + xs: '10px', + s: '12px', + m: '14px', + l: '18px', + }, + heading: { + h1: '2rem', + h2: '1.75rem', + h3: '1.5rem', + h4: '1.3rem', + h5: '1.2rem', + h6: '1rem', + }, + weight: { + light: 300, + normal: 400, + semibold: 500, + }, + lineHeight: { + xs: 1, + s: 1.1, + m: 4/3, + l: 1.5 + } + }, + brakpoints: { + xs: '0', + s: '544px', + m: '768px', + l: '992px', + xl: '1200px' + }, + spacing: { + xs: '0', + s: '0.2rem', + m: '1rem', + l: '1.5rem', + xl: '3rem', + gutter: '30px', + }, + border: { + radius: { + xs: '2px', + s: '3px', + m: '5px', + } + } +}; + +export default theme; diff --git a/packages/grafana-ui/src/themes/index.ts b/packages/grafana-ui/src/themes/index.ts new file mode 100644 index 00000000000..c0d9a4f2d32 --- /dev/null +++ b/packages/grafana-ui/src/themes/index.ts @@ -0,0 +1,14 @@ +import darkTheme from './dark'; +import lightTheme from './light'; +import { GrafanaTheme } from '../types/theme'; + +let themeMock: ((name?: string) => GrafanaTheme) | null; + +export let getTheme = (name?: string) => (themeMock && themeMock(name)) || (name === 'light' ? lightTheme : darkTheme); + +export const mockTheme = (mock: (name: string) => GrafanaTheme) => { + themeMock = mock; + return () => { + themeMock = null; + }; +}; diff --git a/packages/grafana-ui/src/themes/light.ts b/packages/grafana-ui/src/themes/light.ts new file mode 100644 index 00000000000..fd1f1d05b95 --- /dev/null +++ b/packages/grafana-ui/src/themes/light.ts @@ -0,0 +1,70 @@ +import tinycolor from 'tinycolor2'; +import defaultTheme from './default'; +import { GrafanaTheme, GrafanaThemeType } from '../types/theme'; + +const basicColors = { + black: '#000000', + white: '#ffffff', + dark1: '#13161d', + dark2: '#1e2028', + dark3: '#303133', + dark4: '#35373f', + dark5: '#41444b', + gray1: '#52545c', + gray2: '#767980', + gray3: '#acb6bf', + gray4: '#c7d0d9', + gray5: '#dde4ed', + gray6: '#e9edf2', + gray7: '#f7f8fa', + grayBlue: '#212327', // not used in light theme + blue: '#0083b3', + blueDark: '#005f81', + blueLight: '#00a8e6', + green: '#3aa655', + red: '#d44939', + yellow: '#ff851b', + pink: '#e671b8', + purple: '#9954bb', + variable: '#0083b3', + orange: '#ff7941', +}; + +const lightTheme: GrafanaTheme = { + ...defaultTheme, + type: GrafanaThemeType.Light, + name: 'Grafana Light', + colors: { + ...basicColors, + variable: basicColors.blue, + inputBlack: '#09090b', + queryRed: basicColors.red, + queryGreen: basicColors.green, + queryPurple: basicColors.purple, + queryKeyword: basicColors.blue, + queryOrange: basicColors.orange, + online: '#01a64f', + warn: '#f79520', + critical: '#ec2128', + bodyBg: basicColors.gray7, + pageBg: basicColors.gray7, + bodyColor: basicColors.gray1, + textColor: basicColors.gray1, + textColorStrong: basicColors.dark2, + textColorWeak: basicColors.gray2, + textColorEmphasis: basicColors.gray5, + textColorFaint: basicColors.dark4, + linkColor: basicColors.gray1, + linkColorDisabled: new tinycolor(basicColors.gray1).lighten(30).toString(), + linkColorHover: new tinycolor(basicColors.gray1).darken(20).toString(), + linkColorExternal: basicColors.blueLight, + headingColor: basicColors.gray1, + }, + background: { + dropdown: basicColors.white, + scrollbar: basicColors.gray5, + scrollbar2: basicColors.gray5, + }, +}; + +export default lightTheme; diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts new file mode 100644 index 00000000000..66cb02a2372 --- /dev/null +++ b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts @@ -0,0 +1,52 @@ +import { GrafanaThemeType } from '../types/theme'; +import { selectThemeVariant } from './selectThemeVariant'; +import { mockTheme } from './index'; + +const lightThemeMock = { + color: { + red: '#ff0000', + green: '#00ff00', + }, +}; + +const darkThemeMock = { + color: { + red: '#ff0000', + green: '#00ff00', + }, +}; + +describe('Theme variable variant selector', () => { + // @ts-ignore + const restoreTheme = mockTheme(name => (name === GrafanaThemeType.Light ? lightThemeMock : darkThemeMock)); + + afterAll(() => { + restoreTheme(); + }); + it('return correct variable value for given theme', () => { + const theme = lightThemeMock; + + const selectedValue = selectThemeVariant( + { + dark: theme.color.red, + light: theme.color.green, + }, + GrafanaThemeType.Light + ); + + expect(selectedValue).toBe(lightThemeMock.color.green); + }); + + it('return dark theme variant if no theme given', () => { + const theme = lightThemeMock; + + const selectedValue = selectThemeVariant( + { + dark: theme.color.red, + light: theme.color.green, + } + ); + + expect(selectedValue).toBe(lightThemeMock.color.red); + }); +}); diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.ts b/packages/grafana-ui/src/themes/selectThemeVariant.ts new file mode 100644 index 00000000000..e7e8e780222 --- /dev/null +++ b/packages/grafana-ui/src/themes/selectThemeVariant.ts @@ -0,0 +1,9 @@ +import { GrafanaThemeType } from '../types/theme'; + +type VariantDescriptor = { + [key in GrafanaThemeType]: string | number; +}; + +export const selectThemeVariant = (variants: VariantDescriptor, currentTheme?: GrafanaThemeType) => { + return variants[currentTheme || GrafanaThemeType.Dark]; +}; diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts index e23b5e63af8..81bdf741f30 100644 --- a/packages/grafana-ui/src/types/index.ts +++ b/packages/grafana-ui/src/types/index.ts @@ -1,14 +1,7 @@ + export * from './data'; export * from './time'; export * from './panel'; export * from './plugin'; export * from './datasource'; - -export enum GrafanaTheme { - Light = 'light', - Dark = 'dark', -} - -export interface Themeable { - theme?: GrafanaTheme; -} +export * from './theme'; diff --git a/packages/grafana-ui/src/types/theme.ts b/packages/grafana-ui/src/types/theme.ts new file mode 100644 index 00000000000..8a79658b423 --- /dev/null +++ b/packages/grafana-ui/src/types/theme.ts @@ -0,0 +1,129 @@ +export enum GrafanaThemeType { + Light = 'light', + Dark = 'dark', +} + +export interface GrafanaTheme { + type: GrafanaThemeType; + name: string; + // TODO: not sure if should be a part of theme + brakpoints: { + xs: string; + s: string; + m: string; + l: string; + xl: string; + }; + typography: { + fontFamily: { + sansSerif: string; + serif: string; + monospace: string; + }; + size: { + base: string; + xs: string; + s: string; + m: string; + l: string; + }; + weight: { + light: number; + normal: number; + semibold: number; + }; + lineHeight: { + xs: number; //1 + s: number; //1.1 + m: number; // 4/3 + l: number; // 1.5 + }; + // TODO: Refactor to use size instead of custom defs + heading: { + h1: string; + h2: string; + h3: string; + h4: string; + h5: string; + h6: string; + }; + }; + spacing: { + xs: string; + s: string; + m: string; + l: string; + gutter: string; + }; + border: { + radius: { + xs: string; + s: string; + m: string; + }; + }; + background: { + dropdown: string; + scrollbar: string; + scrollbar2: string; + }; + colors: { + black: string; + white: string; + dark1: string; + dark2: string; + dark3: string; + dark4: string; + dark5: string; + gray1: string; + gray2: string; + gray3: string; + gray4: string; + gray5: string; + gray6: string; + gray7: string; + grayBlue: string; + inputBlack: string; + + // Accent colors + blue: string; + blueLight: string; + blueDark: string; + green: string; + red: string; + yellow: string; + pink: string; + purple: string; + variable: string; + orange: string; + queryRed: string; + queryGreen: string; + queryPurple: string; + queryKeyword: string; + queryOrange: string; + + // Status colors + online: string; + warn: string; + critical: string; + + // TODO: move to background section + bodyBg: string; + pageBg: string; + bodyColor: string; + textColor: string; + textColorStrong: string; + textColorWeak: string; + textColorFaint: string; + textColorEmphasis: string; + linkColor: string; + linkColorDisabled: string; + linkColorHover: string; + linkColorExternal: string; + headingColor: string; + }; +} + +export interface Themeable { + theme: GrafanaTheme; +} diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index c6a1aaf0dd0..f875b9966f1 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -5,20 +5,20 @@ import { getColorFromHexRgbOrName, getColorDefinitionByName, } from './namedColorsPalette'; -import { GrafanaTheme } from '../types/index'; +import { GrafanaThemeType } from '../types/index'; describe('colors', () => { const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue'); describe('getColorDefinition', () => { it('returns undefined for unknown hex', () => { - expect(getColorDefinition('#ff0000', GrafanaTheme.Light)).toBeUndefined(); - expect(getColorDefinition('#ff0000', GrafanaTheme.Dark)).toBeUndefined(); + expect(getColorDefinition('#ff0000', GrafanaThemeType.Light)).toBeUndefined(); + expect(getColorDefinition('#ff0000', GrafanaThemeType.Dark)).toBeUndefined(); }); it('returns definition for known hex', () => { - expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue); - expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue); + expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue); + expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue); }); }); @@ -28,8 +28,8 @@ describe('colors', () => { }); it('returns name for known hex', () => { - expect(getColorName(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue.name); - expect(getColorName(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue.name); + expect(getColorName(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue.name); + expect(getColorName(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue.name); }); }); @@ -53,7 +53,7 @@ describe('colors', () => { }); it("returns correct variant's hex for known color if theme specified", () => { - expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light); + expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaThemeType.Light)).toBe(SemiDarkBlue.variants.light); }); it('returns color if specified as hex or rgb/a', () => { diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts index 5312b27ad26..a99a93f4207 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts @@ -1,5 +1,5 @@ import { flatten } from 'lodash'; -import { GrafanaTheme } from '../types'; +import { GrafanaThemeType } from '../types'; type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple'; @@ -68,7 +68,7 @@ export const getColorDefinitionByName = (name: Color): ColorDefinition => { return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === name)[0]; }; -export const getColorDefinition = (hex: string, theme: GrafanaTheme): ColorDefinition | undefined => { +export const getColorDefinition = (hex: string, theme: GrafanaThemeType): ColorDefinition | undefined => { return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.variants[theme] === hex)[0]; }; @@ -77,7 +77,7 @@ const isHex = (color: string) => { return hexRegex.test(color); }; -export const getColorName = (color?: string, theme?: GrafanaTheme): Color | undefined => { +export const getColorName = (color?: string, theme?: GrafanaThemeType): Color | undefined => { if (!color) { return undefined; } @@ -86,7 +86,7 @@ export const getColorName = (color?: string, theme?: GrafanaTheme): Color | unde return undefined; } if (isHex(color)) { - const definition = getColorDefinition(color, theme || GrafanaTheme.Dark); + const definition = getColorDefinition(color, theme || GrafanaThemeType.Dark); return definition ? definition.name : undefined; } @@ -98,7 +98,7 @@ export const getColorByName = (colorName: string) => { return definition.length > 0 ? definition[0] : undefined; }; -export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => { +export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType): string => { if (color.indexOf('rgb') > -1 || isHex(color)) { return color; } @@ -112,14 +112,14 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): s return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark; }; -export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => { +export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaThemeType) => { return theme ? color.variants[theme] : color.variants.dark; }; const buildNamedColorsPalette = () => { const palette = new Map(); - const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true); + const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true); const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']); const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']); const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']); diff --git a/packages/grafana-ui/src/utils/storybook/themeKnob.ts b/packages/grafana-ui/src/utils/storybook/themeKnob.ts deleted file mode 100644 index a3733462bea..00000000000 --- a/packages/grafana-ui/src/utils/storybook/themeKnob.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { select } from '@storybook/addon-knobs'; -import { GrafanaTheme } from '../../types'; - -export const getThemeKnob = (defaultTheme: GrafanaTheme = GrafanaTheme.Dark) => { - return select( - 'Theme', - { - Default: defaultTheme, - Light: GrafanaTheme.Light, - Dark: GrafanaTheme.Dark, - }, - defaultTheme - ); -}; diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx new file mode 100644 index 00000000000..5417af1de05 --- /dev/null +++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { RenderFunction } from '@storybook/react'; +import { ThemeContext } from '../../themes/ThemeContext'; +import { select } from '@storybook/addon-knobs'; +import { getTheme } from '../../themes'; +import { GrafanaThemeType } from '../../types'; + +const ThemableStory: React.FunctionComponent<{}> = ({ children }) => { + const themeKnob = select( + 'Theme', + { + Light: GrafanaThemeType.Light, + Dark: GrafanaThemeType.Dark, + }, + GrafanaThemeType.Dark + ); + + return ( + + {children} + + + ); +}; + +// Temporary solution. When we update to Storybook V5 we will be able to pass data from decorator to story +// https://github.com/storybooks/storybook/issues/340#issuecomment-456013702 +export const renderComponentWithTheme = (component: React.ComponentType, props: any) => { + return ( + + {theme => { + return React.createElement(component, { + ...props, + theme, + }); + }} + + ); +}; + +export const withTheme = (story: RenderFunction) => {story()}; diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index 4806275e87d..6db442e7470 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -9,7 +9,7 @@ import { TagFilter } from './components/TagFilter/TagFilter'; import { SideMenu } from './components/sidemenu/SideMenu'; import { MetricSelect } from './components/Select/MetricSelect'; import AppNotificationList from './components/AppNotifications/AppNotificationList'; -import { ColorPicker, SeriesColorPickerPopover } from '@grafana/ui'; +import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui'; export function registerAngularDirectives() { react2AngularDirective('passwordStrength', PasswordStrength, ['password']); @@ -27,7 +27,7 @@ export function registerAngularDirectives() { 'color', ['onChange', { watchDepth: 'reference', wrapApply: true }], ]); - react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [ + react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopoverWithTheme, [ 'color', 'series', 'onColorChange', diff --git a/public/app/core/config.ts b/public/app/core/config.ts index 368b3798117..f4254ac251a 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import { PanelPlugin } from 'app/types/plugins'; +import { GrafanaTheme, getTheme, GrafanaThemeType } from '@grafana/ui'; export interface BuildInfo { version: string; @@ -36,8 +37,11 @@ export class Settings { loginError: any; viewersCanEdit: boolean; disableSanitizeHtml: boolean; + theme: GrafanaTheme; constructor(options: Settings) { + this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark); + const defaults = { datasources: {}, windowTitlePrefix: 'Grafana - ', diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index 6883401ad27..cb3ad88b191 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -1,6 +1,6 @@ import React from 'react'; import config, { Settings } from 'app/core/config'; -import { GrafanaTheme } from '@grafana/ui'; +import { GrafanaThemeType, ThemeContext, getTheme } from '@grafana/ui'; export const ConfigContext = React.createContext(config); export const ConfigConsumer = ConfigContext.Consumer; @@ -13,16 +13,20 @@ export const provideConfig = (component: React.ComponentType) => { return ConfigProvider; }; -interface ThemeProviderProps { - children: (theme: GrafanaTheme) => JSX.Element; -} +export const getCurrentThemeName = () => + config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark; +export const getCurrentTheme = () => getTheme(getCurrentThemeName()); -export const ThemeProvider = ({ children }: ThemeProviderProps) => { +export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { return ( - {({ bootData }) => { - return children(bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark); + {config => { + return {children}; }} ); }; + +export const provideTheme = (component: React.ComponentType) => { + return provideConfig((props: any) => {React.createElement(component, { ...props })}); +}; diff --git a/public/app/core/utils/react2angular.ts b/public/app/core/utils/react2angular.ts index 1057f68fcda..eb4bccab267 100644 --- a/public/app/core/utils/react2angular.ts +++ b/public/app/core/utils/react2angular.ts @@ -1,11 +1,11 @@ import coreModule from 'app/core/core_module'; -import { provideConfig } from 'app/core/utils/ConfigProvider'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; export function react2AngularDirective(name: string, component: any, options: any) { coreModule.directive(name, [ 'reactDirective', reactDirective => { - return reactDirective(provideConfig(component), options); + return reactDirective(provideTheme(component), options); }, ]); } diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index b6f37dde94f..5cb256ee1aa 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; // Services & Utils -import { processTimeSeries } from '@grafana/ui'; +import { processTimeSeries, ThemeContext } from '@grafana/ui'; // Components import { Gauge } from '@grafana/ui'; @@ -10,7 +10,6 @@ import { Gauge } from '@grafana/ui'; // Types import { GaugeOptions } from './types'; import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types'; -import { ThemeProvider } from 'app/core/utils/ConfigProvider'; interface Props extends PanelProps {} @@ -38,7 +37,7 @@ export class GaugePanel extends PureComponent { } return ( - + {theme => ( { theme={theme} /> )} - + ); } } diff --git a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx index 655c596ce84..84726ac88bf 100644 --- a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx @@ -11,7 +11,6 @@ import { import ValueOptions from 'app/plugins/panel/gauge/ValueOptions'; import GaugeOptionsEditor from './GaugeOptionsEditor'; import { GaugeOptions } from './types'; -import { ThemeProvider } from 'app/core/utils/ConfigProvider'; export const defaultProps = { options: { @@ -46,24 +45,17 @@ export default class GaugePanelOptions extends PureComponent - {(theme) => ( - <> - - - - - - - - )} - + return ( + <> + + + + + + + + ); } } diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx index d62613319b2..2cf45727c4a 100644 --- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx +++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx @@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { TimeSeries } from 'app/core/core'; import { SeriesColorPicker } from '@grafana/ui'; -import { ThemeProvider } from 'app/core/utils/ConfigProvider'; export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total']; @@ -168,24 +167,17 @@ class LegendSeriesIcon extends PureComponent - {theme => { - return ( - - - - - - ); - }} - + + + + + ); } } diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index 4141d36e273..2966bb33eb4 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { colors, GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { colors, getColorFromHexRgbOrName } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; import config from 'app/core/config'; @@ -113,7 +113,7 @@ export class DataProcessor { const series = new TimeSeries({ datapoints: datapoints, alias: alias, - color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark), + color: getColorFromHexRgbOrName(color, config.theme.type), unit: seriesData.unit, }); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index aeb540551b8..54ba4ed1e6f 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -25,7 +25,10 @@ import ReactDOM from 'react-dom'; import { Legend, GraphLegendProps } from './Legend/Legend'; import { GraphCtrl } from './module'; -import { GrafanaTheme, getValueFormat } from '@grafana/ui'; +import { getValueFormat } from '@grafana/ui'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; + +const LegendWithThemeProvider = provideTheme(Legend); class GraphElement { ctrl: GraphCtrl; @@ -43,6 +46,7 @@ class GraphElement { legendElem: HTMLElement; constructor(private scope, private elem, private timeSrv) { + this.ctrl = scope.ctrl; this.dashboard = this.ctrl.dashboard; this.panel = this.ctrl.panel; @@ -51,10 +55,7 @@ class GraphElement { this.panelWidth = 0; this.eventManager = new EventManager(this.ctrl); this.thresholdManager = new ThresholdManager(this.ctrl); - this.timeRegionManager = new TimeRegionManager( - this.ctrl, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark - ); + this.timeRegionManager = new TimeRegionManager(this.ctrl, config.theme.type); this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => { return this.sortedSeries; }); @@ -109,7 +110,7 @@ class GraphElement { onToggleAxis: this.ctrl.onToggleAxis, }; - const legendReactElem = React.createElement(Legend, legendProps); + const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps); ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel()); } diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 68d982eab13..3919c4f69a9 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -10,7 +10,7 @@ import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { DataProcessor } from './data_processor'; import { axesEditorComponent } from './axes_editor'; import config from 'app/core/config'; -import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { getColorFromHexRgbOrName } from '@grafana/ui'; class GraphCtrl extends MetricsPanelCtrl { static template = template; @@ -244,7 +244,7 @@ class GraphCtrl extends MetricsPanelCtrl { } onColorChange = (series, color) => { - series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)); + series.setColor(getColorFromHexRgbOrName(color, config.theme.type)); this.panel.aliasColors[series.alias] = color; this.render(); }; diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index 2917583ff36..ea39927bf57 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -1,7 +1,7 @@ import 'vendor/flot/jquery.flot'; import _ from 'lodash'; import moment from 'moment'; -import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; type TimeRegionColorDefinition = { fill: string; @@ -43,7 +43,7 @@ export function getColorModes() { }); } -function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition { +function getColor(timeRegion, theme: GrafanaThemeType): TimeRegionColorDefinition { if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) { timeRegion.colorMode = 'red'; } @@ -58,7 +58,7 @@ function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition { const colorMode = colorModes[timeRegion.colorMode]; if (colorMode.themeDependent === true) { - return theme === GrafanaTheme.Light ? colorMode.lightColor : colorMode.darkColor; + return theme === GrafanaThemeType.Light ? colorMode.lightColor : colorMode.darkColor; } return { @@ -71,7 +71,7 @@ export class TimeRegionManager { plot: any; timeRegions: any; - constructor(private panelCtrl, private theme: GrafanaTheme = GrafanaTheme.Dark) {} + constructor(private panelCtrl, private theme: GrafanaThemeType = GrafanaThemeType.Dark) {} draw(plot) { this.timeRegions = this.panelCtrl.panel.timeRegions; diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 81329fe297b..dea250abf74 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -5,7 +5,7 @@ import { contextSrv } from 'app/core/core'; import { tickStep } from 'app/core/utils/ticks'; import { getColorScale, getOpacityScale } from './color_scale'; import coreModule from 'app/core/core_module'; -import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; const LEGEND_HEIGHT_PX = 6; const LEGEND_WIDTH_PX = 100; @@ -250,7 +250,7 @@ function drawSimpleOpacityLegend(elem, options) { .attr('stroke-width', 0) .attr( 'fill', - getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark) + getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark) ) .style('opacity', d => legendOpacityScale(d)); } diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 6489c9e9895..63604382432 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -7,7 +7,7 @@ import * as ticksUtils from 'app/core/utils/ticks'; import { HeatmapTooltip } from './heatmap_tooltip'; import { mergeZeroBuckets } from './heatmap_data_converter'; import { getColorScale, getOpacityScale } from './color_scale'; -import { GrafanaTheme, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui'; const MIN_CARD_SIZE = 1, CARD_PADDING = 1, @@ -663,7 +663,7 @@ export class HeatmapRenderer { if (this.panel.color.mode === 'opacity') { return getColorFromHexRgbOrName( this.panel.color.cardColor, - contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark + contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark ); } else { return this.colorScale(d.count); diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 2768951d2ba..21ab32278f8 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn'; import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; import { MetricsPanelCtrl } from 'app/plugins/sdk'; -import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; class SingleStatCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -588,10 +588,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { fill: 1, zero: false, lineWidth: 1, - fillColor: getColorFromHexRgbOrName( - panel.sparkline.fillColor, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark - ), + fillColor: getColorFromHexRgbOrName(panel.sparkline.fillColor, config.theme.type), }, }, yaxes: { show: false }, @@ -608,10 +605,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { const plotSeries = { data: data.flotpairs, - color: getColorFromHexRgbOrName( - panel.sparkline.lineColor, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark - ), + color: getColorFromHexRgbOrName(panel.sparkline.lineColor, config.theme.type), }; $.plot(plotCanvas, [plotSeries], options); @@ -630,7 +624,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { // Map panel colors to hex or rgb/a values data.colorMap = panel.colors.map(color => - getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark) + getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark) ); const body = panel.gauge.show ? '' : getBigValueHtml(); diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 82763e1839a..268f5aa7ac4 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -6,7 +6,6 @@ import { transformDataToTable } from './transformers'; import { tablePanelEditor } from './editor'; import { columnOptionsTab } from './column_options'; import { TableRenderer } from './renderer'; -import { GrafanaTheme } from '@grafana/ui'; class TablePanelCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -131,7 +130,7 @@ class TablePanelCtrl extends MetricsPanelCtrl { this.dashboard.isTimezoneUtc(), this.$sanitize, this.templateSrv, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark, + config.theme.type ); return super.render(this.table); diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index 90479a67602..e9bf89f45fe 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import moment from 'moment'; import kbn from 'app/core/utils/kbn'; -import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; +import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType } from '@grafana/ui'; export class TableRenderer { formatters: any[]; @@ -13,7 +13,7 @@ export class TableRenderer { private isUtc, private sanitize, private templateSrv, - private theme?: GrafanaTheme + private theme?: GrafanaThemeType ) { this.initColumns(); } diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index d64e74e3949..35c23239561 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -5,6 +5,7 @@ import { Provider } from 'react-redux'; import coreModule from 'app/core/core_module'; import { store } from 'app/store/store'; import { ContextSrv } from 'app/core/services/context_srv'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; function WrapInProvider(store, Component, props) { return ( @@ -49,7 +50,7 @@ export function reactContainer( document.body.classList.add('is-react'); - ReactDOM.render(WrapInProvider(store, component, props), elem[0]); + ReactDOM.render(WrapInProvider(store, provideTheme(component), props), elem[0]); scope.$on('$destroy', () => { document.body.classList.remove('is-react'); diff --git a/scripts/webpack/sass.rule.js b/scripts/webpack/sass.rule.js index 75455fc4184..66a48a12b32 100644 --- a/scripts/webpack/sass.rule.js +++ b/scripts/webpack/sass.rule.js @@ -1,6 +1,6 @@ 'use strict'; -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = function(options) { return { @@ -23,7 +23,12 @@ module.exports = function(options) { config: { path: __dirname + '/postcss.config.js' }, }, }, - { loader: 'sass-loader', options: { sourceMap: options.sourceMap } }, + { + loader: 'sass-loader', + options: { + sourceMap: options.sourceMap + }, + }, ], }; }; diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js index b37e4c08592..c1053f1f7da 100644 --- a/scripts/webpack/webpack.hot.js +++ b/scripts/webpack/webpack.hot.js @@ -85,7 +85,9 @@ module.exports = merge(common, { config: { path: __dirname + '/postcss.config.js' }, }, }, - 'sass-loader', // compiles Sass to CSS + { + loader: 'sass-loader' + } ], }, { diff --git a/scripts/webpack/webpack.test.js b/scripts/webpack/webpack.test.js deleted file mode 100644 index ec9ee5e26df..00000000000 --- a/scripts/webpack/webpack.test.js +++ /dev/null @@ -1,38 +0,0 @@ -const webpack = require('webpack'); -const merge = require('webpack-merge'); -const common = require('./webpack.common.js'); - -config = merge(common, { - mode: 'development', - devtool: 'cheap-module-source-map', - - externals: { - 'react/addons': true, - 'react/lib/ExecutionEnvironment': true, - 'react/lib/ReactContext': true, - }, - - module: { - rules: [ - { - test: /\.tsx?$/, - exclude: /node_modules/, - use: { - loader: 'ts-loader', - options: { - transpileOnly: true, - }, - }, - }, - ], - }, - - plugins: [ - new webpack.SourceMapDevToolPlugin({ - filename: null, // if no value is provided the sourcemap is inlined - test: /\.(ts|js)($|\?)/i, // process .js and .ts files only - }), - ], -}); - -module.exports = config;