Skip to content

Commit

Permalink
feat: add range slider with custom scale
Browse files Browse the repository at this point in the history
  • Loading branch information
AdzicMilos committed Feb 9, 2024
1 parent 550bb84 commit 88812c6
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 120 deletions.
31 changes: 29 additions & 2 deletions src/components/rangeFilterInputWithSlider/index.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ return (
</Box>
); };

## Overview
## With histograms

<Canvas>
<Story
name="Overview"
name="With histograms"
args={{
unit: 'CHF',
from: {
Expand Down Expand Up @@ -133,3 +133,30 @@ return (
{Template.bind({})}
</Story>
</Canvas>

## With custom range slider scale

<Canvas>
<Story
name="With custom range slider scale"
args={{
unit: 'CHF',
from: {
name: 'priceFrom',
placeholder: '0',
value: 100,
},
to: {
name: 'priceTo',
placeholder: '1000000+',
value: 10000,
},
rangeSliderScale: [
0, 100, 200, 1000, 2000, 5000, 10000, 30000, 60000, 300000, 1000000,
90000, 200000,
],
}}
>
{Template.bind({})}
</Story>
</Canvas>
41 changes: 30 additions & 11 deletions src/components/rangeFilterInputWithSlider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';

import RangeSliderWithScale from '../rangeSlider/RangeSliderWithScale';
import RangeSliderWithChart, {
Facet,
NumericMinMaxValue,
Expand All @@ -21,8 +22,16 @@ type ChangeRangeInputWithSliderCallback<Name> = {
changeType?: 'inputfield' | 'slider';
} & ChangeCallback<Name>;

type RangeSliderProps = {
facets?: Array<Facet>;
rangeSliderScale?: Array<number>;
chartHeight?: string;
} & (
| { facets: Array<Facet>; chartHeight?: string; rangeSliderScale?: never }
| { rangeSliderScale: Array<number>; facets?: never; chartHeight?: never }
);

export type Props<NameFrom, NameTo> = {
facets: Array<Facet>;
from: RangeFilterInputField<NameFrom>;
onChange: (
event: ChangeRangeInputWithSliderCallback<NameFrom | NameTo>,
Expand All @@ -32,13 +41,14 @@ export type Props<NameFrom, NameTo> = {
) => void;
to: RangeFilterInputField<NameTo>;
unit?: string;
chartHeight?: string;
} & PickedNumberInputProps;
} & RangeSliderProps &
PickedNumberInputProps;

function RangeFilterInputWithSlider<
NameFrom extends string,
NameTo extends string,
>({
rangeSliderScale,
facets,
unit,
onChange,
Expand Down Expand Up @@ -107,14 +117,23 @@ function RangeFilterInputWithSlider<
return (
<Flex direction="column">
<Box order={{ base: 1, sm: 0 }} px="md" py={{ base: 'md', sm: 0 }}>
<RangeSliderWithChart
onSliderChange={handleSliderChange}
onSliderRelease={handleSliderRelease}
selection={appliedValue()}
facets={facets}
chartHeight={chartHeight}
{...rest}
/>
{facets ? (
<RangeSliderWithChart
onSliderChange={handleSliderChange}
onSliderRelease={handleSliderRelease}
selection={appliedValue()}
facets={facets}
chartHeight={chartHeight}
/>
) : null}
{rangeSliderScale ? (
<RangeSliderWithScale
onSliderChange={handleSliderChange}
onSliderRelease={handleSliderRelease}
selection={appliedValue()}
scale={rangeSliderScale}
/>
) : null}
</Box>
<RangeFilterInput
from={{
Expand Down
120 changes: 13 additions & 107 deletions src/components/rangeSlider/RangeSliderWithChart.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useState } from 'react';
import React from 'react';

import Box from '../box';
import RangeSliderWithScale from './RangeSliderWithScale';
import Chart from './Chart';

import RangeSlider from './';

export type NumericMinMaxValue = {
min: number | null | undefined;
max: number | null | undefined;
Expand Down Expand Up @@ -36,114 +35,21 @@ const RangeSliderWithChart: React.FC<RangeSliderWithChartProps> = ({
onSliderRelease,
chartHeight = '3xl',
}) => {
const [startRange, setStartRange] = useState<number[] | null>(null);

const sortedFacetsByFromKey = facets.sort((a, b) => a.from - b.from);

const scale = sortedFacetsByFromKey.map(({ from }) => from);

const toIndex = (value: number) => {
const closestValue = scale.reduce((prev, curr) => {
return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
}, 0);

return scale.indexOf(closestValue);
};

const toValue = (index: number) => {
if (index === scale.length) {
return null;
}

return scale[index];
};

const toMinMax = (
minIndex: number,
maxIndex: number,
previousSelection: NumericMinMaxValue,
): NumericMinMaxValue => ({
min: minIndex ? toValue(minIndex) : null,
max: maxIndex ? toValue(maxIndex) : previousSelection.max,
});

const toRange = ({ min, max }: NumericMinMaxValue) => {
const maxValue = max ? toIndex(max) : scale.length;
const minValue = min ? toIndex(min) : 0;

const range: number[] = [minValue, maxValue];
const sortedRange = [...range].sort((a, b) => a - b);

return JSON.stringify(range) === JSON.stringify(sortedRange)
? range
: [minValue, minValue];
};

const getChangedThumb = (
initial: number[],
current: number[],
): 'max' | 'min' | null => {
const [initialMinIndex, initialMaxIndex] = initial;
const [currentMinIndex, currentMaxIndex] = current;
if (
initialMinIndex === currentMinIndex &&
initialMaxIndex === currentMaxIndex
) {
return null;
}

return initialMinIndex !== currentMinIndex ? 'min' : 'max';
};

const getChangeEvent = ([
newMinIndex,
newMaxIndex,
]: number[]): ChangeCallback | null => {
const changedThumb = getChangedThumb(
startRange ? startRange : toRange(selection),
[newMinIndex, newMaxIndex],
);

if (!changedThumb) return null;

return {
touched: changedThumb,
value: toMinMax(newMinIndex, newMaxIndex, selection),
};
};

const handleChange = (
newValues: number[],
callback: (event: ChangeCallback) => void,
) => {
const changeEvent = getChangeEvent(newValues);
if (changeEvent) {
callback(changeEvent);
}
};

return (
<>
<Box position="relative" top="sm" h={chartHeight}>
<Chart range={toRange(selection)} facets={sortedFacetsByFromKey} />
</Box>
<RangeSlider
h="sm"
step={1}
min={0}
max={scale.length}
onChange={(newValues) => handleChange(newValues, onSliderChange)}
onChangeEnd={(newValues) => {
const callback = (event: ChangeCallback) => {
onSliderRelease(event);
setStartRange(newValues);
};
handleChange(newValues, callback);
}}
onChangeStart={setStartRange}
value={toRange(selection)}
/>
</>
<RangeSliderWithScale
onSliderChange={onSliderChange}
onSliderRelease={onSliderRelease}
selection={selection}
scale={scale}
renderChart={(range: Array<number>) => (
<Box position="relative" top="sm" h={chartHeight}>
<Chart range={range} facets={sortedFacetsByFromKey} />
</Box>
)}
/>
);
};

Expand Down
Loading

0 comments on commit 88812c6

Please sign in to comment.