Примеры кастомных виджетов

Ниже — практические шаблоны, которые реально используются в MixSlide.

Пример 1. Минимальный текстовый виджет

function Widget() {
  const { width, height, text, color } = useParams({
    width: { type: "float" },
    height: { type: "float" },
    text: { type: "text", default: "Привет MixSlide" },
    color: { type: "colorGradient", default: "#222222" },
  });

  const _color = isGradient(color) ? undefined : color;
  const _bg = isGradient(color) ? color : "none";

  return (
    <div style={{
      width: `${width}px`,
      height: `${height}px`,
      color: "#000000",
      backgroundImage: _bg,
      backgroundColor: _color,
      display: "grid",
      placeItems: "center",
      fontSize: "24px",
    }}>
      {text}
    </div>
  );
}

isGradient помогает корректно поддерживать и цвет, и градиентный формат.

![Простое текстовое поле в предпросмотре] []

Пример 2. Кнопка «Перегенерировать» (regenerate)

function Widget() {
  const { width, height, seed, recalcBtn } = useParams({
    width: { type: "float" },
    height: { type: "float" },
    seed: { type: "integer", default: 0 },
    recalcBtn: {
      type: "button",
      title: "Перегенерировать",
      default: 0,
      onClick: (current) => (Number(current) || 0) + 1,
    },
  });

  const { pointsPath } = useBlockMemo("blob", () => {
    const points = generateBlobPoints(8, 70, 35, seed + recalcBtn);
    return { pointsPath: createSmoothPath(points, false) };
  }, [width, height, seed, recalcBtn]);

  return (
    <svg width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none">
      <path d={pointsPath} fill="#0a7" stroke="#0f0" />
    </svg>
  );
}

Идея: recalcBtn — это параметр-счётчик, при клике инкрементится через onClick, а ререндер зависит от него через useBlockMemo.

Пример 3. Editable-текст (редактирование на холсте)

function Widget() {
  const { width, height, text, font, color } = useParams({
    width: { type: "float" },
    height: { type: "float" },
    text: { type: "text", default: "Нажми двойной клик", linkedParamName: "font" },
    font: { type: "font" },
    color: { type: "color", default: "#ffffff" },
  });

  return (
    <div style={{ width: "100%", height: "100%" }}>
      <div
        editable="text"
        v1ParamName="text_text"
        style={{
          width: "100%",
          height: "100%",
          color,
          fontFamily: font?.refId,
          fontSize: "38px",
          fontFeatureSettings: font?.features,
          fontVariationSettings: font?.variant,
        }}
      >
        {text}
      </div>
    </div>
  );
}

Важно:

  • editable="text" должен указывать имя параметра, который редактируется (text);
  • для совместимости со старыми виджетами можно добавить v1ParamName.

![Editable текст в режиме редактирования] []

Пример 4. Работа с control points

function Widget() {
  const { width, height, radius, color } = useParams({
    width: { type: "float" },
    height: { type: "float" },
    radius: { type: "integer", min: 2, max: 100, default: 24 },
    color: { type: "colorGradient", default: "#ff6b00" },
  });

  const points = useControlPoints();
  const p1 = points.start ?? points.p0 ?? { x: 0, y: 0 };
  const p2 = points.end ?? points.p1 ?? { x: width, y: height };

  return (
    <svg width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} overflow="visible">
      <line x1={p1.x} y1={p1.y} x2={p2.x} y2={p2.y} stroke={color} strokeWidth={radius} />
    </svg>
  );
}

useControlPoints полезен, когда форма редактируется пользователем на холсте (например, для линий/фигур).
points — это словарь с ключами идентификаторов контрольных точек (start, p0, p1, …), ключи лучше задавать в самой фигуре/виджете.

Пример 5. svg-виджет с fill/stroke и градиентом

function Widget() {
  const { fill, stroke, strokeWidth, svgPathData, viewBoxWidth, viewBoxHeight } = useParams({
    fill: { type: "colorGradient", default: "#ffffff" },
    stroke: { type: "colorGradient", default: "#000000" },
    strokeWidth: { type: "integer", min: 0, step: 1, default: 2 },
    svgPathData: { type: "string", default: "" },
    viewBoxWidth: { type: "float", default: 100 },
    viewBoxHeight: { type: "float", default: 100 },
  });

  const fillValue = isGradient(fill) ? undefined : fill;
  const strokeValue = isGradient(stroke) ? undefined : stroke;

  return (
    <svg width="100%" height="100%" viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}>
      <path
        d={svgPathData}
        fill={fillValue}
        stroke={strokeValue}
        strokeWidth={strokeWidth}
        vectorEffect="non-scaling-stroke"
      />
    </svg>
  );
}

fill / stroke в формате градиента лучше обрабатывать через условие isGradient, если логика виджета предполагает fallback на плоский цвет.