/** * Shared Animations tab body. * * The base swatch hover/interaction animation controls live here so every admin * shell has one Animations destination. Extension bundles can compose additional * sections around this body without duplicating the base controls. */ import React, { useState } from "react"; import { BaseTabProps } from "../types"; import { Toggle } from "../../components/Toggle"; import { CustomSelect } from "../../components/CustomSelect"; import { RangeSlider } from "../../components/RangeSlider"; import { HighlightableField } from "../../components/HighlightableField"; import type { SwatchAnimation } from "../../types"; import { useReducedMotion } from "../../../frontend/shared/useReducedMotion"; import { TooltipAnimationPreviewGrid } from "../../components/TooltipAnimationPreviewGrid"; import { QuantityAnimationsSection } from "./QuantityAnimationsSection"; export { QUANTITY_ANIMATION_OPTIONS } from "./QuantityAnimationsSection"; // Base swatch hover-animation types. Extension-only effects are intentionally // registered by the extension section instead of this shared picker. export const SWATCH_ANIMATION_OPTIONS = [ { value: "none", label: "None" }, { value: "scale", label: "Scale" }, { value: "lift", label: "Lift" }, { value: "bounce", label: "Bounce" }, { value: "jiggle", label: "Jiggle" }, { value: "wobble", label: "Wobble" }, { value: "flip", label: "Flip" }, { value: "glow", label: "Glow" }, ] as const; // Tooltip animation enum (free contract). Values must match the allow-list in // the PHP Sanitizer/Normalizer/FreeTierPolicy exactly. const TOOLTIP_ANIMATION_OPTIONS = [ { value: "none", label: "None" }, { value: "fade", label: "Fade" }, { value: "scale", label: "Scale" }, { value: "shift", label: "Shift" }, { value: "slide", label: "Slide" }, { value: "flip", label: "Flip" }, { value: "zoom", label: "Zoom" }, { value: "swing", label: "Swing" }, { value: "elastic", label: "Elastic" }, { value: "blur", label: "Blur" }, { value: "rotate", label: "Rotate" }, { value: "stretch", label: "Stretch" }, ] as const; // Base preview chips. Extension-only effects are previewed by extension UI. const PREVIEW_ANIMATIONS = [ "none", "scale", "lift", "bounce", "jiggle", "wobble", "flip", "glow", ] as const; /** * Animated preview box that respects the "Animate on Hover" trigger setting. * Scale/lift use CSS hover classes; the keyframe animations (bounce/jiggle/ * wobble/flip) toggle a JS-applied class while hovered. */ export function AnimatedPreviewBox({ animation, animationClass, hoverClass, activeClass, children, className = "", animateOnHover = true, style, }: { animation: string; animationClass: string; hoverClass?: string; activeClass?: string; children: React.ReactNode; className?: string; animateOnHover?: boolean; style?: React.CSSProperties; }) { const [isActive, setIsActive] = useState(false); // The keyframe classes are reset by CSS under prefers-reduced-motion, but the // scale/lift group-hover utilities are unconditional Tailwind classes, so the // demo cards must drop every motion class themselves. Hover/focus state and // the focus-visible ring keep working; only the movement is suppressed. const reducedMotion = useReducedMotion(); // scale/lift are driven by CSS hover classes; the rest use a JS-applied class. const usesHoverClass = ["scale", "lift"].includes(animation); const motionEnabled = animateOnHover && !reducedMotion; const isAnimated = motionEnabled && isActive; return ( ); } /** * Shared chip-animation preview grid + swatch animation select + hover toggle. */ export interface AnimationsTabProps extends BaseTabProps { /** Optional extension live-preview rendered inside the Tooltip Animations section. */ tooltipAnimationPreview?: React.ReactNode; /** Optional controls for the selected chip animation. */ chipAnimationControls?: React.ReactNode; navigateToField?: (target: { id: string; tabId: string; subTab?: string; }) => void; } export function AnimationsTab({ settings, setSettings, tooltipAnimationPreview, chipAnimationControls, adminColor, navigateToField, }: AnimationsTabProps) { // Resolve the saved value against the shared option set, coercing extension // values back to "lift" so this select always reflects a valid base choice. const swatchAnimation: SwatchAnimation = SWATCH_ANIMATION_OPTIONS.some( (option) => option.value === settings.animation_type, ) ? (settings.animation_type as SwatchAnimation) : "lift"; const animateOnHover = settings.swatch_animate_on_hover !== false; const tooltipsEnabled = settings.enable_tooltips !== false; return (
Hover/interaction animation played on swatches.
Focus or hover over the boxes to see the animation effects.
Animation played when swatch tooltips appear. Requires tooltips to be enabled (Tooltips tab).
{!tooltipsEnabled ? (Controls the animation when tooltips appear on hover.