Наскоро ми беше възложено да представя някои данни, базирани на времева линия, и да позволя на потребителите да добавят свои собствени точки от данни. Плъзгачът за материали изглеждаше като очевиден избор - изкара ми около 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 елемента по подразбиране classNames, но се нуждаем от повече контрол върху елемента за маркиране, който се изобразява, така че да можем да добавим каквато персонализирана бизнес логика пожелаем.

Ето нашата отправна точка за плъзгача на горната снимка...

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 за всеки елемент във вашия масив marks. Масивът от марки между другото може да бъде какъвто искате да бъде (и ние ще го модифицираме), но стойностите по подразбиране, с които материалът работи добре, са { label: string; value: number; } — стойността е съответната стойност на плъзгача, а етикетът е това, което се показва в етикета на маркировката елемент. След като се освободите от материалната матрица, можете да увеличите типа Mark, за да бъде каквото искате, и да го използвате по съответния начин.

Да вървим напред! Ето компонента 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>
  );
};

Ето го! От тук можете да развихрите въображението си и да представите своите данни, базирани на Киану Рийвс, по какъвто начин сметнете за подходящ. Приятно кодиране!