diff --git a/public/app/core/components/OptionsUI/NumberInput.tsx b/public/app/core/components/OptionsUI/NumberInput.tsx index 5a95524cb44..0631853b5d0 100644 --- a/public/app/core/components/OptionsUI/NumberInput.tsx +++ b/public/app/core/components/OptionsUI/NumberInput.tsx @@ -126,7 +126,12 @@ export class NumberInput extends PureComponent { range = `> ${max}`; } return ( - + {this.renderInput()} ); diff --git a/public/app/core/components/OptionsUI/slider.tsx b/public/app/core/components/OptionsUI/slider.tsx index 2d1c3f0bb2e..079a4464593 100644 --- a/public/app/core/components/OptionsUI/slider.tsx +++ b/public/app/core/components/OptionsUI/slider.tsx @@ -1,26 +1,144 @@ -import React from 'react'; +import { css, cx } from '@emotion/css'; +import { Global } from '@emotion/react'; +import SliderComponent from 'rc-slider'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { FieldConfigEditorProps, SliderFieldConfigSettings } from '@grafana/data'; -import { Slider } from '@grafana/ui'; +import { FieldConfigEditorProps, GrafanaTheme2, SliderFieldConfigSettings } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; +import { getStyles } from '@grafana/ui/src/components/Slider/styles'; + +import { NumberInput } from './NumberInput'; export const SliderValueEditor: React.FC> = ({ value, onChange, item, }) => { + // Input reference + const inputRef = useRef(null); + + // Settings const { settings } = item; - const initialValue = typeof value === 'number' ? value : typeof value === 'string' ? +value : 0; + const min = settings?.min || 0; + const max = settings?.max || 100; + const step = settings?.step; + const marks = settings?.marks || { [min]: min, [max]: max }; + const included = settings?.included; + const ariaLabelForHandle = settings?.ariaLabelForHandle; + + // Core slider specific parameters and state + const inputWidthDefault = 75; + const isHorizontal = true; + const theme = useTheme2(); + const SliderWithTooltip = SliderComponent; + const [sliderValue, setSliderValue] = useState(value ?? min); + const [inputWidth, setInputWidth] = useState(inputWidthDefault); + + // Check for a difference between prop value and internal state + useEffect(() => { + if (value != null && value !== sliderValue) { + setSliderValue(value); + } + }, [value, sliderValue]); + + // Using input font and expected maximum number of digits, set input width + useEffect(() => { + const inputElement = getComputedStyle(inputRef.current!); + const fontWeight = inputElement.getPropertyValue('font-weight') || 'normal'; + const fontSize = inputElement.getPropertyValue('font-size') || '16px'; + const fontFamily = inputElement.getPropertyValue('font-family') || 'Arial'; + const wideNumericalCharacter = '0'; + const marginDigits = 4; // extra digits to account for things like negative, exponential, and controls + const inputPadding = 8; // TODO: base this on input styling + const maxDigits = + Math.max((max + (step || 0)).toString().length, (max - (step || 0)).toString().length) + marginDigits; + const refString = wideNumericalCharacter.repeat(maxDigits); + const calculatedTextWidth = getTextWidth(refString, `${fontWeight} ${fontSize} ${fontFamily}`); + if (calculatedTextWidth) { + setInputWidth(calculatedTextWidth + inputPadding * 2); + } + }, [max, step]); + + const onSliderChange = useCallback( + (v: number) => { + setSliderValue(v); + + if (onChange) { + onChange(v); + } + }, + [setSliderValue, onChange] + ); + + const onSliderInputChange = useCallback( + (value?: number) => { + let v = value; + + if (Number.isNaN(v) || !v) { + v = 0; + } + + setSliderValue(v); + + if (onChange) { + onChange(v); + } + }, + [onChange] + ); + + // Styles + const styles = getStyles(theme, isHorizontal, Boolean(marks)); + const stylesSlider = getStylesSlider(theme, inputWidth); + const sliderInputClassNames = !isHorizontal ? [styles.sliderInputVertical] : []; return ( - +
+ {/** Slider tooltip's parent component is body and therefore we need Global component to do css overrides for it. */} + + +
); }; + +// Calculate width of string with given font +function getTextWidth(text: string, font: string): number | null { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (context) { + context.font = font; + const metrics = context.measureText(text); + return metrics.width; + } + return null; +} + +const getStylesSlider = (theme: GrafanaTheme2, width: number) => { + return { + numberInputWrapper: css` + margin-left: 10px; + max-height: 32px; + max-width: ${width}px; + min-width: ${width}px; + overflow: visible; + width: 100%; + `, + }; +}; diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index 0647d99a707..44ebe5871fc 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -515,7 +515,7 @@ export class GeomapPanel extends Component { const handler = await item.create(map, options, this.props.eventBus, config.theme2); const layer = handler.init(); if (options.opacity != null) { - layer.setOpacity(1 - options.opacity); + layer.setOpacity(options.opacity); } if (!options.name) {