Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,012 changes: 4,006 additions & 4,006 deletions package-lock.json

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions src/components/Slider/Range/Range.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Range from './Range';
import { render } from '@testing-library/react';
import React from 'react';

describe('Range', () => {
it('should match snapshot', () => {
const { asFragment } = render(<Range />);
expect(asFragment()).toMatchSnapshot();
});

it('should display different color range slider base upon color props', () => {
// primary color
const primaryRangeSlider = render(
<Range color="primary" data-testid="primary" />
).getByTestId('primary');
expect(primaryRangeSlider).toBeInTheDocument();
expect(primaryRangeSlider).toHaveClass('primary-color');

// secondary color
const secondaryRangeSlider = render(
<Range color="secondary" data-testid="secondary" />
).getByTestId('secondary');
expect(secondaryRangeSlider).toBeInTheDocument();
expect(secondaryRangeSlider).toHaveClass('secondary-color');
});

it('should display different size range slider base upon size props', () => {
// large range slider
const largeRangeSlider = render(
<Range data-testid="large" size="lg" />
).getByTestId('large');
expect(largeRangeSlider).toBeInTheDocument();
expect(largeRangeSlider).toHaveClass('custom_thumb_lg');

// small range slider
const smallRangeSlider = render(
<Range data-testid="small" size="sm" />
).getByTestId('small');
expect(smallRangeSlider).toBeInTheDocument();
expect(smallRangeSlider).toHaveClass('custom_thumb_sm');
});
});
157 changes: 157 additions & 0 deletions src/components/Slider/Range/Range.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, {
ChangeEvent,
FC,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { OnInput, SliderColor, SliderSize } from '../Slider';
import ValueLabel from '../ValueLabel/ValueLabel';

// interface for RangeSlider
interface IRangeProps {
size?: SliderSize;
color?: SliderColor;
range?: boolean;
disable?: boolean;
showValueLabel?: boolean;
sliderLeftValue?: number;
sliderRightValue?: number;
onInputLeft?: OnInput;
onInputRight?: OnInput;
}
const Range: FC<IRangeProps> = (props) => {
// destructuring props
const {
size = 'default',
color,
showValueLabel = false,
disable = false,
sliderLeftValue = 25,
sliderRightValue = 75,
onInputLeft,
onInputRight,
...rest
} = props;

// things we need it to work
const rangeRef = useRef<HTMLDivElement>(null);
const leftThumbRef = useRef<HTMLDivElement>(null);
const rightThumbRef = useRef<HTMLDivElement>(null);
const [leftInput, setLeftInput] = useState<number>(sliderLeftValue);
const [rightInput, setRightInput] = useState<number>(sliderRightValue);

// set position for thumb and range
const setLeftRightThumbAndRange = useCallback(
(direction: string, percent: number) => {
if (leftThumbRef.current && rangeRef.current && rightThumbRef.current) {
switch (direction) {
case 'left':
leftThumbRef.current.style.left = percent + '%';
rangeRef.current.style.left = percent + '%';
break;
case 'right':
rightThumbRef.current.style.right = 100 - percent + '%';
rangeRef.current.style.right = 100 - percent + '%';
break;
default:
return null;
}
}
},
[]
);

// when left thumb is moving
const handleLeftInput = (e: ChangeEvent<HTMLInputElement>) => {
e.target.value = String(Math.min(parseInt(e.target.value), rightInput - 1));
setLeftInput(+e.target.value);
let percent =
((+e.target.value - parseInt(e.target.min)) /
(parseInt(e.target.max) - parseInt(e.target.min))) *
100;
setLeftRightThumbAndRange('left', percent);
};

// when right thumb is moving
const handleRightInput = (e: ChangeEvent<HTMLInputElement>) => {
e.target.value = String(Math.max(parseInt(e.target.value), leftInput + 1));
setRightInput(+e.target.value);
let percent =
((+e.target.value - parseInt(e.target.min)) /
(parseInt(e.target.max) - parseInt(e.target.min))) *
100;
setLeftRightThumbAndRange('right', percent);
};

// since user has full control, we follow user's value, and only trigger once after mounted
useEffect(() => {
setLeftRightThumbAndRange('left', leftInput);
setLeftRightThumbAndRange('right', rightInput);
}, []);

return (
<div className="range_slider">
<div>
<input
type="range"
className={`input-left range_${size}-track range_${size}-thumb ${
!disable ? 'cursor' : 'not-allowed'
}`}
min="0"
max="100"
value={leftInput}
onChange={handleLeftInput}
disabled={disable}
onInput={onInputLeft}
/>
<input
type="range"
className={`input-right range_${size}-track range_${size}-thumb ${
!disable ? 'cursor' : 'not-allowed'
}`}
min="0"
max="100"
value={rightInput}
onChange={handleRightInput}
disabled={disable}
onInput={onInputRight}
/>
</div>
<div className={`range_custom_slider range_${size}-track`}>
<div className={`track ${color}-color_track`} />
<div className={`range ${color}-color`} ref={rangeRef} />
<div
{...rest}
className={`thumb left ${color}-color custom_thumb_${size} ${size}_left_transform`}
ref={leftThumbRef}
>
{showValueLabel && (
<ValueLabel
value={leftInput}
color={color}
size={size}
keyword="_range"
/>
)}
</div>
<div
className={`thumb right ${color}-color custom_thumb_${size} ${size}_right_transform`}
ref={rightThumbRef}
>
{showValueLabel && (
<ValueLabel
value={rightInput}
color={color}
size={size}
keyword="_range"
/>
)}
</div>
</div>
</div>
);
};

export default Range;
75 changes: 75 additions & 0 deletions src/components/Slider/Range/_Range.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.range_slider {
position: relative;
width: 300px;
@include slider-top-left(30px, 30px);
margin-bottom: 50px;
}

.range_custom_slider {
position: relative;
z-index: 1;
}

.range_custom_slider > .track {
position: absolute;
z-index: 1;
left: 0;
right: 0;
bottom: 0;
top: 0;
border-radius: 10px;
}
.range_custom_slider > .range {
position: absolute;
z-index: 2;
left: 25%;
right: 25%;
top: 0;
bottom: 0;
border-radius: 10px;
}
.range_custom_slider > .thumb {
position: absolute;
z-index: 3;
border-radius: 50%;
}

.range_custom_slider > .thumb.left {
left: 25%;
}

.range_custom_slider > .thumb.right {
right: 25%;
}

.default_left_transform{
@include range-slider-transform(-4px, -4px);
}
.lg_left_transform {
@include range-slider-transform(-5.5px, -5.5px);
}
.sm_left_transform {
@include range-slider-transform(-2.5px, -2.5px);
}

.default_right_transform{
@include range-slider-transform(4px, -4px);
}
.lg_right_transform {
@include range-slider-transform(5.5px, -5.5px);
}
.sm_right_transform {
@include range-slider-transform(2.5px, -2.5px);
}

.range_slider input[type="range"] {
position: absolute;
pointer-events: none;
background-color: transparent;
z-index: 2;
width: 100%;
}
.range_slider input[type="range"]::-webkit-slider-thumb {
pointer-events: all;
border: none;
}
57 changes: 57 additions & 0 deletions src/components/Slider/Slider.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import Slider from './Slider';

export default {
title: 'Slider',
component: Slider,
};

export const DefaultSlider = () => {
// onscroll show value
return (
<Slider sliderValue={60} onInput={(e) => console.log(e.target.value)} />
);
};

export const DiffColorSlider = () => (
<div>
<Slider />
<Slider color="secondary" />
<Slider color="disabled" disable={true} />x
</div>
);

export const DiffSizeSlider = () => (
<div>
<h1>Large</h1>
<Slider sliderSize="lg" />
<Slider sliderSize="lg" color="secondary" />
<Slider sliderSize="lg" color="disabled" disable={true} />
<br />
<br />
<h1>Small</h1>
<Slider sliderSize="sm" />
<Slider sliderSize="sm" color="secondary" />
<Slider sliderSize="sm" color="disabled" disable={true} />
</div>
);

export const ValueLabelSlider = () => <Slider showValueLabel={true} />;

export const RangeSlider = () => (
<div>
<Slider range={true} />
<Slider range={true} color="secondary" />
<Slider range={true} disable={true} />
<h1>Large</h1>
<Slider range={true} sliderSize="lg" />
<Slider range={true} sliderSize="lg" color="secondary" />
<Slider range={true} sliderSize="lg" disable={true} />
<h1>Small</h1>
<Slider range={true} sliderSize="sm" />
<Slider range={true} sliderSize="sm" color="secondary" />
<Slider range={true} sliderSize="sm" disable={true} />
<h1>Value Label</h1>
<Slider range={true} showValueLabel={true} />
</div>
);
60 changes: 60 additions & 0 deletions src/components/Slider/Slider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Slider from './Slider';
import { render } from '@testing-library/react';
import React from 'react';

describe('Slider', () => {
it('should match snapshot', () => {
const { asFragment } = render(<Slider />);
expect(asFragment()).toMatchSnapshot();
});

it('should render default slider', () => {
const wrapper = render(<Slider data-testid="test" />);
const element = wrapper.getByTestId('test');
expect(element).toBeInTheDocument();
expect(element).toHaveClass(
'slider_range range_default-track range_default-thumb'
);
});

it('should render correct slider based on size props', () => {
// large slider
const largeSlider = render(
<Slider data-testid="large-slider" sliderSize="lg" />
).queryByTestId('large-slider');
expect(largeSlider).toBeInTheDocument();
expect(largeSlider).toHaveClass('range_lg-track range_lg-thumb');
// small slider
const smallSlider = render(
<Slider data-testid="small-slider" sliderSize="sm" />
).queryByTestId('small-slider');
expect(smallSlider).toBeInTheDocument();
expect(smallSlider).toHaveClass('range_sm-track range_sm-thumb');
});

it('should render correct slider based on color props', () => {
// primary color slider
const primaryColorSlider = render(
<Slider data-testid="primary-color" />
).queryByTestId('primary-color');
expect(primaryColorSlider).toBeInTheDocument();
expect(primaryColorSlider).toHaveClass('primary-color_track');

// secondary color slider
const secondaryColorSlider = render(
<Slider data-testid="secondary-color" color="secondary" />
).queryByTestId('secondary-color');
expect(secondaryColorSlider).toBeInTheDocument();
expect(secondaryColorSlider).toHaveClass('secondary-color_track');
});

it('should render disabled slider with disabled color by passing disable props', () => {
// disabled color + disable
const disabledColorSlider = render(
<Slider data-testid="disabled-color" disable={true} />
).queryByTestId('disabled-color');
expect(disabledColorSlider).toBeInTheDocument();
expect(disabledColorSlider).toHaveClass('not-allowed');
expect(disabledColorSlider).toBeDisabled();
});
});
Loading