Skip to content

Commit

Permalink
RC #291 - Refactoring FacetSlider and FacetTimeline to always require…
Browse files Browse the repository at this point in the history
… being controlled
  • Loading branch information
dleadbetter committed Aug 13, 2024
1 parent b036c37 commit 2986ca4
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 231 deletions.
249 changes: 107 additions & 142 deletions packages/core-data/src/components/FacetSlider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ import { useTimer } from '@performant-software/shared-components';
import * as Slider from '@radix-ui/react-slider';
import * as Tooltip from '@radix-ui/react-tooltip';
import { clsx } from 'clsx';
import {
ChevronLeft,
ChevronRight,
RotateCcw,
ZoomIn,
ZoomOut
} from 'lucide-react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import React, { useCallback, useEffect, useState } from 'react';
import _ from 'underscore';

type MarkerProps = {
className?: string,
position: 'top' | 'bottom' | 'left' | 'right',
value: number
};

Expand Down Expand Up @@ -73,6 +68,7 @@ const SliderMarker = (props: MarkerProps) => {
/>
</Tooltip.Trigger>
<Tooltip.Content
side={props.position}
sideOffset={5}
>
<div
Expand All @@ -89,6 +85,10 @@ const SliderMarker = (props: MarkerProps) => {
);
};

SliderMarker.defaultProps = {
position: 'top'
};

type Action = {
/**
* Class name to apply to the button element.
Expand Down Expand Up @@ -134,130 +134,68 @@ type Props = {
/**
* The maximum facet value.
*/
defaultMax: number,
max?: number,

/**
* The minimum facet value.
*/
defaultMin: number,
min?: number,

/**
* Callback fired when the range is changed.
*/
onChange?: ([[number, number], [number, number]]) => void,
onValueChange: (value: [number, number]) => void,

onValueCommit?: (value: [number, number]) => void,

/**
* Position of the value tooltip marker.
*/
position?: 'top' | 'bottom' | 'left' | 'right',

/**
* Number of steps to increment the slider.
*/
step?: number,

/**
* Zoom in/out increment.
* Value for controlled input.
*/
zoom?: number
value: [number, number]
};

const FacetSlider = (props: Props) => {
const [min, setMin] = useState(props.defaultMin);
const [max, setMax] = useState(props.defaultMax);
const [range, setRange] = useState([props.defaultMin, props.defaultMax]);

/**
* Callback fired when the left button is clicked. This function decrements the min range value by the "step" prop.
*
* @type {(function(): void)|*}
*/
const onLeft = useCallback(() => {
const [start, end] = range;
const [start, end] = props.value;

let newStart = start - props.step;
if (newStart < min) {
newStart = min;
if (newStart < props.min) {
newStart = props.min;
}

setRange([newStart, end]);
}, [min, range, props.step]);
props.onValueChange([newStart, end]);
}, [props.min, props.onValueChange, props.step, props.value]);

/**
* Callback fired when the right button is clicked. This function increments the max range value by the "step" prop.
*
* @type {(function(): void)|*}
*/
const onRight = useCallback(() => {
const [start, end] = range;
const [start, end] = props.value;

let newEnd = end + props.step;
if (newEnd > max) {
newEnd = max;
}

setRange([start, newEnd]);
}, [max, range, props.step]);

/**
* Zooms in the min/max values.
*
* @type {(function(): void)|*}
*/
const onZoomIn = useCallback(() => {
const newRange = [...range];

const newMin = min + props.zoom;
const newMax = max - props.zoom;

if (newMin >= newMax) {
return;
}

setMin(newMin);
setMax(newMax);

if (newMin > newRange[0]) {
newRange[0] = newMin;
}

if (newMax < newRange[1]) {
newRange[1] = newMax;
if (newEnd > props.max) {
newEnd = props.max;
}

setRange(newRange);
}, [max, min, range, props.zoom]);

/**
* Zooms out the min/max values.
*
* @type {(function(): void)|*}
*/
const onZoomOut = useCallback(() => {
const newMin = min - props.zoom;
const newMax = max + props.zoom;

if (newMin >= newMax) {
return;
}

setMin(newMin);
setMax(newMax);
}, [max, min, range, props.zoom]);

/**
* Resets the min/max values to the defaults.
*
* @type {(function(): void)|*}
*/
const onZoomReset = useCallback(() => {
setMin(props.defaultMin);
setMax(props.defaultMax);
}, [props.defaultMax, props.defaultMin]);

/**
* Calls the onChange prop when the range value changes.
*/
useEffect(() => {
if (props.onChange) {
props.onChange(range, [min, max]);
}
}, [max, min, range]);
props.onValueChange([start, newEnd]);
}, [props.max, props.onValueChange, props.step, props.value]);

return (
<>
Expand All @@ -277,12 +215,13 @@ const FacetSlider = (props: Props) => {
'relative flex flex-grow h-5 touch-none items-center w-full',
props.classNames.root
)}
max={max}
min={min}
max={props.max}
min={props.min}
minStepsBetweenThumbs={1}
onValueChange={setRange}
onValueChange={props.onValueChange}
onValueCommit={props.onValueCommit}
step={1}
value={range}
value={props.value}
>
<Slider.Track
className={clsx(
Expand All @@ -299,11 +238,13 @@ const FacetSlider = (props: Props) => {
</Slider.Track>
<SliderMarker
className={props.classNames.thumb}
value={range[0]}
position={props.position}
value={props.value[0]}
/>
<SliderMarker
className={props.classNames.thumb}
value={range[1]}
position={props.position}
value={props.value[1]}
/>
</Slider.Root>
<button
Expand All @@ -318,67 +259,91 @@ const FacetSlider = (props: Props) => {
<div
className='flex justify-between w-full px-12'
>
<div>{ min }</div>
<div>{ max }</div>
<div>{ props.min }</div>
<div>{ props.max }</div>
</div>
{ props.zoom && (
{ !_.isEmpty(props.actions) && (
<div
className={clsx(
'flex justify-center items-center w-full py-3 text-gray-600',
props.classNames.zoom
)}
>
<button
aria-label='Zoom In'
className='p-3'
onClick={onZoomIn}
type='button'
>
<ZoomIn />
</button>
<button
aria-label='Zoom Out'
className='p-3'
onClick={onZoomOut}
type='button'
>
<ZoomOut />
</button>
<button
aria-label='Zoom Reset'
className='p-3'
onClick={onZoomReset}
type='button'
>
<RotateCcw />
</button>
{ !_.isEmpty(props.actions) && (
<>
{ _.map(props.actions, (action, index) => (
<button
aria-label={action.label}
className={clsx(
'p-3',
action.className
)}
key={index}
onClick={action.onClick}
type='button'
>
{ action.icon }
</button>
))}
</>
)}
{ _.map(props.actions, (action, index) => (
<button
aria-label={action.label}
className={clsx(
'p-3',
action.className
)}
key={index}
onClick={action.onClick}
type='button'
>
{ action.icon }
</button>
))}
</div>
)}
{/*{ props.zoom && (*/}
{/* <div*/}
{/* className={clsx(*/}
{/* 'flex justify-center items-center w-full py-3 text-gray-600',*/}
{/* props.classNames.zoom*/}
{/* )}*/}
{/* >*/}
{/* <button*/}
{/* aria-label='Zoom In'*/}
{/* className='p-3'*/}
{/* onClick={onZoomIn}*/}
{/* type='button'*/}
{/* >*/}
{/* <ZoomIn />*/}
{/* </button>*/}
{/* <button*/}
{/* aria-label='Zoom Out'*/}
{/* className='p-3'*/}
{/* onClick={onZoomOut}*/}
{/* type='button'*/}
{/* >*/}
{/* <ZoomOut />*/}
{/* </button>*/}
{/* <button*/}
{/* aria-label='Zoom Reset'*/}
{/* className='p-3'*/}
{/* onClick={onZoomReset}*/}
{/* type='button'*/}
{/* >*/}
{/* <RotateCcw />*/}
{/* </button>*/}
{/* { !_.isEmpty(props.actions) && (*/}
{/* <>*/}
{/* { _.map(props.actions, (action, index) => (*/}
{/* <button*/}
{/* aria-label={action.label}*/}
{/* className={clsx(*/}
{/* 'p-3',*/}
{/* action.className*/}
{/* )}*/}
{/* key={index}*/}
{/* onClick={action.onClick}*/}
{/* type='button'*/}
{/* >*/}
{/* { action.icon }*/}
{/* </button>*/}
{/* ))}*/}
{/* </>*/}
{/* )}*/}
{/* </div>*/}
{/*)}*/}
</>
);
};

FacetSlider.defaultProps = {
classNames: {},
step: 1,
value: []
};

export default FacetSlider;
Expand Down
Loading

0 comments on commit 2986ca4

Please sign in to comment.