fix: react-doctor use-lazy-motion 36→0 (m + LazyMotion in Providers), rerender-state-only-in-handlers 5→0 (WaterAdminClient loading), js-set-map-lookups 1→0 (regex in supabase), js-hoist-regexp 2→0 (helper funcs), rules-of-hooks error in QuickCartSheet (use onClose direct in handler)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { m as motion, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
const badgeConfig: Record<"best-seller" | "new" | "limited" | "organic", { label: string; bg: string; text: string; border: string }> = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useEffectEvent } from "react";
|
||||
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { m, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
|
||||
@@ -68,6 +68,44 @@ export default function QuickCartSheet({
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [open]);
|
||||
|
||||
// Mobile sheet drag state. Replaces framer-motion's `drag` prop with
|
||||
// pointer events so the `m` (lazy) component can be used everywhere.
|
||||
const dragStartYRef = useRef<number | null>(null);
|
||||
const dragDeltaRef = useRef(0);
|
||||
const dragVelocityRef = useRef(0);
|
||||
const lastDragTimeRef = useRef(0);
|
||||
const lastDragYRef = useRef(0);
|
||||
const [dragOffset, setDragOffset] = useState(0);
|
||||
|
||||
function handleDragPointerDown(e: React.PointerEvent) {
|
||||
dragStartYRef.current = e.clientY;
|
||||
lastDragYRef.current = e.clientY;
|
||||
lastDragTimeRef.current = performance.now();
|
||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||
}
|
||||
function handleDragPointerMove(e: React.PointerEvent) {
|
||||
if (dragStartYRef.current === null) return;
|
||||
const delta = Math.max(0, e.clientY - dragStartYRef.current);
|
||||
const now = performance.now();
|
||||
const dt = Math.max(1, now - lastDragTimeRef.current);
|
||||
dragVelocityRef.current = ((e.clientY - lastDragYRef.current) / dt) * 1000;
|
||||
lastDragYRef.current = e.clientY;
|
||||
lastDragTimeRef.current = now;
|
||||
dragDeltaRef.current = delta;
|
||||
setDragOffset(delta);
|
||||
}
|
||||
function handleDragPointerUp(e: React.PointerEvent) {
|
||||
if (dragStartYRef.current === null) return;
|
||||
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
|
||||
if (dragDeltaRef.current > 90 || dragVelocityRef.current > 480) {
|
||||
onClose();
|
||||
}
|
||||
dragStartYRef.current = null;
|
||||
dragDeltaRef.current = 0;
|
||||
dragVelocityRef.current = 0;
|
||||
setDragOffset(0);
|
||||
}
|
||||
|
||||
const displayName = productName ?? justAdded?.name ?? cart[0]?.name ?? "Your box";
|
||||
const addedAt = new Date();
|
||||
const headerLabel = justAddedLabel ?? "Just added";
|
||||
@@ -87,7 +125,7 @@ export default function QuickCartSheet({
|
||||
{open && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<motion.button
|
||||
<m.button
|
||||
type="button"
|
||||
aria-label="Close cart"
|
||||
onClick={onClose}
|
||||
@@ -99,7 +137,7 @@ export default function QuickCartSheet({
|
||||
/>
|
||||
|
||||
{/* Mobile: bottom sheet */}
|
||||
<motion.div
|
||||
<m.div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Quick cart"
|
||||
@@ -107,12 +145,11 @@ export default function QuickCartSheet({
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: "100%" }}
|
||||
transition={{ type: "spring", stiffness: 380, damping: 38, mass: 0.9 }}
|
||||
drag="y"
|
||||
dragConstraints={{ top: 0, bottom: 0 }}
|
||||
dragElastic={{ top: 0, bottom: 0.5 }}
|
||||
onDragEnd={(_, info) => {
|
||||
if (info.offset.y > 90 || info.velocity.y > 480) onClose();
|
||||
}}
|
||||
onPointerDown={handleDragPointerDown}
|
||||
onPointerMove={handleDragPointerMove}
|
||||
onPointerUp={handleDragPointerUp}
|
||||
onPointerCancel={handleDragPointerUp}
|
||||
style={{ y: dragOffset, touchAction: "none" }}
|
||||
className="lg:hidden fixed inset-x-0 bottom-0 z-[61] max-h-[88vh] rounded-t-[28px] bg-[#FAF6EA] shadow-[0_-24px_60px_-12px_rgba(26,77,46,0.35)] flex flex-col"
|
||||
>
|
||||
{/* Drag handle */}
|
||||
@@ -131,10 +168,10 @@ export default function QuickCartSheet({
|
||||
onCheckout={handleCheckout}
|
||||
onContinue={onClose}
|
||||
/>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
|
||||
{/* Desktop: right-side drawer */}
|
||||
<motion.aside
|
||||
<m.aside
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Quick cart"
|
||||
@@ -156,7 +193,7 @@ export default function QuickCartSheet({
|
||||
onContinue={onClose}
|
||||
showCloseButton
|
||||
/>
|
||||
</motion.aside>
|
||||
</m.aside>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -211,7 +248,7 @@ function SheetContent({
|
||||
{/* Header */}
|
||||
<div className="relative px-6 sm:px-8 pt-2 pb-5 border-b border-stone-900/10">
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.div
|
||||
<m.div
|
||||
initial={{ scale: 0.95, opacity: 0 , rotate: -45 }}
|
||||
animate={{ scale: 1, rotate: 0 }}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 18, delay: 0.05 }}
|
||||
@@ -220,7 +257,7 @@ function SheetContent({
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.22em] text-[#1A4D2E]/70">
|
||||
{headerLabel}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { m as motion, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
type StorefrontHeaderProps = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { motion, AnimatePresence, useScroll, useTransform } from "framer-motion";
|
||||
import { m as motion, AnimatePresence, useScroll, useTransform } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
import QuickCartSheet from "./QuickCartSheet";
|
||||
import StorefrontHeader from "./StorefrontHeader";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { m as motion } from "framer-motion";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { m as motion, useInView } from "framer-motion";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
|
||||
// ── Feature type & data ──────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user