Skip to content

Commit

Permalink
[WIP] Slider range (#3451)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeac123 authored May 28, 2024
1 parent 9ac5022 commit 363b462
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 82 deletions.
20 changes: 19 additions & 1 deletion packages/lab/src/__tests__/__e2e__/slider/Slider.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe("Given a Slider", () => {
cy.findByRole("slider")
.parent()
.trigger("mousemove", { clientX: 50, clientY: 50 });
cy.get("@changeSpy").should("have.callCount", 2);
cy.get("@changeSpy").should("have.callCount", 1);
});

it("THEN it should respond to keyboard navigation", () => {
Expand Down Expand Up @@ -76,4 +76,22 @@ describe("Given a Slider", () => {
cy.get(".saltSliderThumb-tooltip").should("not.be.visible");
});
});

describe("Given a Slider with a range value", () => {
it("THEN it should have ARIA roles and attributes", () => {
cy.mount(
<Slider min={-100} max={100} step={10} defaultValue={[20, 40]} />
);

cy.findAllByRole("slider").should("have.length", 2);

cy.findAllByRole("slider")
.eq(0)
.should("have.attr", "aria-valuenow", "20");

cy.findAllByRole("slider")
.eq(1)
.should("have.attr", "aria-valuenow", "40");
});
});
});
5 changes: 5 additions & 0 deletions packages/lab/src/slider/Slider.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
display: grid;
gap: var(--salt-spacing-100);
grid-template-columns: auto 1fr auto;
user-select: none;
}

.saltSlider-bottomLabel {
Expand Down Expand Up @@ -90,6 +91,10 @@
position: absolute;
}

.saltSliderSelection-range {
align-items: center;
}

.saltSliderMarks {
position: relative;
}
Expand Down
16 changes: 10 additions & 6 deletions packages/lab/src/slider/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useComponentCssInjection } from "@salt-ds/styles";
import { SliderTrack, SliderMarks, SliderContext } from "./internal";

import sliderCss from "./Slider.css";
import { SliderChangeHandler } from "./types";
import { SliderChangeHandler, SliderValue } from "./types";

const withBaseName = makePrefixer("saltSlider");

Expand All @@ -31,15 +31,15 @@ export interface SliderProps
/**
* Initial value of the slider
*/
defaultValue?: number;
defaultValue?: SliderValue;
/**
* The markings the slider is displayed with
*/
marks?: "inline" | "bottom" | "all";
/**
* Value of the slider, to be used when in a controlled state
*/
value?: number;
value?: SliderValue;
/**
* Change handler to be used when in a controlled state
*/
Expand Down Expand Up @@ -68,22 +68,26 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(function Slider(
window: targetWindow,
});

const [value, setValue] = useControlled<number>({
const [value, setValue] = useControlled<SliderValue>({
controlled: valueProp,
default: defaultValue,
name: "Slider",
state: "Value",
});

const handleSliderChange = (value: SliderValue) => {
setValue(value);
onChange?.(value);
};

return (
<SliderContext.Provider
value={{
value,
min,
max,
step,
setValue,
onChange,
onChange: handleSliderChange,
ariaLabel,
}}
>
Expand Down
1 change: 1 addition & 0 deletions packages/lab/src/slider/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./Slider";
export * from "./types";
4 changes: 1 addition & 3 deletions packages/lab/src/slider/internal/SliderContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ export interface SliderContextValue {
max: number;
step: number;
value: SliderValue;
setValue: SliderChangeHandler;
onChange: SliderChangeHandler | undefined;
onChange: SliderChangeHandler;
ariaLabel: string | undefined;
}

Expand All @@ -15,7 +14,6 @@ export const SliderContext = createContext<SliderContextValue>({
max: 10,
step: 1,
value: 0,
setValue: () => null,
onChange: () => null,
ariaLabel: "slider",
});
Expand Down
24 changes: 20 additions & 4 deletions packages/lab/src/slider/internal/SliderSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { makePrefixer } from "@salt-ds/core";
import { ComponentPropsWithoutRef } from "react";
import { getPercentage } from "./utils";
import {
getPercentage,
getPercentageDifference,
getPercentageOffset,
} from "./utils";
import { useSliderContext } from "./SliderContext";
import { clsx } from "clsx";

const withBaseName = makePrefixer("saltSliderSelection");

Expand All @@ -12,12 +17,23 @@ export function SliderSelection({
}: SliderSelectionProps): JSX.Element {
const { min, max, value } = useSliderContext();

const percentage = getPercentage(min, max, value);
const percentageDifference = Array.isArray(value)
? getPercentageDifference(min, max, value)
: getPercentage(min, max, value);

const percentageOffset = Array.isArray(value)
? getPercentageOffset(min, max, value)
: 0;

return (
<div
className={withBaseName()}
style={{ width: `${percentage}` }}
className={clsx(withBaseName(), {
[withBaseName("range")]: Array.isArray(value),
})}
style={{
width: percentageDifference,
left: percentageOffset,
}}
{...props}
/>
);
Expand Down
33 changes: 21 additions & 12 deletions packages/lab/src/slider/internal/SliderThumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,33 @@ const withBaseName = makePrefixer("saltSliderThumb");

export interface SliderThumbProps extends ComponentPropsWithoutRef<"div"> {
trackRef: RefObject<HTMLDivElement>;
index: number;
}

export function SliderThumb(props: SliderThumbProps): JSX.Element {
const { trackRef, ...rest } = props;
const { trackRef, index, ...rest } = props;

const { min, max, step, value, setValue, onChange, ariaLabel } =
useSliderContext();
const { min, max, step, value, onChange, ariaLabel } = useSliderContext();

const onKeyDown = useKeyDownThumb(min, max, step, value, setValue, onChange);
const onKeyDown = useKeyDownThumb(min, max, step, value, onChange, index);

const { thumbProps, thumbFocus } = useMouseDownThumb(
console.log(index);

const { thumbProps, tooltipVisible } = useMouseDownThumb(
trackRef,
min,
max,
step,
value,
setValue,
onChange
onChange,
index
);

const percentage = getPercentage(min, max, value);
const percentage = Array.isArray(value)
? index
? getPercentage(min, max, value[1])
: getPercentage(min, max, value[0])
: getPercentage(min, max, value);

return (
<div
Expand All @@ -40,19 +46,22 @@ export function SliderThumb(props: SliderThumbProps): JSX.Element {
>
<div
className={clsx(withBaseName("tooltip"), {
[withBaseName("showTooltip")]: !thumbFocus,
[withBaseName("showTooltip")]: !tooltipVisible,
})}
aria-expanded={thumbFocus}
aria-expanded={tooltipVisible}
>
<Label>{value}</Label>
{Array.isArray(value) && <Label>{index ? value[1] : value[0]}</Label>}
{!Array.isArray(value) && <Label>{value}</Label>}
</div>
<div
className={withBaseName()}
onKeyDown={onKeyDown}
role="slider"
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
aria-valuenow={
Array.isArray(value) ? (index === 0 ? value[0] : value[1]) : value
}
aria-label={ariaLabel}
aria-orientation="horizontal"
tabIndex={0}
Expand Down
10 changes: 7 additions & 3 deletions packages/lab/src/slider/internal/SliderTrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface SliderTrackProps extends ComponentPropsWithoutRef<"div"> {}
const withBaseName = makePrefixer("saltSliderTrack");

export const SliderTrack = ({ ...props }: SliderTrackProps) => {
const { min, max, step, setValue, onChange } = useSliderContext();
const { min, max, step, value, onChange } = useSliderContext();

const trackRef = useRef<HTMLDivElement>(null);

Expand All @@ -20,15 +20,19 @@ export const SliderTrack = ({ ...props }: SliderTrackProps) => {
min,
max,
step,
setValue,
value,
onChange
);

const thumbs = Array.isArray(value) ? value : [value];

return (
<div className={withBaseName()} ref={trackRef} {...trackProps} {...props}>
<div className={withBaseName("rail")} />
<SliderSelection />
<SliderThumb trackRef={trackRef} />
{thumbs.map((value, i) => {
return <SliderThumb key={i} index={i} trackRef={trackRef} />;
})}
</div>
);
};
20 changes: 14 additions & 6 deletions packages/lab/src/slider/internal/useKeyDownThumb.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { SliderChangeHandler, SliderValue } from "../types";
import { roundToStep, roundToTwoDp } from "./utils";
import { clampValue, roundToStep, roundToTwoDp, setRangeValue } from "./utils";

export function useKeyDownThumb(
min: number,
max: number,
step: number,
value: SliderValue,
setValue: SliderChangeHandler,
onChange: SliderChangeHandler | undefined
onChange: SliderChangeHandler,
index: number
) {
return (event: React.KeyboardEvent) => {
let valueItem: number = value;
// event.preventDefault();
let valueItem: number = Array.isArray(value)
? index
? value[1]
: value[0]
: value;
switch (event.key) {
case "Home":
valueItem = min;
Expand All @@ -31,7 +36,10 @@ export function useKeyDownThumb(
}
valueItem = roundToStep(valueItem, step);
valueItem = roundToTwoDp(valueItem);
setValue(valueItem);
onChange?.(valueItem);
valueItem = clampValue(valueItem, min, max);

Array.isArray(value)
? setRangeValue(value, valueItem, onChange, index, step)
: onChange?.(valueItem);
};
}
Loading

0 comments on commit 363b462

Please sign in to comment.