Skip to content

Commit

Permalink
Fix(slider): connect input and slider position, disabled input, value…
Browse files Browse the repository at this point in the history
…s style (#2219)

* fix(Slider): disabled input, input onBlur impact the slider, show labels, respect min & max

* fix(Slider Range): position of yellow range with min and max values

* refactor(Slider): rename var & refactor Slider handlers

* test(Slider): add tests for disabled prop
  • Loading branch information
RobelTekle authored Sep 13, 2023
1 parent 2db5387 commit 5fa1cea
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 33 deletions.
110 changes: 90 additions & 20 deletions packages/Slider/src/Range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,42 @@ export interface RangeOptions extends Omit<SliderOptions, 'type' | 'value' | 'on
export type RangeProps = CreateWuiProps<'div', RangeOptions>

/**
* Ensure mininum of a given value against a value `toCompare` based on a step
* Ensure minimum of a given value against a value `toCompare` based on a step
*/
const ensureMin = (value: number, toCompare: number, step: number) =>
round(Math.min(value, toCompare - 1 * step), step)
const ensureMin = ({
min,
step,
toCompare,
value,
}: {
value: number
toCompare: number
step: number
min: number
}) => {
let ensuredValue = Math.min(value, toCompare - 1 * step)
ensuredValue = Math.max(ensuredValue, min)
return round(ensuredValue, step)
}

/**
* Ensure maximum of a given value against a value `toCompare` based on a step
*/
const ensureMax = (value: number, toCompare: number, step: number) =>
round(Math.max(value, toCompare + 1 * step), step)
const ensureMax = ({
max,
step,
toCompare,
value,
}: {
value: number
toCompare: number
step: number
max: number
}) => {
let ensuredValue = Math.max(value, toCompare + 1 * step)
ensuredValue = Math.min(ensuredValue, max)
return round(ensuredValue, step)
}

/**
* @name Slider.Range
Expand Down Expand Up @@ -70,16 +96,26 @@ export const Range = forwardRef<'div', RangeProps>(
const [tooltipMaxVisible, setTooltipMaxVisible] = useState<boolean>(false)

const handleMinValue = (e: ChangeEvent<HTMLInputElement>) => {
// Prevents the min value from being above the max
const value = ensureMin(parseInt(e.target.value, 10), maxValue, step)
// Prevents the min value from being above the max value and under the min
const value = ensureMin({
value: parseInt(e.target.value, 10),
toCompare: maxValue,
step,
min,
})
setInputMinValue(value)
setMinValue(value)
e.target.value = value.toString()
}

const handleMaxValue = (e: ChangeEvent<HTMLInputElement>) => {
// Prevents the max from being below the min
const value = ensureMax(parseInt(e.target.value, 10), minValue, step)
// Prevents the max value from being below the min value and above the max
const value = ensureMax({
value: parseInt(e.target.value, 10),
toCompare: minValue,
step,
max,
})
setInputMaxValue(value)
setMaxValue(value)
e.target.value = value.toString()
Expand All @@ -92,10 +128,10 @@ export const Range = forwardRef<'div', RangeProps>(
let value = minValue

if (e.key === 'ArrowRight') {
value = ensureMin(value + step, maxValue, step)
value = ensureMin({ value: value + step, toCompare: maxValue, step, min })
}
if (e.key === 'ArrowLeft') {
value = ensureMin(value - step, maxValue, step)
value = ensureMin({ value: value - step, toCompare: maxValue, step, min })
}

setInputMinValue(value)
Expand All @@ -107,10 +143,10 @@ export const Range = forwardRef<'div', RangeProps>(
let value = maxValue

if (e.key === 'ArrowRight') {
value = ensureMax(value + step, minValue, step)
value = ensureMax({ value: value + step, toCompare: minValue, step, max })
}
if (e.key === 'ArrowLeft') {
value = ensureMax(value - step, minValue, step)
value = ensureMax({ value: value - step, toCompare: minValue, step, max })
}

setInputMaxValue(value)
Expand All @@ -122,7 +158,9 @@ export const Range = forwardRef<'div', RangeProps>(
const getPercent = useCallback(
(value: number) => {
const percent = Math.round(((value - min) / (max - min)) * 100)
return percent > max ? max : percent < min ? min : percent
if (percent < 0) return 0
if (percent > 100) return 100
return percent
},
[min, max]
)
Expand Down Expand Up @@ -172,12 +210,12 @@ export const Range = forwardRef<'div', RangeProps>(
useEffect(() => {
if (value) {
if (!isNaN(value.min) && value.min !== minValue) {
const validValue = ensureMin(value.min || min, maxValue, step)
const validValue = ensureMin({ value: value.min || min, toCompare: maxValue, step, min })
setMinValue(validValue)
setInputMinValue(validValue)
}
if (!isNaN(value.max) && value.max !== maxValue) {
const validValue = ensureMax(value.max || max, minValue, step)
const validValue = ensureMax({ value: value.max || max, toCompare: minValue, step, max })
setMaxValue(validValue)
setInputMaxValue(validValue)
}
Expand All @@ -197,8 +235,20 @@ export const Range = forwardRef<'div', RangeProps>(
{(type === 'inline' || type === 'fields') &&
(type === 'fields' ? (
<InputText
disabled={disabled}
max={maxValue}
min={min}
onBlur={() => {
const value = ensureMin({
value: inputMinValue,
toCompare: maxValue,
step,
min,
})
setInputMinValue(value)
setMinValue(value)
onChange({ min: value, max: maxValue })
}}
onChange={e => {
let value = parseInt(e.target.value, 10)
if (isNaN(value)) {
Expand All @@ -208,7 +258,12 @@ export const Range = forwardRef<'div', RangeProps>(
}}
onKeyDown={e => {
if (e.key === 'Enter') {
const value = ensureMin(inputMinValue, maxValue, step)
const value = ensureMin({
value: inputMinValue,
toCompare: maxValue,
step,
min,
})
setInputMinValue(value)
setMinValue(value)
onChange({ min: value, max: maxValue })
Expand Down Expand Up @@ -300,8 +355,20 @@ export const Range = forwardRef<'div', RangeProps>(
{(type === 'inline' || type === 'fields') &&
(type === 'fields' ? (
<InputText
disabled={disabled}
max={max}
min={minValue + 1}
onBlur={() => {
const value = ensureMax({
value: inputMaxValue,
toCompare: minValue,
step,
max,
})
setInputMaxValue(value)
setMaxValue(value)
onChange({ min: minValue, max: value })
}}
onChange={e => {
let value = parseInt(e.target.value, 10)
if (isNaN(value)) {
Expand All @@ -311,7 +378,12 @@ export const Range = forwardRef<'div', RangeProps>(
}}
onKeyDown={e => {
if (e.key === 'Enter') {
const value = ensureMax(inputMaxValue, minValue, step)
const value = ensureMax({
value: inputMaxValue,
toCompare: minValue,
step,
max,
})
setInputMaxValue(value)
setMaxValue(value)
onChange({ min: minValue, max: value })
Expand All @@ -336,5 +408,3 @@ export const Range = forwardRef<'div', RangeProps>(
)
}
)

Range.displayName = 'Range'
34 changes: 21 additions & 13 deletions packages/Slider/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
}

// Handle enter key
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleSliderKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.preventDefault()
let value = localValue

if (e.key === 'ArrowRight') {
value = value + step
value = ensureMinMax(value + step, min, max, step)
}
if (e.key === 'ArrowLeft') {
value = value - step
value = ensureMinMax(value - step, min, max, step)
}
setLocalValue(value)
setInputValue(value)
Expand All @@ -93,12 +93,16 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
setInputValue(value)
}

const handleInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleInput = () => {
const value = ensureMinMax(inputValue, min, max, step)
setInputValue(value)
setLocalValue(value)
onChange(value)
}

const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
const value = ensureMinMax(inputValue, min, max, step)
setInputValue(value)
setLocalValue(value)
onChange(value)
handleInput()
}
}

Expand Down Expand Up @@ -140,10 +144,12 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
{(type === 'inline' || type === 'left-field') &&
(type === 'left-field' ? (
<InputText
disabled={disabled}
max={max}
min={min}
onBlur={handleInput}
onChange={handleInputChange}
onKeyDown={handleInput}
onKeyDown={handleInputKeyDown}
size="sm"
type="number"
value={inputValue.toString()}
Expand All @@ -153,7 +159,7 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
<Box>{min}</Box>
))}

<Box display="flex" flexGrow="1" h={20} position="relative">
<Box display="flex" flexDirection="column" flexGrow="1" h={20} position="relative">
<S.Slider
borderSelectorColor={borderSelectorColor}
disabled={disabled}
Expand All @@ -167,7 +173,7 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
_setLocalValue(value)
setInputValue(value)
}}
onKeyDown={handleKeyDown}
onKeyDown={handleSliderKeyDown}
onMouseDown={() => {
tooltip && tooltipVisible === false && setTooltipVisible(true)
}}
Expand All @@ -187,7 +193,7 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
</S.Output>
)}
{values && (
<Box h={24} ml={10} mr={10} position="relative">
<Box h={24} ml={10} mr={10} mt={5} position="relative">
{values
.reduce((prev, acc) => (prev.includes(acc) ? prev : [...prev, acc]), [])
.filter(v => v >= min && v <= max)
Expand All @@ -204,10 +210,12 @@ export const SliderComponent = forwardRef<'div', SliderProps>(
{(type === 'inline' || type === 'right-field') &&
(type === 'right-field' ? (
<InputText
disabled={disabled}
max={max}
min={min}
onBlur={handleInput}
onChange={handleInputChange}
onKeyDown={handleInput}
onKeyDown={handleInputKeyDown}
size="sm"
type="number"
value={inputValue.toString()}
Expand Down
71 changes: 71 additions & 0 deletions packages/Slider/tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,54 @@ describe('<Slider> test', () => {

expect(slider).toMatchObject({ value: '60' })
})

test('Slider and left field should be disabled', () => {
const handleChange = jest.fn()
const value = 30
const { container } = render(
<Slider
disabled
max={100}
min={0}
onChange={handleChange}
type="left-field"
value={value}
w={100}
/>
)

/* eslint-disable @typescript-eslint/no-non-null-assertion */
const slider = container.querySelector<HTMLInputElement>('input[type="range"]')!
const numberInput = container.querySelector<HTMLInputElement>('input[type="number"]')!
/* eslint-enable @typescript-eslint/no-non-null-assertion */

expect(slider).toBeDisabled()
expect(numberInput).toBeDisabled()
})

test('Slider and right field should be disabled', () => {
const handleChange = jest.fn()
const value = 30
const { container } = render(
<Slider
disabled
max={100}
min={0}
onChange={handleChange}
type="right-field"
value={value}
w={100}
/>
)

/* eslint-disable @typescript-eslint/no-non-null-assertion */
const slider = container.querySelector<HTMLInputElement>('input[type="range"]')!
const numberInput = container.querySelector<HTMLInputElement>('input[type="number"]')!
/* eslint-enable @typescript-eslint/no-non-null-assertion */

expect(slider).toBeDisabled()
expect(numberInput).toBeDisabled()
})
})

describe('<Slider.Range> test', () => {
Expand Down Expand Up @@ -280,4 +328,27 @@ describe('<Slider.Range> test', () => {
expect(handleChange.mock.calls.length).toBe(2)
expect(handleChange.mock.calls[1][0]).toMatchObject({ min: 20, max: 30 })
})

test('Slider.Range and fields should be disabled', () => {
const handleChange = jest.fn()
const { container } = render(
<Slider.Range
disabled
max={100}
min={0}
onChange={handleChange}
step={10}
type="fields"
value={{ min: 20, max: 50 }}
/>
)
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const inputSlider = container.querySelector<HTMLInputElement>('input[type="number"]')!
const fields = container.querySelectorAll<HTMLInputElement>('input[type="number"]')!
/* eslint-enable @typescript-eslint/no-non-null-assertion */

expect(inputSlider).toBeDisabled()
expect(fields[0]).toBeDisabled()
expect(fields[1]).toBeDisabled()
})
})

0 comments on commit 5fa1cea

Please sign in to comment.