fix(DicomTagBrowser): Fix parsing problems that was limiting nested tags rendering (#3306)

* fix(DicomTagBrowser): Fix parsing problems that was limiting nested tags rendering

* Added a progress indicator when switching to a list of more than 1000 rows.

* Switched to using react-window to virtualize the dicom tag browser window.

* Added filtering to the DICOM tag browser with a new search UI widget.
Lots of tweaks and updates to the look-and-feel.

* Removed unused import.

* PR feedback including some UI tweaks.

* More PR feedback.

---------

Co-authored-by: Igor Octaviano <igor.octaviano@radicalimaging.com>
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
This commit is contained in:
Igor Octaviano 2023-05-26 12:42:25 -03:00 committed by GitHub
parent 217b330d7b
commit b684d80426
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 407 additions and 166 deletions

View File

@ -38,6 +38,7 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-i18next": "^12.2.2", "react-i18next": "^12.2.2",
"react-window": "^1.8.9",
"webpack": "^5.50.0", "webpack": "^5.50.0",
"webpack-merge": "^5.7.3" "webpack-merge": "^5.7.3"
}, },

View File

@ -1,27 +1,41 @@
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import moment from 'moment'; import moment from 'moment';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo, useEffect, useRef } from 'react';
import { classes } from '@ohif/core'; import { classes } from '@ohif/core';
import { Icon, InputRange, Select, Typography } from '@ohif/ui';
import debounce from 'lodash.debounce';
import classNames from 'classnames';
import DicomTagTable from './DicomTagTable'; import DicomTagTable from './DicomTagTable';
import './DicomTagBrowser.css'; import './DicomTagBrowser.css';
import { InputRange, Select, Typography } from '@ohif/ui';
const { ImageSet } = classes; const { ImageSet } = classes;
const { DicomMetaDictionary } = dcmjs.data; const { DicomMetaDictionary } = dcmjs.data;
const { nameMap } = DicomMetaDictionary; const { nameMap } = DicomMetaDictionary;
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
// The column indices that are to be excluded during a filter of the table.
// At present the column indices are:
// 0: DICOM tag
// 1: VR
// 2: Keyword
// 3: Value
const excludedColumnIndicesForFilter: Set<number> = new Set([1]);
const [ const [
selectedDisplaySetInstanceUID, selectedDisplaySetInstanceUID,
setSelectedDisplaySetInstanceUID, setSelectedDisplaySetInstanceUID,
] = useState(displaySetInstanceUID); ] = useState(displaySetInstanceUID);
const [instanceNumber, setInstanceNumber] = useState(1); const [instanceNumber, setInstanceNumber] = useState(1);
const [filterValue, setFilterValue] = useState('');
const onSelectChange = value => { const onSelectChange = value => {
setSelectedDisplaySetInstanceUID(value.value); setSelectedDisplaySetInstanceUID(value.value);
setInstanceNumber(1); setInstanceNumber(1);
}; };
const searchInputRef = useRef(null);
const activeDisplaySet = displaySets.find( const activeDisplaySet = displaySets.find(
ds => ds.displaySetInstanceUID === selectedDisplaySetInstanceUID ds => ds.displaySetInstanceUID === selectedDisplaySetInstanceUID
); );
@ -54,64 +68,130 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
}); });
}, [displaySets]); }, [displaySets]);
const rows = useMemo(() => {
let metadata;
if (isImageStack) {
metadata = activeDisplaySet.images[instanceNumber - 1];
} else {
metadata = activeDisplaySet.instance || activeDisplaySet;
}
const tags = getSortedTags(metadata);
return getFormattedRowsFromTags(tags, metadata);
}, [instanceNumber, selectedDisplaySetInstanceUID]);
const filteredRows = useMemo(() => {
if (!filterValue) {
return rows;
}
const filterValueLowerCase = filterValue.toLowerCase();
return rows.filter(row => {
return row.reduce((keepRow, col, colIndex) => {
if (keepRow) {
// We are already keeping the row, why do more work so return now.
return keepRow;
}
if (excludedColumnIndicesForFilter.has(colIndex)) {
return keepRow;
}
return keepRow || col.toLowerCase().includes(filterValueLowerCase);
}, false);
});
}, [rows, filterValue]);
const debouncedSetFilterValue = useMemo(() => {
return debounce(setFilterValue, 200);
}, []);
useEffect(() => {
return () => {
debouncedSetFilterValue?.cancel();
};
}, []);
return ( return (
<div className="dicom-tag-browser-content"> <div className="dicom-tag-browser-content">
<div className="flex flex-row items-center mb-2"> <div className="flex flex-row mb-6 items-center pl-1">
<Typography variant="subtitle" className="w-1/2 mr-8"> <div className="flex flex-row items-center w-1/2">
Series <Typography variant="subtitle" className="mr-4">
</Typography> Series
{showInstanceList && (
<Typography variant="subtitle" className="w-1/2">
Instance Number
</Typography> </Typography>
)} <div className="grow mr-8">
</div> <Select
<div className="flex flex-row items-center mb-6"> id="display-set-selector"
<div className="w-1/2 mr-8"> isClearable={false}
<Select onChange={onSelectChange}
isClearable={false} options={displaySetList}
onChange={onSelectChange} value={displaySetList.find(
options={displaySetList} ds => ds.value === selectedDisplaySetInstanceUID
value={displaySetList.find( )}
ds => ds.value === selectedDisplaySetInstanceUID className="text-white"
)}
className="text-white"
/>
</div>
{showInstanceList ? (
<div className="w-1/2">
<InputRange
value={instanceNumber}
key={selectedDisplaySetInstanceUID}
onChange={value => {
setInstanceNumber(parseInt(value));
}}
minValue={1}
maxValue={activeDisplaySet.images.length}
step={1}
/> />
</div> </div>
) : null} </div>
<div className="flex flex-row items-center w-1/2">
{showInstanceList && (
<Typography variant="subtitle" className="mr-4">
Instance Number
</Typography>
)}
{showInstanceList && (
<div className="grow">
<InputRange
value={instanceNumber}
key={selectedDisplaySetInstanceUID}
onChange={value => {
setInstanceNumber(parseInt(value));
}}
minValue={1}
maxValue={activeDisplaySet.images.length}
step={1}
inputClassName="w-full"
labelPosition="left"
trackColor={'#3a3f99'}
/>
</div>
)}
</div>
</div> </div>
<DicomTagTable <div className="w-full h-1 bg-black"></div>
rows={getFormattedRowsFromTags(activeDisplaySet, instanceNumber)} <div className="flex flex-row my-3 w-1/2">
></DicomTagTable> {/* TODO - refactor the following into its own reusable component */}
<label className="relative block w-full mr-8">
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<Icon name="icon-search"></Icon>
</span>
<input
ref={searchInputRef}
type="text"
className="block bg-black w-full shadow transition duration-300 appearance-none border border-inputfield-main focus:border-inputfield-focus focus:outline-none disabled:border-inputfield-disabled rounded w-full py-2 px-9 text-base leading-tight placeholder:text-inputfield-placeholder"
placeholder="Search metadata..."
onChange={event => debouncedSetFilterValue(event.target.value)}
autoComplete="off"
></input>
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
<Icon
name="icon-clear-field"
className={classNames(
'cursor-pointer',
filterValue ? '' : 'hidden'
)}
onClick={() => {
searchInputRef.current.value = '';
debouncedSetFilterValue('');
}}
></Icon>
</span>
</label>
</div>
<DicomTagTable rows={filteredRows} />
</div> </div>
); );
}; };
function getFormattedRowsFromTags(displaySet, instanceNumber) { function getFormattedRowsFromTags(tags, metadata) {
const isImageStack = _isImageStack(displaySet);
let metadata;
if (isImageStack) {
metadata = displaySet.images[instanceNumber - 1];
} else {
metadata = displaySet;
}
const tags = getSortedTags(metadata);
const rows = []; const rows = [];
tags.forEach(tagInfo => { tags.forEach(tagInfo => {
@ -126,7 +206,7 @@ function getFormattedRowsFromTags(displaySet, instanceNumber) {
const { values } = tagInfo; const { values } = tagInfo;
values.forEach((item, index) => { values.forEach((item, index) => {
const formatedRowsFromTags = getFormattedRowsFromTags(item); const formatedRowsFromTags = getFormattedRowsFromTags(item, metadata);
rows.push([ rows.push([
`${item[0].tagIndent}(FFFE,E000)`, `${item[0].tagIndent}(FFFE,E000)`,
@ -140,34 +220,21 @@ function getFormattedRowsFromTags(displaySet, instanceNumber) {
} else { } else {
if (tagInfo.vr === 'xs') { if (tagInfo.vr === 'xs') {
try { try {
/* const dataset = metadataProvider.getStudyDataset( const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
meta.StudyInstanceUID const originalTagInfo = metadata[tag];
);*/ tagInfo.vr = originalTagInfo.vr;
// console.log(dataset);
// const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
// const originalTagInfo = dataset[tag];
// tagInfo.vr = originalTagInfo.vr;
} catch (error) { } catch (error) {
console.error( console.error(
`Failed to parse value representation for tag '${tagInfo.keyword}'` `Failed to parse value representation for tag '${tagInfo.keyword}'`
); );
} }
} }
if (tagInfo.vr === 'PN') { rows.push([
rows.push([ `${tagInfo.tagIndent}${tagInfo.tag}`,
`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr,
tagInfo.vr, tagInfo.keyword,
tagInfo.keyword, tagInfo.value,
tagInfo.value, ]);
]);
} else {
rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr,
tagInfo.keyword,
tagInfo.value,
]);
}
} }
}); });

View File

@ -1,97 +1,224 @@
import React from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { VariableSizeList as List } from 'react-window';
import classNames from 'classnames';
import debounce from 'lodash.debounce';
function ColumnHeaders() { const lineHeightPx = 20;
const lineHeightClassName = `leading-[${lineHeightPx}px]`;
const rowVerticalPaddingPx = 10;
const rowBottomBorderPx = 1;
const rowVerticalPaddingStyle = { padding: `${rowVerticalPaddingPx}px 0` };
const rowStyle = {
borderBottomWidth: `${rowBottomBorderPx}px`,
...rowVerticalPaddingStyle,
};
function ColumnHeaders({ tagRef, vrRef, keywordRef, valueRef }) {
return ( return (
<div className="m-auto flex flex-col py-2 bg-secondary-light"> <div
<div className="flex flex-row w-full"> className={classNames(
<div className="px-3 w-5/24"> 'flex flex-row w-full bg-secondary-dark ohif-scrollbar overflow-y-scroll'
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none"> )}
<span className="flex flex-row items-center focus:outline-none"> style={rowVerticalPaddingStyle}
Tag >
</span> <div className="px-3 w-4/24">
</label> <label
</div> ref={tagRef}
<div className="px-3 w-4/24"> className="flex flex-col flex-1 text-white text-lg pl-1 select-none"
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none"> >
<span className="flex flex-row items-center focus:outline-none"> <span className="flex flex-row items-center focus:outline-none">
VR Tag
</span> </span>
</label> </label>
</div> </div>
<div className="px-3 w-6/24"> <div className="px-3 w-2/24">
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none"> <label
<span className="flex flex-row items-center focus:outline-none"> ref={vrRef}
Keyword className="flex flex-col flex-1 text-white text-lg pl-1 select-none"
</span> >
</label> <span className="flex flex-row items-center focus:outline-none">
</div> VR
<div className="px-3 w-5/24"> </span>
<label className="flex flex-col flex-1 text-white text-lg pl-1 select-none"> </label>
<span className="flex flex-row items-center focus:outline-none"> </div>
Value <div className="px-3 w-6/24">
</span> <label
</label> ref={keywordRef}
</div> className="flex flex-col flex-1 text-white text-lg pl-1 select-none"
>
<span className="flex flex-row items-center focus:outline-none">
Keyword
</span>
</label>
</div>
<div className="px-3 w-5/24 grow">
<label
ref={valueRef}
className="flex flex-col flex-1 text-white text-lg pl-1 select-none"
>
<span className="flex flex-row items-center focus:outline-none">
Value
</span>
</label>
</div> </div>
</div> </div>
); );
} }
function DicomTagTable({ rows }) { function DicomTagTable({ rows }) {
const listRef = useRef();
const canvasRef = useRef();
const [tagHeaderElem, setTagHeaderElem] = useState(null);
const [vrHeaderElem, setVrHeaderElem] = useState(null);
const [keywordHeaderElem, setKeywordHeaderElem] = useState(null);
const [valueHeaderElem, setValueHeaderElem] = useState(null);
// Here the refs are inturn stored in state to trigger a render of the table.
// This virtualized table does NOT render until the header is rendered because the header column widths are used to determine the row heights in the table.
// Therefore whenever the refs change (in particular the first time the refs are set), we want to trigger a render of the table.
const tagRef = elem => {
if (elem) {
setTagHeaderElem(elem);
}
};
const vrRef = elem => {
if (elem) {
setVrHeaderElem(elem);
}
};
const keywordRef = elem => {
if (elem) {
setKeywordHeaderElem(elem);
}
};
const valueRef = elem => {
if (elem) {
setValueHeaderElem(elem);
}
};
/**
* When new rows are set, scroll to the top and reset the virtualization.
*/
useEffect(() => {
if (!listRef?.current) {
return;
}
listRef.current.scrollTo(0);
listRef.current.resetAfterIndex(0);
}, [rows]);
/**
* When the browser window resizes, update the row virtualization (i.e. row heights)
*/
useEffect(() => {
const debouncedResize = debounce(
() => listRef.current.resetAfterIndex(0),
100
);
window.addEventListener('resize', debouncedResize);
return () => {
debouncedResize.cancel();
window.removeEventListener('resize', debouncedResize);
};
}, []);
const Row = useCallback(
({ index, style }) => {
const row = rows[index];
return (
<div
style={{ ...style, ...rowStyle }}
className={classNames(
'hover:bg-secondary-main transition duration-300 bg-black flex flex-row w-full border-secondary-light items-center text-base break-all',
lineHeightClassName
)}
key={`DICOMTagRow-${index}`}
>
<div className="px-3 w-4/24">{row[0]}</div>
<div className="px-3 w-2/24">{row[1]}</div>
<div className="px-3 w-6/24">{row[2]}</div>
<div className="px-3 w-5/24 grow">{row[3]}</div>
</div>
);
},
[rows]
);
/**
* Whenever any one of the column headers is set, then the header is rendered.
* Here we chose the tag header.
*/
const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [
tagHeaderElem,
]);
/**
* Get the item/row size. We use the header column widths to calculate the various row heights.
* @param index the row index
* @returns the row height
*/
const getItemSize = useCallback(
index => {
const headerWidths = [
tagHeaderElem.offsetWidth,
vrHeaderElem.offsetWidth,
keywordHeaderElem.offsetWidth,
valueHeaderElem.offsetWidth,
];
const context = canvasRef.current.getContext('2d');
context.font = getComputedStyle(canvasRef.current).font;
return rows[index]
.map((colText, index) => {
const colOneLineWidth = context.measureText(colText).width;
const numLines = Math.ceil(colOneLineWidth / headerWidths[index]);
return (
numLines * lineHeightPx +
2 * rowVerticalPaddingPx +
rowBottomBorderPx
);
})
.reduce((maxHeight, colHeight) => Math.max(maxHeight, colHeight));
},
[rows, keywordHeaderElem, tagHeaderElem, valueHeaderElem, vrHeaderElem]
);
return ( return (
<div> <div>
{ColumnHeaders()} <canvas
style={{ visibility: 'hidden', position: 'absolute' }}
className="text-base"
ref={canvasRef}
/>
<ColumnHeaders
tagRef={tagRef}
vrRef={vrRef}
keywordRef={keywordRef}
valueRef={valueRef}
/>
<div <div
className="m-auto relative border-2 border-secondary-light overflow-hidden ohif-scrollbar" className="m-auto relative border-2 border-black bg-black"
style={{ height: '32rem' }} style={{ height: '32rem' }}
> >
<table className="w-full text-white"> {isHeaderRendered() && (
<tbody> <List
{rows.map((row, index) => { ref={listRef}
const className = row.className ? row.className : null; height={500}
itemCount={rows.length}
return ( itemSize={getItemSize}
<tr width={'100%'}
className="hover:bg-secondary-main transition duration-300 bg-primary-dark" className="ohif-scrollbar"
key={`DICOMTagRow-${index}`} >
> {Row}
<td </List>
style={{ maxWidth: '0px' }} )}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-5/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[0]}</div>
</div>
</td>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-4/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[1]}</div>
</div>
</td>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-6/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[2]}</div>
</div>
</td>
<td
style={{ maxWidth: '0px' }}
className="px-4 py-2 text-base break-all border-b border-secondary-light w-14/24"
>
<div className="flex">
<div className="inline-flex max-w-full">{row[3]}</div>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div> </div>
</div> </div>
); );

View File

@ -47,6 +47,7 @@
"react-outside-click-handler": "^1.3.0", "react-outside-click-handler": "^1.3.0",
"react-select": "3.0.8", "react-select": "3.0.8",
"react-with-direction": "^1.3.1", "react-with-direction": "^1.3.1",
"react-window": "^1.8.9",
"swiper": "^8.4.2", "swiper": "^8.4.2",
"webpack": "^5.81.0" "webpack": "^5.81.0"
}, },

View File

@ -0,0 +1,8 @@
<svg width="19" height="19" viewBox="0 0 19 19" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<circle fill="#0944B3" cx="9.5" cy="9.5" r="9.5"/>
<g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="m5.188 5.187 8.625 8.625M13.813 5.187l-8.625 8.625"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@ -0,0 +1,9 @@
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M0 0h18v18H0z"/>
<g transform="translate(1 1)" stroke="#348CFD" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<circle cx="5.565" cy="5.565" r="5.565"/>
<path d="M9.5 9.5 16 16"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 402 B

View File

@ -53,12 +53,14 @@ import checkboxUnchecked from './../../assets/icons/checkbox-unchecked.svg';
import iconAlertOutline from './../../assets/icons/icons-alert-outline.svg'; import iconAlertOutline from './../../assets/icons/icons-alert-outline.svg';
import iconAlertSmall from './../../assets/icons/icon-alert-small.svg'; import iconAlertSmall from './../../assets/icons/icon-alert-small.svg';
import iconClose from './../../assets/icons/icon-close.svg'; import iconClose from './../../assets/icons/icon-close.svg';
import iconClearField from './../../assets/icons/icon-clear-field.svg';
import iconNextInactive from './../../assets/icons/icon-next-inactive.svg'; import iconNextInactive from './../../assets/icons/icon-next-inactive.svg';
import iconNext from './../../assets/icons/icon-next.svg'; import iconNext from './../../assets/icons/icon-next.svg';
import iconPlay from './../../assets/icons/icon-play.svg'; import iconPlay from './../../assets/icons/icon-play.svg';
import iconPause from './../../assets/icons/icon-pause.svg'; import iconPause from './../../assets/icons/icon-pause.svg';
import iconPrevInactive from './../../assets/icons/icon-prev-inactive.svg'; import iconPrevInactive from './../../assets/icons/icon-prev-inactive.svg';
import iconPrev from './../../assets/icons/icon-prev.svg'; import iconPrev from './../../assets/icons/icon-prev.svg';
import iconSearch from './../../assets/icons/icon-search.svg';
import iconStatusAlert from './../../assets/icons/icon-status-alert.svg'; import iconStatusAlert from './../../assets/icons/icon-status-alert.svg';
import iconTransferring from './../../assets/icons/icon-transferring.svg'; import iconTransferring from './../../assets/icons/icon-transferring.svg';
import iconUpload from './../../assets/icons/icon-upload.svg'; import iconUpload from './../../assets/icons/icon-upload.svg';
@ -151,9 +153,11 @@ const ICONS = {
info: info, info: info,
'icon-alert-outline': iconAlertOutline, 'icon-alert-outline': iconAlertOutline,
'icon-alert-small': iconAlertSmall, 'icon-alert-small': iconAlertSmall,
'icon-clear-field': iconClearField,
'icon-close': iconClose, 'icon-close': iconClose,
'icon-play': iconPlay, 'icon-play': iconPlay,
'icon-pause': iconPause, 'icon-pause': iconPause,
'icon-search': iconSearch,
'icon-status-alert': iconStatusAlert, 'icon-status-alert': iconStatusAlert,
'icon-transferring': iconTransferring, 'icon-transferring': iconTransferring,
'info-action': infoAction, 'info-action': infoAction,

View File

@ -5,8 +5,8 @@ input[type='range'] {
input[type='range']::-webkit-slider-thumb { input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
border: none; border: none;
height: 10px; height: 13px;
width: 10px; width: 13px;
border-radius: 50%; border-radius: 50%;
background: #5acce6; background: #5acce6;
} }
@ -14,8 +14,8 @@ input[type='range']::-webkit-slider-thumb {
input[type='range']::-moz-range-thumb { input[type='range']::-moz-range-thumb {
-webkit-appearance: none; -webkit-appearance: none;
border: none; border: none;
height: 10px; height: 13px;
width: 10px; width: 13px;
border-radius: 50%; border-radius: 50%;
background: #5acce6; background: #5acce6;
} }

View File

@ -23,7 +23,9 @@ const InputRange: React.FC<{
inputClassName?: string; inputClassName?: string;
labelClassName?: string; labelClassName?: string;
labelVariant?: string; labelVariant?: string;
showLabel: boolean; showLabel?: boolean;
labelPosition?: string;
trackColor?: string;
}> = ({ }> = ({
value, value,
onChange, onChange,
@ -36,6 +38,8 @@ const InputRange: React.FC<{
labelClassName, labelClassName,
labelVariant, labelVariant,
showLabel = true, showLabel = true,
labelPosition = '',
trackColor,
}) => { }) => {
const [rangeValue, setRangeValue] = useState(value); const [rangeValue, setRangeValue] = useState(value);
@ -63,6 +67,16 @@ const InputRange: React.FC<{
containerClassName ? containerClassName : '' containerClassName ? containerClassName : ''
}`} }`}
> >
{showLabel && labelPosition === 'left' && (
<Typography
variant={labelVariant ?? 'subtitle'}
component="p"
className={classNames('w-8', labelClassName ?? 'text-white')}
>
{rangeValueForStr}
{unit}
</Typography>
)}
<input <input
type="range" type="range"
min={minValue} min={minValue}
@ -72,14 +86,16 @@ const InputRange: React.FC<{
inputClassName ? inputClassName : '' inputClassName ? inputClassName : ''
}`} }`}
style={{ style={{
background: `linear-gradient(to right, #5acce6 0%, #5acce6 ${rangeValuePercentage - background:
10}%, #3a3f99 ${rangeValuePercentage + 10}%, #3a3f99 100%)`, trackColor ||
`linear-gradient(to right, #5acce6 0%, #5acce6 ${rangeValuePercentage -
10}%, #3a3f99 ${rangeValuePercentage + 10}%, #3a3f99 100%)`,
}} }}
onChange={handleChange} onChange={handleChange}
id="myRange" id="myRange"
step={step} step={step}
/> />
{showLabel && ( {showLabel && (!labelPosition || labelPosition === 'right') && (
<Typography <Typography
variant={labelVariant ?? 'subtitle'} variant={labelVariant ?? 'subtitle'}
component="p" component="p"

View File

@ -39,7 +39,7 @@ module.exports = {
main: '#3a3f99', main: '#3a3f99',
disabled: '#2b166b', disabled: '#2b166b',
focus: '#5acce6', focus: '#5acce6',
placeholder: '#39383f' placeholder: '#39383f',
}, },
secondary: { secondary: {

View File

@ -50,7 +50,7 @@ module.exports = {
main: '#3a3f99', main: '#3a3f99',
disabled: '#2b166b', disabled: '#2b166b',
focus: '#5acce6', focus: '#5acce6',
placeholder: '#39383f' placeholder: '#39383f',
}, },
secondary: { secondary: {

View File

@ -1302,7 +1302,7 @@
core-js-pure "^3.25.1" core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11" regenerator-runtime "^0.13.11"
"@babel/runtime@7.17.9", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": "@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.21.0" version "7.21.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@ -13975,7 +13975,7 @@ memfs@^3.1.2, memfs@^3.4.1, memfs@^3.4.3:
dependencies: dependencies:
fs-monkey "^1.0.3" fs-monkey "^1.0.3"
memoize-one@^5.0.0: "memoize-one@>=3.1.1 <6", memoize-one@^5.0.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
@ -17518,6 +17518,14 @@ react-waypoint@^10.3.0:
prop-types "^15.0.0" prop-types "^15.0.0"
react-is "^17.0.1 || ^18.0.0" react-is "^17.0.1 || ^18.0.0"
react-window@^1.8.9:
version "1.8.9"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.9.tgz#24bc346be73d0468cdf91998aac94e32bc7fa6a8"
integrity sha512-+Eqx/fj1Aa5WnhRfj9dJg4VYATGwIUP2ItwItiJ6zboKWA6EX3lYDAXfGF2hyNqplEprhbtjbipiADEcwQ823Q==
dependencies:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
react-with-direction@^1.3.1: react-with-direction@^1.3.1:
version "1.4.0" version "1.4.0"
resolved "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.4.0.tgz#ebdf64d685d0650ce966e872e6431ad5a2485444" resolved "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.4.0.tgz#ebdf64d685d0650ce966e872e6431ad5a2485444"