Недавно мне поручили представить некоторые данные на основе временной шкалы и позволить пользователям добавлять свои собственные точки данных. Ползунок материала казался очевидным выбором — он дал мне около 60% пути прямо из коробки. Я не новичок в материалах в целом, но это был мой первый раз, когда я использовал этот конкретный компонент, и я столкнулся с некоторыми проблемами с настройкой меток ползунка, которые вызвали у меня некоторую изжогу. Я решил собрать несколько заметок на эту тему, чтобы помочь всем, кому может понадобиться пойти по моим стопам.

Ссылка на демо на Github находится здесь

Хорошо, круто — отправная точка — слайдер основного материала. Вы видели его тысячу раз прежде. Он утешительный и надежный. Однако есть проблема. Он не имеет ничего общего с культовым киноактером — Киану Ривзом.

export const BasicSliderWithMarks = () => {
  const classes = useStyles();

  const initialMarks: Mark[] = [
    { label: "1", value: 15 },
    {
      label: "2",
      value: 45,
    },
    {
      label: "3",
      value: 85,
    },
  ];

  const [sliderValue, setSliderValue] = useState<number>(0);
  const [marks, setMarks] = useState<Mark[]>(initialMarks);

  const updateSliderValue = (e: Event, input: number | number[]): void => {
    const value = typeof input === "number" ? input : input[0];
    setSliderValue(value);
  };

  return (
    <>
      <Typography variant="h4" className={classes.container}>
        Basic Slider with Marks
      </Typography>
      <Box className={classes.sliderContainer}>
        <Typography className={classes.sliderValue}>{sliderValue}</Typography>
        <Slider
          min={0}
          max={100}
          value={sliderValue}
          onChange={updateSliderValue}
          marks={marks}
        />
      </Box>
    </>
  );
};

Здесь мы идем….начинаем получать дополнительную информацию. Это использует встроенный в материал рендеринг меток — все еще довольно скучно с данными, которые мы используем, и (очевидно) все еще не хватает метки, когда дело доходит до любимого актера Киану Ривза.

Вау! Вы видите проблему здесь. Становится очень сложно отображать какие-либо длинные или перекрывающиеся данные, лишая меня доступа ко всей этой милой информации о Киану Ривзе. Есть способы решить эту проблему с помощью css, переопределив классы элементов css по умолчанию, но нам нужно больше контроля над визуализируемым элементом mark, чтобы мы могли добавить любую пользовательскую бизнес-логику, которую мы хотим.

Вот наша отправная точка для показанного выше слайдера…

export const SliderWithInputSource = () => {
  const classes = useStyles();

  const inputRef = useRef();

  const initialMarks = [
    {
      label: "Parenthood",
      value: 1989,
    },
    {
      label: "Bill & Ted's Excellent Adventure",
      value: 1990,
    },
    {
      label: "Point Break",
      value: 1991,
    },
  ];

  const [sliderValue, setSliderValue] = useState<number>(1985);
  const [inputValue, setInputValue] = useState<string>("");
  const [marks, setMarks] = useState<Mark[]>(initialMarks);

  const updateSliderValue = (e: Event, input: number | number[]): void => {
    const value = typeof input === "number" ? input : input[0];
    setSliderValue(value);
  };

  return (
    <>
      <Typography variant="h4" className={classes.container}>
        Keanu Reeves Movies
      </Typography>
      <Box className={classes.sliderContainer}>
        <Typography className={classes.sliderValue}>{sliderValue}</Typography>
        <Slider
          min={1985}
          max={2023}
          value={sliderValue}
          onChange={updateSliderValue}
          marks={marks}
        />

        <Input
          placeholder="Keanu Reeves Movie..."
          inputRef={inputRef}
          value={inputValue}
          onChange={(
            event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
          ) => {
            setInputValue(event.target.value);
          }}
        />
        <Button
          sx={{
            alignContent: "end",
          }}
          onClick={() => {
            setMarks([...marks, { label: inputValue, value: sliderValue }]);
            setInputValue("");
          }}
        >
          Add Movie
        </Button>
      </Box>
    </>
  );
};

Итак, сначала давайте настроим пользовательскую метку, чтобы мы могли переопределить метки по умолчанию, которые создает для нас материал. Чтобы сделать это, мы должны понимать (и использовать) свойство слотов материала. Он предоставляет все дочерние компоненты в данном материальном компоненте, чтобы вы могли передать свой собственный компонент. Наш выглядит так:

<Slider
  min={1985}
  max={2023}
  value={sliderValue}
  onChange={updateSliderValue}
  marks={marks}
  slots={{
    markLabel: BasicMark,
  }}
  />

CustomMark относится к компоненту jsx, которым вы хотите переопределить свойство markLabel. Компонент материала будет генерировать один компонент markLabel для каждого элемента в вашем массиве меток. Между прочим, массив меток может быть любым, каким вы хотите его видеть (и мы будем его изменять), но значения по умолчанию, с которыми материал хорошо работает, равны { label: string; value: number; } — значение представляет собой соответствующее значение ползунка, а метка — это то, что отображается в метке метки. элемент. Как только вы освободитесь от материальной матрицы, вы сможете расширить тип Знака, чтобы он был тем, что вы хотите, и использовать его соответствующим образом.

Давайте двигаться вперед! Вот компонент BasicMark:

import { SliderMarkLabel } from "@mui/material";
import { Mark } from "../types";

export const BasicMark = (props: any) => {
  const index = props["data-index"] as number;
  const mark = props.ownerState.marks[index] as Mark;

  return (
    <SliderMarkLabel {...props}>
      <span>{mark.label}</span>
    </SliderMarkLabel>
  );
};

Простите за использование any, но речь идет о пользовательских метках ползунка (и Киану), а не о восхождении по золотой лестнице машинописного текста.

Уверен, теперь вы видите, что возможности безграничны! Давайте оденемся так, чтобы Киану получил представление, которого Киану заслуживает.

Вот расширенный компонент пользовательской метки:

import { SliderMarkLabel, Tooltip } from "@mui/material";

import { Mark } from "../types";

export const CustomMark = (props: any) => {
  const index = props["data-index"] as number;
  const mark = props.ownerState.marks[index] as Mark;

  const isTop = index % 2 !== 0;

  const baseStyles = {
    backgroundColor: "#0050C9",
    height: "24px",
    boxShadow: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)",
    borderRadius: 8,
    color: "white",
  };
  const topRowStyles = {
    ...baseStyles,
    ...{
      top: "-24px",
    },
  };
  
  const customMarkStyles = isTop ? topRowStyles : baseStyles;

  return (
    <Tooltip title={mark.label} placement={isTop ? "top" : "bottom"} arrow>
      <SliderMarkLabel
        {...props}
        style={{
          ...props.style,
          ...customMarkStyles,
        }}
      >
        <div
          style={{
            marginLeft: 8,
            marginTop: 2,
            marginRight: 8,
          }}
        >
          <span className="text-white">{mark.value}</span>
        </div>
      </SliderMarkLabel>
    </Tooltip>
  );
};

Вот оно! Отсюда вы можете дать волю своему воображению и представить свои данные, основанные на Киану Ривзе, любым способом, который вы считаете нужным. Удачного кодирования!