diff --git a/lib/src/slider/Slider.stories.tsx b/lib/src/slider/Slider.stories.tsx
index 773c6e326..086696dc3 100644
--- a/lib/src/slider/Slider.stories.tsx
+++ b/lib/src/slider/Slider.stories.tsx
@@ -171,7 +171,13 @@ export const Chromatic = () => (
-
+
>
);
diff --git a/lib/src/slider/Slider.test.js b/lib/src/slider/Slider.test.js
index 6fa9b4e3e..8205be8ae 100644
--- a/lib/src/slider/Slider.test.js
+++ b/lib/src/slider/Slider.test.js
@@ -4,10 +4,13 @@ import userEvent from "@testing-library/user-event";
import DxcSlider from "./Slider";
describe("Slider component tests", () => {
- test("Slider renders with correct text", () => {
- const { getByText } = render();
+ test("Slider renders with correct text and label id", () => {
+ const { getByText, getByRole } = render();
expect(getByText("0")).toBeTruthy();
expect(getByText("100")).toBeTruthy();
+ const sliderId = getByText("label").getAttribute("id");
+ expect(getByRole("slider").getAttribute("aria-labelledby")).toBe(sliderId);
+ expect(getByRole("slider").getAttribute("aria-orientation")).toBe("horizontal");
});
test("Slider renders with correct initial value when it is uncontrolled", () => {
const { getByRole } = render(
@@ -18,6 +21,19 @@ describe("Slider component tests", () => {
expect(slider.getAttribute("aria-valuenow")).toBe("30");
expect(input.value).toBe("30");
});
+ test("Slider correct limit values", () => {
+ const { getByRole, getByText } = render(
+
+ );
+ const slider = getByRole("slider");
+ expect(slider.getAttribute("aria-valuemin")).toBe("30");
+ expect(slider.getAttribute("aria-valuemax")).toBe("125");
+ userEvent.tab();
+ fireEvent.keyDown(slider, { key: "ArrowRight", code: "ArrowRight", keyCode: 39, charCode: 39 });
+ expect(slider.getAttribute("aria-valuenow")).toBe("125");
+ expect(getByText("30")).toBeTruthy();
+ expect(getByText("125")).toBeTruthy();
+ });
test("Calls correct function onChange in controlled slider", () => {
const onChange = jest.fn();
const { getByRole } = render(
@@ -44,7 +60,7 @@ describe("Slider component tests", () => {
expect(getByRole("slider").getAttribute("aria-valuenow")).toBe("25");
expect(getByRole("textbox").value).toBe("25");
});
- test("Disabled slider have disabled input", () => {
+ test("Disabled slider have disabled input and slider", () => {
const onChange = jest.fn();
const { getByRole } = render(
@@ -54,17 +70,32 @@ describe("Slider component tests", () => {
});
expect(getByRole("textbox").hasAttribute("disabled")).toBeTruthy();
expect(getByRole("textbox").value).toBe("13");
+ expect(getByRole("slider").hasAttribute("disabled")).toBeTruthy();
});
- test("Calls correct function onDragEnd", () => {
+ test("Calls correct function onDragEnd when it is uncontrolled", () => {
const onDragEnd = jest.fn();
- const { getByRole } = render(
-
- );
+ const { getByRole } = render();
+ const slider = getByRole("slider");
+ act(() => {
+ fireEvent.mouseDown(slider);
+ });
+ act(() => {
+ fireEvent.mouseUp(slider, { target: { value: 120 } });
+ });
+ expect(onDragEnd).toHaveBeenCalledWith("120");
+ });
+ test("Calls correct function onDragEnd when it is controlled", () => {
+ const onDragEnd = jest.fn();
+ const { getByRole } = render();
+ const slider = getByRole("slider");
+ act(() => {
+ fireEvent.mouseDown(slider);
+ });
act(() => {
- fireEvent.mouseDown(getByRole("slider"));
- fireEvent.mouseUp(getByRole("slider"));
+ fireEvent.mouseUp(slider, { target: { value: 120 } });
});
- expect(onDragEnd).toHaveBeenCalled();
+ expect(onDragEnd).toHaveBeenCalledWith("120");
+ expect(slider.getAttribute("aria-valuenow")).toBe("50");
});
test("Calls correct function labelFormatCallback", () => {
const labelFormatCallback = jest.fn((x) => `${x}$`);
diff --git a/lib/src/slider/Slider.tsx b/lib/src/slider/Slider.tsx
index 629cc66cb..f0f3c6a66 100644
--- a/lib/src/slider/Slider.tsx
+++ b/lib/src/slider/Slider.tsx
@@ -1,14 +1,11 @@
-// @ts-nocheck
-import React, { useState, useMemo, useContext } from "react";
-import Slider from "@material-ui/lab/Slider";
+import React, { useState, useMemo, useContext, useId } from "react";
import styled, { ThemeProvider } from "styled-components";
import DxcTextInput from "../text-input/TextInput";
import { spaces } from "../common/variables.js";
import { getMargin } from "../common/utils.js";
import useTheme from "../useTheme";
import BackgroundColorContext from "../BackgroundColorContext";
-import SliderPropsType from "./types";
-import { v4 as uuidv4 } from "uuid";
+import SliderPropsType, { Margin, Space } from "./types";
const DxcSlider = ({
label = "",
@@ -30,28 +27,59 @@ const DxcSlider = ({
size = "fillParent",
}: SliderPropsType): JSX.Element => {
const [innerValue, setInnerValue] = useState(defaultValue ?? 0);
+ const [dragging, setDragging] = useState(false);
const colorsTheme = useTheme();
const backgroundType = useContext(BackgroundColorContext);
- const [labelId] = useState(`label-${uuidv4()}`);
+ const labelId = "slider" + useId();
const minLabel = useMemo(
() => (labelFormatCallback ? labelFormatCallback(minValue) : minValue),
[labelFormatCallback, minValue]
);
+
const maxLabel = useMemo(
() => (labelFormatCallback ? labelFormatCallback(maxValue) : maxValue),
[labelFormatCallback, maxValue]
);
- const handlerSliderChange = (event, newValue) => {
- const valueToCheck = value != null && value >= 0 ? value : innerValue;
- valueToCheck !== newValue && setInnerValue(newValue);
- onChange?.(newValue);
+ const tickMarks = useMemo(() => {
+ const ticks = [];
+ const numberOfMarks = Math.floor(maxValue / step - minValue / step);
+ let index = 0;
+ const range = maxValue - minValue;
+ if (marks) {
+ while (index <= numberOfMarks) {
+ ticks.push(
+
+ );
+ index++;
+ }
+ return ticks;
+ } else {
+ return null;
+ }
+ }, [minValue, maxValue, step]);
+
+ const handleSliderChange = (event) => {
+ const valueToCheck = event.target.value;
+ (valueToCheck !== value || valueToCheck !== innerValue) && setInnerValue(valueToCheck);
+ onChange?.(valueToCheck);
+ };
+
+ const handleSliderDragging = () => {
+ setDragging(true);
};
- const handleSliderOnChangeCommited = (event, selectedValue) => {
- onDragEnd?.(selectedValue);
+ const handleSliderOnChangeCommited = (event) => {
+ if (dragging) {
+ setDragging(false);
+ onDragEnd?.(event.target.value);
+ }
};
const handlerInputChange = (event) => {
@@ -64,6 +92,8 @@ const DxcSlider = ({
}
};
+ const isFirefox = navigator.userAgent.indexOf("Firefox") !== -1;
+
return (
@@ -73,23 +103,34 @@ const DxcSlider = ({
{helperText}
-
+
{showLimitsValues && (
{minLabel}
)}
- = 0 ? value : innerValue}
- min={minValue}
- max={maxValue}
- onChange={handlerSliderChange}
- onChangeCommitted={handleSliderOnChangeCommited}
- step={step}
- marks={marks || []}
- disabled={disabled}
- aria-labelledby={labelId}
- />
+
+ = 0 ? value : innerValue}
+ min={minValue}
+ max={maxValue}
+ step={step}
+ marks={marks}
+ disabled={disabled}
+ aria-labelledby={labelId}
+ aria-orientation="horizontal"
+ aria-valuemax={maxValue}
+ aria-valuemin={minValue}
+ aria-valuenow={value != null && value >= 0 ? value : innerValue}
+ onChange={handleSliderChange}
+ onMouseUp={handleSliderOnChangeCommited}
+ onMouseDown={handleSliderDragging}
+ backgroundType={backgroundType}
+ />
+ {marks && {tickMarks}}
+
{showLimitsValues && (
{maxLabel}
@@ -123,7 +164,21 @@ const calculateWidth = (margin, size) =>
? `calc(${sizes[size]} - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`
: sizes[size];
-const Container = styled.div`
+const getChromeStyles = () => {
+ return `
+ width: 100%;
+ margin-right: 4px;`;
+};
+
+const getFireFoxStyles = () => {
+ return `
+ width: calc(100% - 16px);
+ margin-right: 3px;`;
+};
+
+type ContainerPropsType = { margin: Margin | Space; size: "medium" | "large" | "fillParent" };
+
+const Container = styled.div`
display: flex;
flex-direction: column;
margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
@@ -138,7 +193,9 @@ const Container = styled.div`
width: ${(props) => calculateWidth(props.margin, props.size)};
`;
-const Label = styled.label`
+type LabelPropsType = { disabled: boolean; backgroundType: "dark" | "light" };
+
+const Label = styled.label`
color: ${(props) =>
props.disabled
? props.backgroundType === "dark"
@@ -155,7 +212,7 @@ const Label = styled.label`
line-height: ${(props) => props.theme.labelLineHeight};
`;
-const HelperText = styled.span`
+const HelperText = styled.span`
color: ${(props) =>
props.disabled
? props.backgroundType === "dark"
@@ -164,6 +221,7 @@ const HelperText = styled.span`
: props.backgroundType === "dark"
? props.theme.helperTextFontColorOnDark
: props.theme.helperTextFontColor};
+
font-family: ${(props) => props.theme.fontFamily};
font-size: ${(props) => props.theme.helperTextFontSize};
font-style: ${(props) => props.theme.helperTextFontStyle};
@@ -171,139 +229,173 @@ const HelperText = styled.span`
line-height: ${(props) => props.theme.helperTextLineHeight};
`;
-const SliderContainer = styled.div`
- display: flex;
- height: 48px;
- align-items: center;
+type SliderInputPropsType = {
+ disabled: boolean;
+ backgroundType: "dark" | "light";
+ value: number;
+ min: number;
+ max: number;
+ marks: boolean;
+};
- .MuiSlider-root {
- min-width: 15rem;
- }
- .MuiSlider-container {
- padding: 30px 24px;
- }
- .MuiSlider-root.Mui-disabled {
- color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.disabledThumbBackgroundColorOnDark
- : props.theme.disabledThumbBackgroundColor};
- cursor: not-allowed;
+const SliderInput = styled.input`
+ width: 100%;
+ min-width: 240px;
+ height: ${(props) => props.theme.trackLineThickness};
+ display: inline-block;
+ vertical-align: middle;
+ -webkit-appearance: none;
+ background-color: ${(props) =>
+ props.disabled
+ ? props.backgroundType === "dark"
+ ? props.theme.disabledTotalLineColorOnDark + "61"
+ : props.theme.disabledTotalLineColor + "61"
+ : props.backgroundType === "dark"
+ ? props.theme.totalLineColorOnDark + "61"
+ : props.theme.totalLineColor + "61"};
+ background-image: ${(props) =>
+ props.disabled
+ ? props.backgroundType === "dark"
+ ? `linear-gradient(${props.theme.disabledTrackLineColorOnDark}, ${props.theme.disabledTrackLineColorOnDark})`
+ : `linear-gradient(${props.theme.disabledTrackLineColor}, ${props.theme.disabledTrackLineColor})`
+ : props.backgroundType === "dark"
+ ? `linear-gradient(${props.theme.trackLineColorOnDark}, ${props.theme.trackLineColorOnDark})`
+ : `linear-gradient(${props.theme.trackLineColor}, ${props.theme.trackLineColor})`};
+ background-repeat: no-repeat;
+ background-size: ${(props) => ((props.value - props.min) * 100) / (props.max - props.min) + "% 100%"};
+ border-radius: 5px;
+ z-index: 1;
+ cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
+ &::-webkit-slider-runnable-track {
+ -webkit-appearance: none;
+ box-shadow: none;
+ border: none;
+ background: transparent;
+ margin: 0px -8px;
}
- .Mui-disabled {
- & .MuiSlider-thumb {
- height: ${(props) => props.theme.thumbHeight};
- width: ${(props) => props.theme.thumbWidth};
- background-color: ${(props) =>
- props.backgroundType === "dark"
+
+ &::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ border: none;
+ height: ${(props) => props.theme.thumbHeight};
+ width: ${(props) => props.theme.thumbWidth};
+ border-radius: 25px;
+ background: ${(props) =>
+ props.disabled
+ ? props.backgroundType === "dark"
? props.theme.disabledThumbBackgroundColorOnDark
- : props.theme.disabledThumbBackgroundColor};
- top: ${(props) => props.theme.disabledThumbVerticalPosition};
+ : props.theme.disabledThumbBackgroundColor
+ : props.backgroundType === "dark"
+ ? props.theme.thumbBackgroundColorOnDark
+ : props.theme.thumbBackgroundColor};
+ &:active {
+ ${(props) => {
+ if (!props.disabled) {
+ return `
+ background: ${
+ props.backgroundType === "dark"
+ ? props.theme.activeThumbBackgroundColorOnDark
+ : props.theme.activeThumbBackgroundColor
+ };
+ transform: scale(1.16667);`;
+ }
+ }}
}
- & .MuiSlider-track {
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.disabledTrackLineColorOnDark
- : props.theme.disabledTrackLineColor};
- }
- & .MuiSlider-rail {
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.disabledTotalLineColorOnDark
- : props.theme.disabledTotalLineColor};
- }
- & > .MuiSlider-mark.MuiSlider-markActive {
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.disabledTickBackgroundColorOnDark
- : props.theme.disabledTickBackgroundColor} !important;
- }
- & > .MuiSlider-mark {
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.disabledTickBackgroundColorOnDark
- : props.theme.disabledTickBackgroundColor};
- height: ${(props) => props.theme.tickHeight};
- width: ${(props) => props.theme.tickWidth};
- border-radius: 18px;
- top: ${(props) => props.theme.disabledTickVerticalPosition};
+ &:hover {
+ ${(props) => {
+ if (!props.disabled) {
+ return `height: ${props.theme.hoverThumbHeight};
+ width: ${props.theme.hoverThumbWidth};
+ transform: scale(1.16667);
+ transform-origin: center center;
+ background: ${
+ props.backgroundType === "dark"
+ ? props.theme.hoverThumbBackgroundColorOnDark
+ : props.theme.hoverThumbBackgroundColor
+ };`;
+ }
+ }}
}
}
- .MuiSlider-thumb {
+ &::-moz-range-track {
+ -webkit-appearance: none;
+ box-shadow: none;
+ border: none;
+ background: transparent;
+ }
+ &::-moz-range-thumb {
+ -webkit-appearance: none;
+ border: none;
height: ${(props) => props.theme.thumbHeight};
width: ${(props) => props.theme.thumbWidth};
- background-color: ${(props) =>
- props.backgroundType === "dark" ? props.theme.thumbBackgroundColorOnDark : props.theme.thumbBackgroundColor};
- top: ${(props) => props.theme.thumbVerticalPosition};
- border-radius: 9999px;
-
- :hover,
- &.Mui-focusVisible {
- box-shadow: none;
+ border-radius: 25px;
+ background: ${(props) =>
+ props.disabled
+ ? props.backgroundType === "dark"
+ ? props.theme.disabledThumbBackgroundColorOnDark
+ : props.theme.disabledThumbBackgroundColor
+ : props.backgroundType === "dark"
+ ? props.theme.thumbBackgroundColorOnDark
+ : props.theme.thumbBackgroundColor};
+ &:active {
+ background: ${(props) =>
+ props.backgroundType === "dark"
+ ? props.theme.activeThumbBackgroundColorOnDark
+ : props.theme.activeThumbBackgroundColor};
+ transform: scale(1.16667);
}
- &.MuiSlider-active {
- box-shadow: none;
+ &:hover {
+ ${(props) => {
+ if (!props.disabled) {
+ return `height: ${props.theme.hoverThumbHeight};
+ width: ${props.theme.hoverThumbWidth};
+ transform: scale(1.16667);
+ transform-origin: center center;
+ background: ${
+ props.backgroundType === "dark"
+ ? props.theme.hoverThumbBackgroundColorOnDark
+ : props.theme.hoverThumbBackgroundColor
+ };`;
+ }
+ }}
}
- :focus {
- outline: ${(props) => (props.backgroundType === "dark" ? props.theme.focusColorOnDark : props.theme.focusColor)}
+ }
+ &:focus {
+ outline: none;
+ &::-webkit-slider-thumb {
+ outline: ${(props) =>
+ props.disabled
+ ? props.backgroundType === "dark"
+ ? props.theme.disabledFocusColorOnDark
+ : props.theme.disabledFocusColor
+ : props.backgroundType === "dark"
+ ? props.theme.focusColorOnDark
+ : props.theme.focusColor}
auto 1px;
outline-offset: 2px;
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.focusThumbBackgroundColorOnDark
- : props.theme.focusThumbBackgroundColor};
}
- :hover {
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.hoverThumbBackgroundColorOnDark
- : props.theme.hoverThumbBackgroundColor};
- transform: scale(${(props) => props.theme.hoverThumbScale});
- transform-origin: center;
- height: ${(props) => props.theme.hoverThumbHeight};
- width: ${(props) => props.theme.hoverThumbWidth};
- top: ${(props) => props.theme.hoverThumbVerticalPosition};
- }
- :active {
- background-color: ${(props) =>
- props.backgroundType === "dark"
- ? props.theme.activeThumbBackgroundColorOnDark
- : props.theme.activeThumbBackgroundColor};
- transform: scale(${(props) => props.theme.activeThumbScale});
- transform-origin: center;
+ &::-moz-range-thumb {
+ outline: ${(props) =>
+ props.disabled
+ ? props.backgroundType === "dark"
+ ? props.theme.disabledFocusColorOnDark
+ : props.theme.disabledFocusColor
+ : props.backgroundType === "dark"
+ ? props.theme.focusColorOnDark
+ : props.theme.focusColor}
+ auto 1px;
+ outline-offset: 2px;
}
}
- .MuiSlider-track {
- background-color: ${(props) =>
- props.backgroundType === "dark" ? props.theme.trackLineColorOnDark : props.theme.trackLineColor};
- height: ${(props) => props.theme.trackLineThickness};
- top: ${(props) => props.theme.trackLineVerticalPosition};
- border-radius: 9999px;
- }
- .MuiSlider-track.MuiSlider-trackAfter {
- background-color: ${(props) =>
- props.backgroundType === "dark" ? props.theme.trackLineColorOnDark : props.theme.trackLineColor};
- }
- .MuiSlider-rail {
- background-color: ${(props) =>
- props.backgroundType === "dark" ? props.theme.totalLineColorOnDark : props.theme.totalLineColor};
- height: ${(props) => props.theme.totalLineThickness};
- top: ${(props) => props.theme.totalLineVerticalPosition};
- }
- .MuiSlider-mark.MuiSlider-markActive {
- background-color: ${(props) =>
- props.backgroundType === "dark" ? props.theme.tickBackgroundColorOnDark : props.theme.tickBackgroundColor};
- }
- .MuiSlider-mark {
- background-color: ${(props) =>
- props.backgroundType === "dark" ? props.theme.tickBackgroundColorOnDark : props.theme.tickBackgroundColor};
- height: ${(props) => props.theme.tickHeight};
- width: ${(props) => props.theme.tickWidth};
- border-radius: 18px;
- top: ${(props) => props.theme.tickVerticalPosition};
- }
`;
-const MinLabelContainer = styled.span`
+const SliderContainer = styled.div`
+ display: flex;
+ height: 48px;
+ align-items: center;
+`;
+
+const LimitLabelContainer = styled.span`
color: ${(props) =>
props.disabled
? props.theme.disabledLimitValuesFontColor
@@ -317,31 +409,58 @@ const MinLabelContainer = styled.span`
font-weight: ${(props) => props.theme.limitValuesFontWeight};
letter-spacing: ${(props) => props.theme.limitValuesFontLetterSpacing};
white-space: nowrap;
+`;
+
+const MinLabelContainer = styled(LimitLabelContainer)`
margin-right: ${(props) => props.theme.floorLabelMarginRight};
`;
-const MaxLabelContainer = styled.span`
- color: ${(props) =>
+const MaxLabelContainer = styled(LimitLabelContainer)<{ step: number }>`
+ margin-left: ${(props) => (props.step === 1 ? props.theme.ceilLabelMarginLeft : "1.25rem")};
+`;
+
+const SliderInputContainer = styled.div`
+ position: relative;
+ width: 100%;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: -2px;
+ padding-top: 1px;
+`;
+
+type MarksContainerPropsType = { isFirefox: boolean };
+
+const MarksContainer = styled.div`
+ ${(props) => (props.isFirefox ? getFireFoxStyles() : getChromeStyles())}
+ position: absolute;
+ pointer-events: none;
+ height: 100%;
+ display: flex;
+ align-items: center;
+`;
+
+type TickMarkPropsType = { stepPosition: number; disabled: boolean; backgroundType: "dark" | "light" };
+
+const TickMark = styled.span`
+ position: absolute;
+ background: ${(props) =>
props.disabled
- ? props.theme.disabledLimitValuesFontColor
+ ? props.backgroundType === "dark"
+ ? props.theme.disabledTickBackgroundColorOnDark
+ : props.theme.disabledTickBackgroundColor
: props.backgroundType === "dark"
- ? props.theme.limitValuesFontColorOnDark
- : props.theme.limitValuesFontColor};
-
- font-family: ${(props) => props.theme.fontFamily};
- font-size: ${(props) => props.theme.limitValuesFontSize};
- font-style: ${(props) => props.theme.limitValuesFontStyle};
- font-weight: ${(props) => props.theme.limitValuesFontWeight};
- letter-spacing: ${(props) => props.theme.limitValuesFontLetterSpacing};
- white-space: nowrap;
- margin-left: ${(props) => (props.step === 1 ? props.theme.ceilLabelMarginLeft : "1.25rem")};
+ ? props.theme.tickBackgroundColorOnDark
+ : props.theme.tickBackgroundColor};
+ height: ${(props) => props.theme.tickHeight};
+ width: ${(props) => props.theme.tickWidth};
+ border-radius: 18px;
+ left: ${(props) => `${props.stepPosition}%`};
`;
const StyledTextInput = styled.div`
margin-left: ${(props) => props.theme.inputMarginLeft};
- label + .MuiInput-formControl {
- margin-top: 2px;
- }
max-width: 70px;
`;
diff --git a/lib/src/slider/types.ts b/lib/src/slider/types.ts
index 0eb98b4ac..263cc7848 100644
--- a/lib/src/slider/types.ts
+++ b/lib/src/slider/types.ts
@@ -1,5 +1,5 @@
-type Space = "xxsmall" | "xsmall" | "small" | "medium" | "large" | "xlarge" | "xxlarge";
-type Margin = {
+export type Space = "xxsmall" | "xsmall" | "small" | "medium" | "large" | "xlarge" | "xxlarge";
+export type Margin = {
top?: Space;
bottom?: Space;
left?: Space;