Skip to content

Commit

Permalink
fix(slider): snap to mark (#200)
Browse files Browse the repository at this point in the history
Co-authored-by: Samuel Andersson <[email protected]>
  • Loading branch information
merlingson and trew authored Feb 15, 2024
1 parent 7cd90f8 commit a391d57
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 7 deletions.
28 changes: 28 additions & 0 deletions components/slider/src/range-slider.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,34 @@ describe("range-slider", () => {

expect(onChangeCommitted).toHaveBeenCalledWith({ value: [40, 100] });
});

it("should snap to closest mark", () => {
const onChange = vi.fn();
const onChangeCommitted = vi.fn();
const { getByTestId } = render(
<RangeSlider
title="Range Slider"
min={0}
max={100}
onChange={onChange}
onChangeCommitted={onChangeCommitted}
marks={[
{ value: 20, label: <span data-testid="mark">20</span> },
{ value: 40, label: "40" },
{ value: 60, label: "60" },
]}
data-testid="slider-root"
/>
);

const mark: HTMLElement = getByTestId("mark");
getControlRoot(getByTestId("slider-root"));
fireEvent.mouseDown(mark, { button: 0, clientX: 29 });
fireEvent.mouseUp(mark, { button: 0, clientX: 29 });

expect(onChange).toHaveBeenCalledWith({ value: [20] });
expect(onChangeCommitted).toHaveBeenCalledWith({ value: [20] });
});
});

describe("keyboard interaction", () => {
Expand Down
36 changes: 29 additions & 7 deletions components/slider/src/use-range-slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { ThumbProps } from "./thumb/thumb.types";
import { toPercent } from "./utils";
import { MarkLabelProps } from "./mark/label/mark-label.types";
import { MarkLabel } from "./mark/label/mark-label";
import { sliderClassNames } from "./use-slider-styles";

const asc = (a: number, b: number): number => a - b;

Expand Down Expand Up @@ -80,6 +81,19 @@ const findClosest = (value: number, candidates: number[]) => {
return { value: closest.value, index: closest.index };
};

const isMarkLabelElement = (target: EventTarget | null): boolean => {
if (
!target || !(target instanceof Element)
|| target.classList.contains(sliderClassNames.root)
) {
return false;
}
if (target.classList.contains(sliderClassNames.mark.label)) {
return true;
}
return isMarkLabelElement(target.parentNode);
};

export const useRangeSlider_unstable = (
props: RangeSliderProps,
ref: React.Ref<HTMLElement>
Expand Down Expand Up @@ -165,17 +179,20 @@ export const useRangeSlider_unstable = (
const controlRef = useFocusWithin<HTMLSpanElement>();

const snapValueToClosest = useCallback(
(value: number) => {
(value: number, snapToMarkValue: boolean) => {
if (snapValues) {
return findClosest(value, snapValues).value;
}
if (snapToMarkValue) {
return findClosest(value, markValues).value;
}
return value;
},
[snapValues]
[snapValues, markValues]
);

const getNewValue = useCallback(
(clientX: number): number => {
(clientX: number, snapToMarkValue: boolean): number => {
const clientRect = controlRef.current?.getBoundingClientRect() as DOMRect;
let relativePosition = clamp(
(clientX - clientRect.x) / clientRect.width,
Expand All @@ -188,7 +205,8 @@ export const useRangeSlider_unstable = (
}

return snapValueToClosest(
Math.round(relativePosition * (max - min) + min)
Math.round(relativePosition * (max - min) + min),
snapToMarkValue
);
},
[controlRef, dir, max, min, snapValueToClosest]
Expand All @@ -210,7 +228,8 @@ export const useRangeSlider_unstable = (
if (e.button !== 0) {
return;
}
const newValue = getNewValue(e.clientX);

const newValue = getNewValue(e.clientX, isMarkLabelElement(e.target));

// avoid text selection
e.preventDefault();
Expand Down Expand Up @@ -245,7 +264,7 @@ export const useRangeSlider_unstable = (
return;
}

const wantedValue = getNewValue(clientX);
const wantedValue = getNewValue(clientX, false);
const previousValue = wantedValue;
const newValues = internalValues.slice();
newValues[active] = wantedValue;
Expand Down Expand Up @@ -291,7 +310,10 @@ export const useRangeSlider_unstable = (
touchId.current = touch.identifier;
}

const newValue = getNewValue(touch.clientX);
const newValue = getNewValue(
touch.clientX,
isMarkLabelElement(event.target)
);
const closestIndex = findClosest(newValue, internalValues).index;

const newValues = internalValues.slice();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RangeSlider } from "@axiscommunications/fluent-slider";
import React from "react";

export function WithStepsAndMarksRangeSliderExample() {
return (
<RangeSlider
min={0}
max={100}
title="Range Slider with steps and marks"
defaultValue={[50, 75]}
marks={[
{ value: 50, label: "50" },
{ value: 75, label: "75" },
]}
step={25}
/>
);
}

export const WithStepsAndMarksRangeSliderExampleAsString = `
import { RangeSlider } from "@axiscommunications/fluent-slider";
import React from "react";
export function WithStepsAndMarksRangeSliderExample() {
return (
<RangeSlider
min={0}
max={100}
title="Range Slider with steps and marks"
defaultValue={[50, 75]}
marks={[
{ value: 50, label: "50" },
{ value: 75, label: "75" },
]}
step={25}
/>
)
}
`;
10 changes: 10 additions & 0 deletions examples/src/stories/slider/slider-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ import {
WithStepsSliderExample,
WithStepsSliderExampleAsString,
} from "./examples/with-steps-example";
import {
WithStepsAndMarksRangeSliderExample,
WithStepsAndMarksRangeSliderExampleAsString,
} from "./examples/range-slider-with-streps-and-marks-example";

const useStyles = makeStyles({
example: {
Expand Down Expand Up @@ -89,6 +93,12 @@ const examples: pageData[] = [
example: <WithStepsSliderExample />,
codeString: WithStepsSliderExampleAsString,
},
{
title: "With steps and marks",
anchor: "WithStepsAndMarksSliderExample",
example: <WithStepsAndMarksRangeSliderExample />,
codeString: WithStepsAndMarksRangeSliderExampleAsString,
},
{
title: "Stepping to marks",
anchor: "SteppingToMarksSliderExample",
Expand Down

0 comments on commit a391d57

Please sign in to comment.