diff --git a/package.json b/package.json index 46bbf826..6fc1ba4d 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,10 @@ "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.17", "classnames": "^2.5.1", + "clsx": "^2.1.0", "color": "^4.2.3", "dompurify": "^3.0.8", - "framer-motion": "^10.18.0", + "framer-motion": "^11.0.25", "localforage": "^1.10.0", "lodash": "^4.17.21", "million": "latest", @@ -71,6 +72,7 @@ "rimraf": "^5.0.5", "sortablejs": "^1.15.2", "swiper": "^11.1.0", + "tailwind-merge": "^2.2.2", "tailwindcss": "^3.4.1", "ts-loader": "^9.5.1", "typescript": "^5.3.3", diff --git a/src/interface/components/store/card.tsx b/src/interface/components/store/card.tsx new file mode 100644 index 00000000..0b4f1e6d --- /dev/null +++ b/src/interface/components/store/card.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { cn } from "../../utils/cn"; +import React, { + createContext, + useState, + useContext, + useRef, + useEffect, +} from "react"; + +const MouseEnterContext = createContext< + [boolean, React.Dispatch>] | undefined +>(undefined); + +export const CardContainer = ({ + children, + className, + containerClassName, +}: { + children?: React.ReactNode; + className?: string; + containerClassName?: string; +}) => { + const containerRef = useRef(null); + const [isMouseEntered, setIsMouseEntered] = useState(false); + + const handleMouseMove = (e: React.MouseEvent) => { + if (!containerRef.current) return; + const { left, top, width, height } = + containerRef.current.getBoundingClientRect(); + const x = (e.clientX - left - width / 2) / 25; + const y = (e.clientY - top - height / 2) / 25; + containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`; + }; + + const handleMouseEnter = (_: React.MouseEvent) => { + setIsMouseEntered(true); + if (!containerRef.current) return; + }; + + const handleMouseLeave = (_: React.MouseEvent) => { + if (!containerRef.current) return; + setIsMouseEntered(false); + containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`; + }; + return ( + +
+
+ {children} +
+
+
+ ); +}; + +export const CardBody = ({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) => { + return ( +
*]:[transform-style:preserve-3d]", + className + )} + > + {children} +
+ ); +}; + +export const CardItem = ({ + as: Tag = "div", + children, + className, + translateX = 0, + translateY = 0, + translateZ = 0, + rotateX = 0, + rotateY = 0, + rotateZ = 0, + ...rest +}: { + as?: React.ElementType; + children: React.ReactNode; + className?: string; + translateX?: number | string; + translateY?: number | string; + translateZ?: number | string; + rotateX?: number | string; + rotateY?: number | string; + rotateZ?: number | string; + [key: string]: any; +}) => { + const ref = useRef(null); + const [isMouseEntered] = useMouseEnter(); + + useEffect(() => { + handleAnimations(); + }, [isMouseEntered]); + + const handleAnimations = () => { + if (!ref.current) return; + if (isMouseEntered) { + ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`; + } else { + ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`; + } + }; + + return ( + + {children} + + ); +}; + +// Create a hook to use the context +export const useMouseEnter = () => { + const context = useContext(MouseEnterContext); + if (context === undefined) { + throw new Error("useMouseEnter must be used within a MouseEnterProvider"); + } + return context; +}; diff --git a/src/interface/pages/Store.tsx b/src/interface/pages/Store.tsx index f1b4aab1..66576c98 100644 --- a/src/interface/pages/Store.tsx +++ b/src/interface/pages/Store.tsx @@ -1,68 +1,83 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Swiper, SwiperSlide } from 'swiper/react'; -import { Scrollbar, Autoplay } from 'swiper/modules'; +import { Autoplay } from 'swiper/modules'; import Header from '../components/store/header'; +import { motion } from 'framer-motion'; import 'swiper/css'; import 'swiper/css/pagination'; import 'swiper/css/scrollbar'; import 'swiper/css/autoplay'; +import { CardBody, CardContainer, CardItem } from '../components/store/card'; +import { spring } from 'motion'; const Store = () => { const [searchTerm, setSearchTerm] = useState(''); const swiperCover = useRef(null); + const [filteredThemes, setFilteredThemes] = useState([]); + const coverThemes = [ - { - coverImage: 'https://via.placeholder.com/300x200', - name: 'Theme Name', - description: 'Theme description goes here.' - }, - { - coverImage: 'https://via.placeholder.com/300x200', - name: 'Theme Name', - description: 'Theme description goes here.' - }, - { - coverImage: 'https://via.placeholder.com/300x200', - name: 'Theme Name', - description: 'Theme description goes here.' - } - ] + { coverImage: 'https://source.unsplash.com/random', name: 'Ocean View', description: 'Feel the ocean breeze with this theme.' }, + { coverImage: 'https://source.unsplash.com/random?2', name: 'Mountain Majesty', description: 'Elevate your desktop to new heights.' }, + { coverImage: 'https://source.unsplash.com/random?3', name: 'Urban Explorer', description: 'Bring the city life to your screen.' } + ]; + + // Additional mocked themes for the grid with varied names + const gridThemes = [ + { image: 'https://source.unsplash.com/random', name: 'Serene Landscapes', description: 'Calm landscapes to soothe your soul.' }, + { image: 'https://source.unsplash.com/random?4', name: 'Cosmic Energy', description: 'Explore the outer space.' }, + { image: 'https://source.unsplash.com/random?5', name: 'Abstract Art', description: 'Artistic and abstract designs.' }, + { image: 'https://source.unsplash.com/random?6', name: 'Nature’s Wonders', description: 'The beauty of nature captured in one theme.' }, + { image: 'https://source.unsplash.com/random?7', name: 'Techie Vibes', description: 'For the tech enthusiasts.' }, + { image: 'https://source.unsplash.com/random?8', name: 'Cafe Culture', description: 'Experience the cafe culture on your screen.' }, + ]; + + useEffect(() => { + setFilteredThemes(gridThemes.filter(theme => + theme.name.toLowerCase().includes(searchTerm.toLowerCase()) + )); + }, [searchTerm]); return (
-
-
- console.log('slide change')} - onSwiper={(swiper) => console.log(swiper)} - > - { coverThemes.map((theme, index) => ( - - Theme Preview - - )) } - +
+
+ + + { coverThemes.map((theme, index) => ( + + Theme Preview + + )) } + + {/* pagination */}
@@ -81,30 +96,38 @@ const Store = () => {
+ +
+ {filteredThemes.map((theme, index) => ( + + + + {theme.name} + + + {theme.description} + + + Theme Preview + + + + + + + ))} +
-
- { - Array.from({ length: 30 }).map((_, index) => ( -
- Theme Preview -
-

- Theme Name -

-

- Theme description goes here. -

- -
-
))} -
); }; diff --git a/src/interface/utils/cn.ts b/src/interface/utils/cn.ts new file mode 100644 index 00000000..95b19a83 --- /dev/null +++ b/src/interface/utils/cn.ts @@ -0,0 +1,6 @@ +import { ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} \ No newline at end of file