From 53b00eb3be92e11560fe164af32aa5a1a661364f Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Sun, 8 Sep 2024 13:35:46 +0200 Subject: [PATCH] chore: use custom range slider --- package.json | 1 - .../MultiRangeSlider/MultiRangeSlider.tsx | 96 ++++++++++++++++++ .../MultiRangeSlider/multiRangeSlider.css | 98 +++++++++++++++++++ src/components/SimplePanel.tsx | 19 +--- yarn.lock | 36 +++++-- 5 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 src/components/MultiRangeSlider/MultiRangeSlider.tsx create mode 100644 src/components/MultiRangeSlider/multiRangeSlider.css diff --git a/package.json b/package.json index 62752cd..dfde878 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "@grafana/runtime": "10.0.3", "@grafana/schema": "10.0.3", "@grafana/ui": "10.0.3", - "multi-range-slider-react": "^2.0.7", "react": "18.2.0", "react-dom": "18.2.0", "tslib": "2.5.3" diff --git a/src/components/MultiRangeSlider/MultiRangeSlider.tsx b/src/components/MultiRangeSlider/MultiRangeSlider.tsx new file mode 100644 index 0000000..4cac1a5 --- /dev/null +++ b/src/components/MultiRangeSlider/MultiRangeSlider.tsx @@ -0,0 +1,96 @@ +import React, { ChangeEvent, FC, useCallback, useEffect, useState, useRef } from 'react'; +import classnames from 'classnames'; +import './multiRangeSlider.css'; + +interface MultiRangeSliderProps { + min: number; + max: number; + onChange: Function; +} + +const MultiRangeSlider: FC = ({ min, max, onChange }) => { + const [minVal, setMinVal] = useState(min); + const [maxVal, setMaxVal] = useState(max); + const minValRef = useRef(null); + const maxValRef = useRef(null); + const range = useRef(null); + + // Convert to percentage + const getPercent = useCallback((value: number) => Math.round(((value - min) / (max - min)) * 100), [min, max]); + + // Set width of the range to decrease from the left side + useEffect(() => { + if (maxValRef.current) { + const minPercent = getPercent(minVal); + const maxPercent = getPercent(+maxValRef.current.value); + + if (range.current) { + range.current.style.left = `${minPercent}%`; + range.current.style.width = `${maxPercent - minPercent}%`; + } + } + }, [minVal, getPercent]); + + // Set width of the range to decrease from the right side + useEffect(() => { + if (minValRef.current) { + const minPercent = getPercent(+minValRef.current.value); + const maxPercent = getPercent(maxVal); + + if (range.current) { + range.current.style.width = `${maxPercent - minPercent}%`; + } + } + }, [maxVal, getPercent]); + + // Get min and max values when their state changes + useEffect(() => { + onChange({ min: minVal, max: maxVal }); + }, [minVal, maxVal, onChange]); + + return ( +
+ ) => { + const value = Math.min(+event.target.value, maxVal - 1); + setMinVal(value); + event.target.value = value.toString(); + }} + className={classnames('thumb thumb--zindex-3', { + 'thumb--zindex-5': minVal > max - 100, + })} + /> + ) => { + const value = Math.max(+event.target.value, minVal + 1); + setMaxVal(value); + event.target.value = value.toString(); + }} + className="thumb thumb--zindex-4" + /> + +
+
+
+
+ {minVal} +
+
+ {maxVal} +
+
+
+ ); +}; + +export default MultiRangeSlider; diff --git a/src/components/MultiRangeSlider/multiRangeSlider.css b/src/components/MultiRangeSlider/multiRangeSlider.css new file mode 100644 index 0000000..b1909ef --- /dev/null +++ b/src/components/MultiRangeSlider/multiRangeSlider.css @@ -0,0 +1,98 @@ +.mrs-container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} + +.slider { + position: relative; + width: 100%; +} + +.slider__track, +.slider__range, +.slider__left-value, +.slider__right-value { + position: absolute; +} + +.slider__track, +.slider__range { + border-radius: 3px; + height: 5px; +} + +.slider__track { + background-color: #ced4da; + width: 100%; + z-index: 1; +} + +.slider__range { + background-color: rgba(59, 130, 246, 0.5); + z-index: 2; +} + +.slider__left-value, +.slider__right-value { + color: #dee2e6; + font-size: 12px; + margin-top: 20px; + transform: translateX(-50%); +} + +/* Removing the default appearance */ +.thumb, +.thumb::-webkit-slider-thumb { + -webkit-appearance: none; + -webkit-tap-highlight-color: transparent; +} + +.thumb { + pointer-events: none; + position: absolute; + height: 0; + width: 100%; + outline: none; +} + +.thumb--zindex-3 { + z-index: 3; +} + +.thumb--zindex-4 { + z-index: 4; +} + +.thumb--zindex-5 { + z-index: 5; +} + +/* For Chrome browsers */ +.thumb::-webkit-slider-thumb { + background-color: #f1f5f7; + border: none; + border-radius: 50%; + box-shadow: 0 0 1px 1px #ced4da; + cursor: pointer; + height: 18px; + width: 18px; + margin-top: 4px; + pointer-events: all; + position: relative; +} + +/* For Firefox browsers */ +.thumb::-moz-range-thumb { + background-color: #f1f5f7; + border: none; + border-radius: 50%; + box-shadow: 0 0 1px 1px #ced4da; + cursor: pointer; + height: 18px; + width: 18px; + margin-top: 4px; + pointer-events: all; + position: relative; +} diff --git a/src/components/SimplePanel.tsx b/src/components/SimplePanel.tsx index 6f02c32..abbb98f 100644 --- a/src/components/SimplePanel.tsx +++ b/src/components/SimplePanel.tsx @@ -1,15 +1,10 @@ import React, { useEffect, useState, useCallback } from 'react'; import { PanelProps } from '@grafana/data'; -import MultiRangeSlider from 'multi-range-slider-react'; import { getTemplateSrv, locationService } from '@grafana/runtime'; import { SimpleOptions } from '../types'; import findMinAndMaxValues from './utils/findMinAndMaxValues'; import { DEFAULT_MAX_THRESHOLD, DEFAULT_MIN_THRESHOLD } from './constants'; - -type RangeChangeEvent = { - minValue: number; - maxValue: number; -}; +import MultiRangeSlider from './MultiRangeSlider/MultiRangeSlider'; interface Props extends PanelProps {} @@ -41,9 +36,7 @@ const SimplePanel: React.FC = ({ options }) => { }, []); const handleSliderInput = useCallback( - (e: RangeChangeEvent) => { - let { minValue, maxValue } = e; - + (minValue: number, maxValue: number) => { minValue = Math.max(minThreshold, Math.min(minValue, maxThreshold)); maxValue = Math.max(minValue, Math.min(maxValue, maxThreshold)); @@ -155,13 +148,7 @@ const SimplePanel: React.FC = ({ options }) => { handleSliderInput(min, max)} /> ); diff --git a/yarn.lock b/yarn.lock index d4b4a00..44fc793 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7258,11 +7258,6 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multi-range-slider-react@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/multi-range-slider-react/-/multi-range-slider-react-2.0.7.tgz#e028511c19142e1edf8b5f85f5c1ec35f21e8f9d" - integrity sha512-KRYUkatXxxYceL5ZT8xvetIN+4yTCdWszxRC6Y6Jkua+oRrWVkmBR6v3R03kosYg/QtcETBf2L1Jt+4U66DFbg== - murmurhash-js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" @@ -9124,7 +9119,16 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9196,7 +9200,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9976,7 +9987,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9994,6 +10005,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"