Как предварительно настроить базовую анимацию движения кадра в полиморфном компоненте, использующем слот Radix?

Согласно документации Slot, если ваш компонент имеет один дочерний элемент, можно создать соответственно полиморфную кнопку:

// your-button.jsx
import React from 'react';
import { Slot } from '@radix-ui/react-slot';

function Button({ asChild, ...props }) {
  const Comp = asChild ? Slot : 'button';
  return <Comp {...props} />;
}

Что, если вы хотите предварительно настроить компонент Button для обеспечения базовой анимации с помощью framer-motion ?

Проблема

Компонент полиморфной кнопки должен возвращать компонент движения, который масштабируется до 0,9 при нажатии на него.

С помощью Framer-motion этого можно добиться с помощью Motion Component соответственно:

function Button({ ..props }) {
  return <motion.button whileTap = {{ scale: 0.9 }} {...props} />;
}

Как добиться такого же поведения с полиморфной кнопкой, использующей Slot?

Что я пробовал:

Я попытался обернуть Slot компонентом высшего порядка motion, но это вызвало конфликт типов.

Мой компонент кнопки:

export interface ButtonProps extends HTMLMotionProps<'div'> {
  asChild?: boolean;
}

const MotionSlot = motion(Slot);

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ asChild = false, ...props }, ref) => {
    const Comp = asChild ? MotionSlot : motion.button;

    return <Comp whileTap = {{ scale: 0.9 }} ref = {ref} {...props} />;
  }
);

Button.displayName = 'Button';

Ошибка машинописного текста:

Type '{ className?: string | undefined; title?: string | undefined; defaultChecked?: boolean | undefined; defaultValue?: string | number | readonly string[] | undefined; suppressContentEditableWarning?: boolean | undefined; ... 314 more ...; ref: ForwardedRef<...>; }' is not assignable to type 'Omit<SlotProps & RefAttributes<HTMLElement> & MotionProps, "ref">'.
  Types of property 'style' are incompatible.
    Type 'MotionStyle | undefined' is not assignable to type '(CSSProperties & MakeCustomValueType<{ outline?: MotionValue<number> | MotionValue<string> | MotionValue<any> | Outline<string | number> | undefined; ... 820 more ...; vectorEffect?: MotionValue<...> | ... 3 more ... | undefined; }> & MakeCustomValueType<...> & MakeCustomValueType<...> & MakeCustomValueType<...>) | ...'.
      Type 'MotionStyle' is not assignable to type '(CSSProperties & MakeCustomValueType<{ outline?: MotionValue<number> | MotionValue<string> | MotionValue<any> | Outline<string | number> | undefined; ... 820 more ...; vectorEffect?: MotionValue<...> | ... 3 more ... | undefined; }> & MakeCustomValueType<...> & MakeCustomValueType<...> & MakeCustomValueType<...>) | ...'.
        Type 'MotionStyle' is not assignable to type 'CSSProperties & MakeCustomValueType<{ outline?: MotionValue<number> | MotionValue<string> | MotionValue<any> | Outline<string | number> | undefined; ... 820 more ...; vectorEffect?: MotionValue<...> | ... 3 more ... | undefined; }> & MakeCustomValueType<...> & MakeCustomValueType<...> & MakeCustomValueType<...>'.
          Type 'MotionStyle' is not assignable to type 'CSSProperties'.
            Types of property 'accentColor' are incompatible.
              Type 'MotionValue<number> | MotionValue<string> | CustomValueType | MotionValue<any> | AccentColor | undefined' is not assignable to type 'AccentColor | undefined'.
                Type 'MotionValue<number>' is not assignable to type 'AccentColor | undefined'.ts(2322)
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, у меня было немного свободного времени, чтобы изучить эту проблему, и мне удалось создать что-то, что работает.

Идея:

  1. Создайте 3 компонента:
    • PolymorphicButton является базовым компонентом, который отвечает только за предоставление элемента на основе реквизита asChild.
    • MotionPolymorphicButton является результатом обертывания PolymorphicButton с помощью Framer-Motion motion Компонента высшего порядка (HoC). Это позволяет нам определять анимацию в PolymorphicButton.
    • Button действует как HoC, который внедряет свойства анимации в MotionPolymorphicButton

Код:

interface PolymorphicButtonProps extends ComponentPropsWithoutRef<'button'> {
  asChild?: boolean;
}

// Forwarding the ref is mandatory for using the `motion` function,
// ensuring proper animation handling.
const PolymorphicButton = forwardRef<HTMLButtonElement, PolymorphicButtonProps>(
  ({ asChild = false, ...rest }, ref) => {
    const Comp = asChild ? Slot : 'button';

    return (
      <Comp
        ref = {ref}
        {...rest}
      />
    );
  }
);

PolymorphicButton.displayName = 'PolymorphicButton';

/* -----------------------------------------------------------------------------------------------*/

// Wrapping PolymorphicButton with `motion` avoids complex type handling,
// keeping the button polymorphic and animated.
const MotionPolymorphicButton = motion(PolymorphicButton);

/* -----------------------------------------------------------------------------------------------*/

// Define pre-configured motion props for the Button component
const buttonMotionProps = {
  whileTap: { scale: 0.9 },
} as const satisfies HTMLMotionProps<'button'>;

const isTargetAndTransition = (
  field: VariantLabels | TargetAndTransition
): field is TargetAndTransition =>
  typeof field !== 'string' && !Array.isArray(field);

interface ButtonProps
  extends ComponentPropsWithoutRef<typeof MotionPolymorphicButton> {
  disableDefaultAnimations?: boolean;
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
  // Merges pre-configured whileTap behavior with user props
  const whileTap = useMemo<
    VariantLabels | TargetAndTransition | undefined
  >(() => {
    if (props.disableDefaultAnimations) {
      return props.whileTap;
    }
    if (props.whileTap === undefined) {
      return buttonMotionProps.whileTap;
    }
    return isTargetAndTransition(props.whileTap)
      ? { ...buttonMotionProps.whileTap, ...props.whileTap }
      : props.whileTap;
  }, [props.disableDefaultAnimations, props.whileTap]);

  return (
    <MotionPolymorphicButton
      {...buttonMotionProps}
      {...props}
      ref = {ref}
      whileTap = {whileTap}
    />
  );
});

Button.displayName = 'Button';

Другие вопросы по теме

Похожие вопросы

Почему PWA на основе реагирования, размещенное в моей службе приложений Azure, случайно начинает возвращать ошибку 500 для ВСЕХ вызовов POST (GET работает нормально)? Исправлено: Перезапустить браузер?
Не могу найти способ сделать условные реквизиты
Есть ли способ визуализировать HTML из строки в React без использования dangerouslySetInnerHTML или пакета React HTML Parser?
Ошибка теста Playwright: реквизиты возвращают разные значения в пользовательском интерфейсе тестирования Playwright и в реальном приложении React в браузере Chrome
TypeScript › Обновление импорта при перемещении файла: включено, не работает в VS Code
Эффект масштабирования при наведении изображения, отображение деталей изображения
React DatePicker не закрывается после выбора даты
Автономная библиотека веб-пакетов React
Становится ли изучение useEffect() менее важным для извлечения данных в React? Должны ли новички отдавать приоритет изучению React Query, а не useEffect()?
Данные в модальном режиме не обновляются в React JS