Repository: mui/material-ui (TypeScript)
Date: 2019-10-05 01:14:51+00:00
Task type: Feature

Issue:
[Slider] Hide the track
It doesn't seem possible to style **only** selected/current mark label for emphasis. All labels left of the thumbnails are given `markLabelActive` css.  For example, users only want see rainfall chart for 2017.  The way it's styled would give the impression that previous years are also included.

<img width="278" alt="slider" src="https://user-images.githubusercontent.com/3805254/61443557-077fc980-a94a-11e9-89cc-f45a59e89223.png">

- [x] This is not a v0.x issue. <!-- (v0.x is no longer maintained) -->
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.


## Current Behavior 😯
all labels left of active one assumed active

## Steps to Reproduce 🕹
https://codesandbox.io/embed/material-demo-tfbdo

## Your Environment 🌎

| Tech         | Version |
|--------------|---------|
| Material-UI  | v4.2.1  |
| React        |       16.8  |
| Browser      |      chrome   |


Issue discussion:
> For example, users only want see rainfall chart for 2017. The way it's styled would give the impression that previous years are also included.

@vnugent Thank you for sharing the use case. It's interesting, I hadn't considered it when working on the component. Take the native browser implementations, [most](https://www.hongkiat.com/blog/html5-range-slider-style/) don't show the track by default.

What do you think of introducing a new `disableTrack` prop that would output the following:

```jsx
<Slider disableTrack={true} />
```

![Capture d’écran 2019-07-18 à 13 30 14](https://user-images.githubusercontent.com/3165635/61454106-399c2600-a960-11e9-958b-0b15ae94025e.png)

vs 

```jsx
<Slider disableTrack={false} />
```

![Capture d’écran 2019-07-18 à 13 31 01](https://user-images.githubusercontent.com/3165635/61454133-4fa9e680-a960-11e9-8991-a819b6a0f22d.png)


Is this something you would want to work on? :)
That could work.  However could we also give track a `direction`, eg left-of-thumb (default) or right-of-thumb instead of defaulting it to left-to-right? I can see many applications where you want to emphasize the upper range.
> However could we also give track a direction

@vnugent I believe we cover this problem in #16352. The conclusion of the thread was: use the RTL mode in this case and that we should warn when min > max.
As it turns out, we can generialize the solution with a better API, see #17215. 
I started to look at this and hopefully can combine with #17215
Will push some code tomorrow

Solution patch:
diff --git a/docs/pages/api/slider.md b/docs/pages/api/slider.md
--- a/docs/pages/api/slider.md
+++ b/docs/pages/api/slider.md
@@ -43,6 +43,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
 | <span class="prop-name">orientation</span> | <span class="prop-type">'horizontal'<br>&#124;&nbsp;'vertical'</span> | <span class="prop-default">'horizontal'</span> | The slider orientation. |
 | <span class="prop-name">step</span> | <span class="prop-type">number</span> | <span class="prop-default">1</span> | The granularity with which the slider can step through values. (A "discrete" slider.) When step is `null`, the thumb can only be slid onto marks provided with the `marks` prop. |
 | <span class="prop-name">ThumbComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">'span'</span> | The component used to display the value label. |
+| <span class="prop-name">track</span> | <span class="prop-type">'normal'<br>&#124;&nbsp;false<br>&#124;&nbsp;'inverted'</span> | <span class="prop-default">'normal'</span> | The track presentation:<br>- `normal` the track will render a bar representing the slider value. - `inverted` the track will render a bar representing the remaining slider value. - `false` the track will render without a bar. |
 | <span class="prop-name">value</span> | <span class="prop-type">number<br>&#124;&nbsp;Array<number></span> |  | The value of the slider. For ranged sliders, provide an array with two values. |
 | <span class="prop-name">ValueLabelComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">ValueLabel</span> | The value label component. |
 | <span class="prop-name">valueLabelDisplay</span> | <span class="prop-type">'on'<br>&#124;&nbsp;'auto'<br>&#124;&nbsp;'off'</span> | <span class="prop-default">'off'</span> | Controls when the value label is displayed:<br>- `auto` the value label will display when the thumb is hovered or focused. - `on` will display persistently. - `off` will never display. |
@@ -67,6 +68,8 @@ Any other props supplied will be provided to the root element (native element).
 | <span class="prop-name">disabled</span> | <span class="prop-name">Mui-disabled</span> | Pseudo-class applied to the root element if `disabled={true}`.
 | <span class="prop-name">rail</span> | <span class="prop-name">MuiSlider-rail</span> | Styles applied to the rail element.
 | <span class="prop-name">track</span> | <span class="prop-name">MuiSlider-track</span> | Styles applied to the track element.
+| <span class="prop-name">trackFalse</span> | <span class="prop-name">MuiSlider-trackFalse</span> | Styles applied to the track element if `track={false}`.
+| <span class="prop-name">trackInverted</span> | <span class="prop-name">MuiSlider-trackInverted</span> | Styles applied to the track element if `track="inverted"`.
 | <span class="prop-name">thumb</span> | <span class="prop-name">MuiSlider-thumb</span> | Styles applied to the thumb element.
 | <span class="prop-name">thumbColorPrimary</span> | <span class="prop-name">MuiSlider-thumbColorPrimary</span> | Styles applied to the thumb element if `color="primary"`.
 | <span class="prop-name">thumbColorSecondary</span> | <span class="prop-name">MuiSlider-thumbColorSecondary</span> | Styles applied to the thumb element if `color="secondary"`.
diff --git a/docs/src/pages/components/slider/TrackFalseSlider.js b/docs/src/pages/components/slider/TrackFalseSlider.js
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/components/slider/TrackFalseSlider.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import Typography from '@material-ui/core/Typography';
+import Slider from '@material-ui/core/Slider';
+
+const useStyles = makeStyles(theme => ({
+  root: {
+    width: 250,
+  },
+  margin: {
+    height: theme.spacing(3),
+  },
+}));
+
+const marks = [
+  {
+    value: 0,
+    label: '0°C',
+  },
+  {
+    value: 20,
+    label: '20°C',
+  },
+  {
+    value: 37,
+    label: '37°C',
+  },
+  {
+    value: 100,
+    label: '100°C',
+  },
+];
+
+function valuetext(value) {
+  return `${value}°C`;
+}
+
+export default function TrackFalseSlider() {
+  const classes = useStyles();
+
+  return (
+    <div className={classes.root}>
+      <Typography id="track-false-slider" gutterBottom>
+        Removed track
+      </Typography>
+      <Slider
+        track={false}
+        aria-labelledby="track-false-slider"
+        getAriaValueText={valuetext}
+        defaultValue={30}
+        marks={marks}
+      />
+      <div className={classes.margin} />
+      <Typography id="track-false-multi-values-slider" gutterBottom>
+        Removed track multi-values
+      </Typography>
+      <Slider
+        track={false}
+        aria-labelledby="track-false-range-slider"
+        getAriaValueText={valuetext}
+        defaultValue={[20, 37, 50]}
+        marks={marks}
+      />
+    </div>
+  );
+}
diff --git a/docs/src/pages/components/slider/TrackFalseSlider.tsx b/docs/src/pages/components/slider/TrackFalseSlider.tsx
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/components/slider/TrackFalseSlider.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
+import Typography from '@material-ui/core/Typography';
+import Slider from '@material-ui/core/Slider';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      width: 250,
+    },
+    margin: {
+      height: theme.spacing(3),
+    },
+  }),
+);
+
+const marks = [
+  {
+    value: 0,
+    label: '0°C',
+  },
+  {
+    value: 20,
+    label: '20°C',
+  },
+  {
+    value: 37,
+    label: '37°C',
+  },
+  {
+    value: 100,
+    label: '100°C',
+  },
+];
+
+function valuetext(value: number) {
+  return `${value}°C`;
+}
+
+export default function TrackFalseSlider() {
+  const classes = useStyles();
+
+  return (
+    <div className={classes.root}>
+      <Typography id="track-false-slider" gutterBottom>
+        Removed track
+      </Typography>
+      <Slider
+        track={false}
+        aria-labelledby="track-false-slider"
+        getAriaValueText={valuetext}
+        defaultValue={30}
+        marks={marks}
+      />
+      <div className={classes.margin} />
+      <Typography id="track-false-multi-values-slider" gutterBottom>
+        Removed track multi-values
+      </Typography>
+      <Slider
+        track={false}
+        aria-labelledby="track-false-range-slider"
+        getAriaValueText={valuetext}
+        defaultValue={[20, 37, 50]}
+        marks={marks}
+      />
+    </div>
+  );
+}
diff --git a/docs/src/pages/components/slider/TrackInvertedSlider.js b/docs/src/pages/components/slider/TrackInvertedSlider.js
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/components/slider/TrackInvertedSlider.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import Typography from '@material-ui/core/Typography';
+import Slider from '@material-ui/core/Slider';
+
+const useStyles = makeStyles(theme => ({
+  root: {
+    width: 250,
+  },
+  margin: {
+    height: theme.spacing(3),
+  },
+}));
+
+const marks = [
+  {
+    value: 0,
+    label: '0°C',
+  },
+  {
+    value: 20,
+    label: '20°C',
+  },
+  {
+    value: 37,
+    label: '37°C',
+  },
+  {
+    value: 100,
+    label: '100°C',
+  },
+];
+
+function valuetext(value) {
+  return `${value}°C`;
+}
+
+export default function TrackInvertedSlider() {
+  const classes = useStyles();
+
+  return (
+    <div className={classes.root}>
+      <Typography id="track-inverted-slider" gutterBottom>
+        Inverted track
+      </Typography>
+      <Slider
+        track="inverted"
+        aria-labelledby="track-inverted-slider"
+        getAriaValueText={valuetext}
+        defaultValue={30}
+        marks={marks}
+      />
+      <div className={classes.margin} />
+      <Typography id="track-inverted-range-slider" gutterBottom>
+        Inverted track range
+      </Typography>
+      <Slider
+        track="inverted"
+        aria-labelledby="track-inverted-range-slider"
+        getAriaValueText={valuetext}
+        defaultValue={[20, 37]}
+        marks={marks}
+      />
+    </div>
+  );
+}
diff --git a/docs/src/pages/components/slider/TrackInvertedSlider.tsx b/docs/src/pages/components/slider/TrackInvertedSlider.tsx
new file mode 100644
--- /dev/null
+++ b/docs/src/pages/components/slider/TrackInvertedSlider.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
+import Typography from '@material-ui/core/Typography';
+import Slider from '@material-ui/core/Slider';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      width: 250,
+    },
+    margin: {
+      height: theme.spacing(3),
+    },
+  }),
+);
+
+const marks = [
+  {
+    value: 0,
+    label: '0°C',
+  },
+  {
+    value: 20,
+    label: '20°C',
+  },
+  {
+    value: 37,
+    label: '37°C',
+  },
+  {
+    value: 100,
+    label: '100°C',
+  },
+];
+
+function valuetext(value: number) {
+  return `${value}°C`;
+}
+
+export default function TrackInvertedSlider() {
+  const classes = useStyles();
+
+  return (
+    <div className={classes.root}>
+      <Typography id="track-inverted-slider" gutterBottom>
+        Inverted track
+      </Typography>
+      <Slider
+        track="inverted"
+        aria-labelledby="track-inverted-slider"
+        getAriaValueText={valuetext}
+        defaultValue={30}
+        marks={marks}
+      />
+      <div className={classes.margin} />
+      <Typography id="track-inverted-range-slider" gutterBottom>
+        Inverted track range
+      </Typography>
+      <Slider
+        track="inverted"
+        aria-labelledby="track-inverted-range-slider"
+        getAriaValueText={valuetext}
+        defaultValue={[20, 37]}
+        marks={marks}
+      />
+    </div>
+  );
+}
diff --git a/docs/src/pages/components/slider/slider.md b/docs/src/pages/components/slider/slider.md
--- a/docs/src/pages/components/slider/slider.md
+++ b/docs/src/pages/components/slider/slider.md
@@ -48,6 +48,22 @@ Continuous sliders allow users to select a value along a subjective range.
 
 {{"demo": "pages/components/slider/VerticalSlider.js"}}
 
+## Track
+
+The track shows the range available for user selection.
+
+### Removed track
+
+The track can be turned off with `track={false}`.
+
+{{"demo": "pages/components/slider/TrackFalseSlider.js"}}
+
+### Inverted track
+
+The track can be inverted with `track="inverted"`.
+
+{{"demo": "pages/components/slider/TrackInvertedSlider.js"}}
+
 ## Accessibility
 
 (WAI-ARIA: https://www.w3.org/TR/wai-aria-practices/#slider)
diff --git a/packages/material-ui/src/Slider/Slider.d.ts b/packages/material-ui/src/Slider/Slider.d.ts
--- a/packages/material-ui/src/Slider/Slider.d.ts
+++ b/packages/material-ui/src/Slider/Slider.d.ts
@@ -36,6 +36,7 @@ export interface SliderProps
   orientation?: 'horizontal' | 'vertical';
   step?: number | null;
   ThumbComponent?: React.ElementType<React.HTMLAttributes<HTMLSpanElement>>;
+  track?: 'normal' | false | 'inverted';
   value?: number | number[];
   ValueLabelComponent?: React.ElementType<ValueLabelProps>;
   valueLabelDisplay?: 'on' | 'auto' | 'off';
@@ -52,6 +53,8 @@ export type SliderClassKey =
   | 'disabled'
   | 'rail'
   | 'track'
+  | 'trackFalse'
+  | 'trackInverted'
   | 'thumb'
   | 'thumbColorPrimary'
   | 'thumbColorSecondary'
diff --git a/packages/material-ui/src/Slider/Slider.js b/packages/material-ui/src/Slider/Slider.js
--- a/packages/material-ui/src/Slider/Slider.js
+++ b/packages/material-ui/src/Slider/Slider.js
@@ -4,7 +4,7 @@ import clsx from 'clsx';
 import { chainPropTypes } from '@material-ui/utils';
 import withStyles from '../styles/withStyles';
 import useTheme from '../styles/useTheme';
-import { fade } from '../styles/colorManipulator';
+import { fade, lighten, darken } from '../styles/colorManipulator';
 import { useIsFocusVisible } from '../utils/focusVisible';
 import useEventCallback from '../utils/useEventCallback';
 import useForkRef from '../utils/useForkRef';
@@ -199,6 +199,25 @@ export const styles = theme => ({
       width: 2,
     },
   },
+  /* Styles applied to the track element if `track={false}`. */
+  trackFalse: {
+    '& $track': {
+      display: 'none',
+    },
+  },
+  /* Styles applied to the track element if `track="inverted"`. */
+  trackInverted: {
+    '& $track': {
+      backgroundColor:
+        // Same logic as the LinearProgress track color
+        theme.palette.type === 'light'
+          ? lighten(theme.palette.primary.main, 0.62)
+          : darken(theme.palette.primary.main, 0.5),
+    },
+    '& $rail': {
+      opacity: 1,
+    },
+  },
   /* Styles applied to the thumb element. */
   thumb: {
     position: 'absolute',
@@ -319,6 +338,7 @@ const Slider = React.forwardRef(function Slider(props, ref) {
     orientation = 'horizontal',
     step = 1,
     ThumbComponent = 'span',
+    track = 'normal',
     value: valueProp,
     ValueLabelComponent = ValueLabel,
     valueLabelDisplay = 'off',
@@ -680,6 +700,8 @@ const Slider = React.forwardRef(function Slider(props, ref) {
           [classes.disabled]: disabled,
           [classes.marked]: marks.length > 0 && marks.some(mark => mark.label),
           [classes.vertical]: orientation === 'vertical',
+          [classes.trackInverted]: track === 'inverted',
+          [classes.trackFalse]: track === false,
         },
         className,
       )}
@@ -692,9 +714,17 @@ const Slider = React.forwardRef(function Slider(props, ref) {
       {marks.map(mark => {
         const percent = valueToPercent(mark.value, min, max);
         const style = axisProps[axis].offset(percent);
-        const markActive = range
-          ? mark.value >= values[0] && mark.value <= values[values.length - 1]
-          : mark.value <= values[0];
+
+        let markActive;
+        if (track === false) {
+          markActive = values.indexOf(mark.value) !== -1;
+        } else {
+          const isMarkActive = range
+            ? mark.value >= values[0] && mark.value <= values[values.length - 1]
+            : mark.value <= values[0];
+          markActive =
+            (isMarkActive && track === 'normal') || (!isMarkActive && track === 'inverted');
+        }
 
         return (
           <React.Fragment key={mark.value}>
@@ -885,6 +915,14 @@ Slider.propTypes = {
    * The component used to display the value label.
    */
   ThumbComponent: PropTypes.elementType,
+  /**
+   * The track presentation:
+   *
+   * - `normal` the track will render a bar representing the slider value.
+   * - `inverted` the track will render a bar representing the remaining slider value.
+   * - `false` the track will render without a bar.
+   */
+  track: PropTypes.oneOf(['normal', false, 'inverted']),
   /**
    * The value of the slider.
    * For ranged sliders, provide an array with two values.


Test patch:
diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js
--- a/packages/material-ui/src/Slider/Slider.test.js
+++ b/packages/material-ui/src/Slider/Slider.test.js
@@ -256,6 +256,18 @@ describe('<Slider />', () => {
     });
   });
 
+  describe('prop: track', () => {
+    it('should render the track classes for false', () => {
+      const { container } = render(<Slider track={false} value={50} />);
+      expect(container.firstChild).to.have.class(classes.trackFalse);
+    });
+
+    it('should render the track classes for inverted', () => {
+      const { container } = render(<Slider track="inverted" value={50} />);
+      expect(container.firstChild).to.have.class(classes.trackInverted);
+    });
+  });
+
   describe('keyboard', () => {
     it('should handle all the keys', () => {
       const { getByRole } = render(<Slider defaultValue={50} />);