Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Slider] Modernize implementation #583

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions docs/app/experiments/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,11 @@ function Label(props: any) {
const defaultId = React.useId();
const labelId = idProp ?? defaultId;

const { subitems } = useSliderContext();
const { inputIdMap } = useSliderContext();

const htmlFor = Array.from(subitems.values())
.reduce((acc, item) => {
return `${acc} ${item.inputId}`;
}, '')
.trim();
const inputId = inputIdMap.get(0);

return <label id={labelId} htmlFor={htmlFor} {...otherProps} />;
return <label id={labelId} htmlFor={inputId} {...otherProps} />;
}

function LabelRange(props: any) {
Expand Down
6 changes: 0 additions & 6 deletions docs/data/api/slider-root.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
"default": "'ltr'"
},
"disabled": { "type": { "name": "bool" }, "default": "false" },
"id": { "type": { "name": "string" } },
"largeStep": { "type": { "name": "number" }, "default": "10" },
"max": { "type": { "name": "number" }, "default": "100" },
"min": { "type": { "name": "number" }, "default": "0" },
"minStepsBetweenValues": { "type": { "name": "number" }, "default": "0" },
"name": { "type": { "name": "string" } },
"onValueChange": {
"type": { "name": "func" },
"signature": {
Expand All @@ -35,8 +31,6 @@
"default": "'horizontal'"
},
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } },
"step": { "type": { "name": "number" }, "default": "1" },
"tabIndex": { "type": { "name": "number" } },
"value": {
"type": { "name": "union", "description": "Array&lt;number&gt;<br>&#124;&nbsp;number" }
}
Expand Down
188 changes: 44 additions & 144 deletions docs/data/components/slider/RangeSlider.js
Original file line number Diff line number Diff line change
@@ -1,168 +1,68 @@
'use client';
import * as React from 'react';
import { styled, useTheme, Box } from '@mui/system';
import * as BaseSlider from '@base_ui/react/Slider';
import { useTheme } from '@mui/system';
import * as Slider from '@base_ui/react/Slider';
import classes from './styles.module.css';

export default function RangeSlider() {
// Replace this with your app logic for determining dark mode
const isDarkMode = useIsDarkMode();
const [value, setValue] = React.useState([20, 37]);

return (
<Box
<div
className={isDarkMode ? 'dark' : ''}
sx={{ display: 'flex', flexDirection: 'column', gap: '4rem', width: 320 }}
style={{ display: 'flex', flexDirection: 'column', gap: '4rem', width: 320 }}
>
{/* controlled: */}
<Slider
<Slider.Root
value={value}
onValueChange={setValue}
aria-labelledby="ControlledRangeLabel"
className={classes.slider}
>
<Label id="ControlledRangeLabel">Controlled Range</Label>
<SliderOutput />
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb />
<SliderThumb />
</SliderTrack>
</SliderControl>
</Slider>
{/*
we can't use a <label> element in a range slider since the `for` attribute
cannot reference more than one <input> element
although the html spec doesn't forbid <label> without `for`:
https://html.spec.whatwg.org/multipage/forms.html#the-label-element
eslint complains by default and a11y validators may complain e.g. WAVE
*/}
<span id="ControlledRangeLabel" className={classes.label}>
Controlled Range
</span>
<Slider.Output className={classes.output} />
<Slider.Control className={classes.control}>
<Slider.Track className={classes.track}>
<Slider.Indicator className={classes.indicator} />
<Slider.Thumb className={classes.thumb} />
<Slider.Thumb className={classes.thumb} />
</Slider.Track>
</Slider.Control>
</Slider.Root>
{/* uncontrolled: */}
<Slider defaultValue={[20, 37]} aria-labelledby="UncontrolledRangeLabel">
<Label id="UncontrolledRangeLabel">Uncontrolled Range</Label>
<SliderOutput />
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb />
<SliderThumb />
</SliderTrack>
</SliderControl>
</Slider>
</Box>
<Slider.Root
defaultValue={[20, 37]}
aria-labelledby="UncontrolledRangeLabel"
className={classes.slider}
>
<span id="UncontrolledRangeLabel" className={classes.label}>
Uncontrolled Range
</span>
<Slider.Output className={classes.output} />
<Slider.Control className={classes.control}>
<Slider.Track className={classes.track}>
<Slider.Indicator className={classes.indicator} />
<Slider.Thumb className={classes.thumb} />
<Slider.Thumb className={classes.thumb} />
</Slider.Track>
</Slider.Control>
</Slider.Root>
</div>
);
}

const grey = {
50: '#F3F6F9',
100: '#E5EAF2',
200: '#DAE2ED',
300: '#C7D0DD',
400: '#B0B8C4',
500: '#9DA8B7',
600: '#6B7A90',
700: '#434D5B',
800: '#303740',
900: '#1C2025',
};

function useIsDarkMode() {
const theme = useTheme();
return theme.palette.mode === 'dark';
}

const Slider = styled(BaseSlider.Root)`
font-family: 'IBM Plex Sans', sans-serif;
font-size: 1rem;
width: 100%;
align-items: center;
position: relative;
-webkit-tap-highlight-color: transparent;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
`;

const SliderOutput = styled(BaseSlider.Output)`
text-align: right;
`;

const SliderControl = styled(BaseSlider.Control)`
grid-column: 1/3;
display: flex;
align-items: center;
position: relative;
width: 100%;
height: 16px;
border-radius: 9999px;
touch-action: none;

&[data-disabled] {
cursor: not-allowed;
}
`;

const SliderTrack = styled(BaseSlider.Track)`
width: 100%;
height: 2px;
border-radius: 9999px;
background-color: ${grey[400]};
touch-action: none;

.dark & {
background-color: ${grey[700]};
}
`;

const SliderIndicator = styled(BaseSlider.Indicator)`
border-radius: 9999px;
background-color: black;

.dark & {
background-color: ${grey[100]};
}
`;

const SliderThumb = styled(BaseSlider.Thumb)`
position: absolute;
width: 16px;
height: 16px;
box-sizing: border-box;
border-radius: 50%;
background-color: black;
transform: translateX(-50%);
touch-action: none;

&:focus-visible {
outline: 2px solid black;
outline-offset: 2px;
}

.dark & {
background-color: ${grey[300]};
}

.dark &:focus-visible {
outline-color: ${grey[300]};
outline-width: 1px;
outline-offset: 3px;
}

&[data-dragging='true'] {
background-color: pink;
}

&[data-disabled],
.dark &[data-disabled] {
background-color: ${grey[600]};
}

.dark &[data-dragging='true'] {
background-color: pink;
}
`;

// we can't use a <label> element in a range slider since the `for` attribute
// cannot reference more than one <input> element
// the html spec doesn't forbid <label> without `for` https://html.spec.whatwg.org/multipage/forms.html#the-label-element
// but eslint complains by default and a11y validators may complain e.g. WAVE
const Label = styled('span')`
cursor: unset;
font-weight: bold;

&[data-disabled='true'] {
color: ${grey[600]};
}
`;
Loading