react-pop-cards 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,43 +1,171 @@
1
- # Docs (Beta)
1
+ # react-pop-cards
2
2
 
3
- Official documentation on [react-pop-cards](https://react-pop-cards.vercel.app)
3
+ Animated pop card component for React with spring-based animations.
4
4
 
5
- ## How can you use it in a jsx file?
5
+ [![npm version](https://img.shields.io/npm/v/react-pop-cards.svg)](https://www.npmjs.com/package/react-pop-cards)
6
+ [![npm downloads](https://img.shields.io/npm/dw/react-pop-cards.svg)](https://www.npmjs.com/package/react-pop-cards)
6
7
 
7
- Here is an example:
8
+ > 📖 **[Live Playground](https://react-pop-cards.vercel.app)** — Try it in your browser
8
9
 
9
- ```jsx
10
- import { Card } from "react-pop-cards"
10
+ ## Installation
11
11
 
12
- const array = [
12
+ ```bash
13
+ # pnpm
14
+ pnpm add react-pop-cards
15
+
16
+ # npm
17
+ npm install react-pop-cards
18
+
19
+ # yarn
20
+ yarn add react-pop-cards
21
+
22
+ # bun
23
+ bun add react-pop-cards
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import { Card } from "react-pop-cards";
30
+
31
+ const data = [
13
32
  {
14
- "title": "Title1",
15
- "description": "Description1",
16
- "image": "https://placehold.co/600x400"
33
+ title: "Design",
34
+ description: "Beautiful UI components",
35
+ image: "https://placehold.co/600x400",
17
36
  },
18
37
  {
19
- "title": "Title2",
20
- "description": "Description2",
21
- "image": "https://placehold.co/600x400"
38
+ title: "Animate",
39
+ description: "Spring-based animations",
40
+ image: "https://placehold.co/600x400",
22
41
  },
23
42
  {
24
- "title": "Title3",
25
- "description": "Description3",
26
- "image": "https://placehold.co/600x400"
43
+ title: "Build",
44
+ description: "Production ready",
45
+ image: "https://placehold.co/600x400",
27
46
  },
28
47
  {
29
- "title": "Title4",
30
- "description": "Description4",
31
- "image": "https://placehold.co/600x400"
32
- }
33
- ]
34
-
35
- const myApp = () => {
36
- return (
37
- <Card data={array} disposition="LeftRight" isRounded=true tension={120} friction={10}/>
38
- )
48
+ title: "Ship",
49
+ description: "Lightweight bundle",
50
+ image: "https://placehold.co/600x400",
51
+ },
52
+ ];
53
+
54
+ export default function App() {
55
+ return (
56
+ <Card
57
+ data={data}
58
+ disposition="LeftRight"
59
+ isRounded
60
+ tension={120}
61
+ friction={10}
62
+ bgColor="#e5e7eb"
63
+ />
64
+ );
65
+ }
66
+ ```
67
+
68
+ ## Props
69
+
70
+ | Prop | Type | Default | Description |
71
+ | ------------- | ---------------------------------------------------------- | -------------- | ------------------------------------------------ |
72
+ | `data` | `CardItem[]` | **(required)** | Array of exactly 4 card items |
73
+ | `disposition` | `"LeftRight" \| "RightLeft" \| "TopBottom" \| "BottomTop"` | `"LeftRight"` | Layout direction |
74
+ | `bgColor` | `string` | `"#e5e7eb"` | Background color (hex) |
75
+ | `isRounded` | `boolean` | `false` | Rounded corners on cards |
76
+ | `tension` | `number` | `120` | Spring animation tension (higher = snappier) |
77
+ | `friction` | `number` | `10` | Spring animation friction (higher = more damped) |
78
+
79
+ ### `CardItem`
80
+
81
+ ```ts
82
+ interface CardItem {
83
+ title: string;
84
+ description: string;
85
+ image?: string;
39
86
  }
87
+ ```
88
+
89
+ ## TypeScript
90
+
91
+ Full TypeScript support out of the box. Types are exported from the package:
92
+
93
+ ```ts
94
+ import { Card } from "react-pop-cards";
95
+ import type { CardProps, CardItem } from "react-pop-cards";
96
+ ```
97
+
98
+ ## Development
99
+
100
+ ```bash
101
+ # Install dependencies
102
+ pnpm install
40
103
 
41
- export default myApp
104
+ # Start dev server (docs playground)
105
+ pnpm dev
42
106
 
107
+ # Build the library only
108
+ pnpm build
109
+
110
+ # Build the docs site (for Vercel deployment)
111
+ pnpm build:vercel
112
+
113
+ # Type check
114
+ pnpm typecheck
43
115
  ```
116
+
117
+ ## Publishing to npm
118
+
119
+ 1. **Login** to your npm account (one-time setup):
120
+
121
+ ```bash
122
+ npm login
123
+ ```
124
+
125
+ 2. **Build** the library:
126
+
127
+ ```bash
128
+ pnpm build
129
+ ```
130
+
131
+ 3. **Verify** the package contents before publishing:
132
+
133
+ ```bash
134
+ npm pack --dry-run
135
+ ```
136
+
137
+ This should show only `dist/` and `README.md` (as defined in `"files"` in package.json).
138
+
139
+ 4. **Publish**:
140
+
141
+ ```bash
142
+ npm publish
143
+ ```
144
+
145
+ For the first publish of a new package name, you may need:
146
+
147
+ ```bash
148
+ npm publish --access public
149
+ ```
150
+
151
+ 5. **Bump version** for future releases:
152
+
153
+ ```bash
154
+ # Patch release (1.0.0 → 1.0.1) — bug fixes
155
+ npm version patch
156
+
157
+ # Minor release (1.0.0 → 1.1.0) — new features
158
+ npm version minor
159
+
160
+ # Major release (1.0.0 → 2.0.0) — breaking changes
161
+ npm version major
162
+
163
+ # Then publish
164
+ npm publish
165
+ ```
166
+
167
+ > **Tip:** Always run `pnpm build` before `npm publish` to ensure `dist/` is up to date.
168
+
169
+ ## License
170
+
171
+ MIT © [thony32](https://github.com/thony32)
@@ -0,0 +1,24 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface CardItem {
4
+ title: string;
5
+ description: string;
6
+ image?: string;
7
+ }
8
+ interface CardProps {
9
+ /** Array of exactly 4 card items to display */
10
+ data: CardItem[];
11
+ /** Background color of the cards (hex string) */
12
+ bgColor?: string;
13
+ /** Layout disposition for the card grid */
14
+ disposition?: 'LeftRight' | 'RightLeft' | 'TopBottom' | 'BottomTop';
15
+ /** Whether cards have rounded corners */
16
+ isRounded?: boolean;
17
+ /** Spring animation tension (higher = snappier) */
18
+ tension?: number;
19
+ /** Spring animation friction (higher = more damped) */
20
+ friction?: number;
21
+ }
22
+ declare function Card({ data, bgColor, disposition, isRounded, tension, friction }: CardProps): react_jsx_runtime.JSX.Element;
23
+
24
+ export { Card, type CardItem, type CardProps };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,24 @@
1
- interface CardDataItem {
2
- title: string
3
- description: string
4
- image?: string
5
- }
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
6
2
 
3
+ interface CardItem {
4
+ title: string;
5
+ description: string;
6
+ image?: string;
7
+ }
7
8
  interface CardProps {
8
- data: CardDataItem[]
9
- bgColor?: string
10
- disposition?: "LeftRight" | "RightLeft" | "TopBottom" | "BottomTop"
11
- isRounded?: boolean
12
- tension?: number
13
- friction?: number
9
+ /** Array of exactly 4 card items to display */
10
+ data: CardItem[];
11
+ /** Background color of the cards (hex string) */
12
+ bgColor?: string;
13
+ /** Layout disposition for the card grid */
14
+ disposition?: 'LeftRight' | 'RightLeft' | 'TopBottom' | 'BottomTop';
15
+ /** Whether cards have rounded corners */
16
+ isRounded?: boolean;
17
+ /** Spring animation tension (higher = snappier) */
18
+ tension?: number;
19
+ /** Spring animation friction (higher = more damped) */
20
+ friction?: number;
14
21
  }
22
+ declare function Card({ data, bgColor, disposition, isRounded, tension, friction }: CardProps): react_jsx_runtime.JSX.Element;
15
23
 
16
- export declare const Card: React.FC<CardProps>
24
+ export { Card, type CardItem, type CardProps };
package/dist/index.js CHANGED
@@ -1,43 +1,2 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- Object.defineProperty(exports, "Card", {
7
- enumerable: true,
8
- get: function get() {
9
- return _Card.default;
10
- }
11
- });
12
- var _react = _interopRequireDefault(require("react"));
13
- var _client = _interopRequireDefault(require("react-dom/client"));
14
- require("./index.css");
15
- var _Card = _interopRequireDefault(require("./lib/components/Card"));
16
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
- // import Sandbox from "./Sandbox"
18
-
19
- const array = [{
20
- title: "Title1",
21
- description: "Description1",
22
- image: "https://placehold.co/600x400"
23
- }, {
24
- title: "Title2",
25
- description: "Description2",
26
- image: "https://placehold.co/600x400"
27
- }, {
28
- title: "Title3",
29
- description: "Description3",
30
- image: "https://placehold.co/600x400"
31
- }, {
32
- title: "Title4",
33
- description: "Description4",
34
- image: "https://placehold.co/600x400"
35
- }];
36
- _client.default.createRoot(document.getElementById("root")).render( /*#__PURE__*/_react.default.createElement(_react.default.StrictMode, null, /*#__PURE__*/_react.default.createElement(_Card.default, {
37
- data: array,
38
- disposition: "LeftRight",
39
- isRounded: false,
40
- tension: 120,
41
- friction: 10,
42
- bgColor: "#e5e7eb"
43
- })));
1
+ 'use strict';var web=require('@react-spring/web'),j=require('chroma-js'),react=require('react'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var j__default=/*#__PURE__*/_interopDefault(j);function v(t){let[s,i]=react.useState(()=>typeof window>"u"?false:window.matchMedia(t).matches);return react.useEffect(()=>{let c=window.matchMedia(t),n=m=>{i(m.matches);};return i(c.matches),c.addEventListener("change",n),()=>c.removeEventListener("change",n)},[t]),s}function o(...t){return t.filter(Boolean).join(" ")}var k="#e5e7eb",B=120,D=10,O={width:"6rem",height:"6rem"},F={width:"20rem",height:"20rem"},Q={width:"11rem",height:"11rem"},z={0:"flex justify-end items-end",1:"flex items-end",2:"flex justify-end",3:"flex"};function P({data:t,bgColor:s=k,disposition:i="LeftRight",isRounded:c=false,tension:n=B,friction:m=D}){let l=v("(max-width: 640px)"),w=react.useRef(null),[d,N]=react.useState(t[0]?.title??""),b=react.useMemo(()=>{try{return j__default.default(s).luminance()<.5?"#e5e5e5":"#1c2531"}catch{return "#1c2531"}},[s]),f=react.useCallback(e=>e===d?l?Q:F:O,[d,l]),[E,g]=web.useSprings(t.length,e=>({width:f(t[e].title).width,height:f(t[e].title).height,backgroundColor:s,config:{tension:n,friction:m}}));react.useEffect(()=>{g.start(e=>({width:f(t[e].title).width,height:f(t[e].title).height,backgroundColor:s,config:{tension:n,friction:m}}));},[d,s,n,m,l,t,g,f]);let p=react.useCallback(e=>{N(e);},[]),L=react.useMemo(()=>{let e="flex flex-col justify-center items-center gap-8 h-full";switch(i){case "LeftRight":return l?e:"grid grid-cols-5 h-full";case "RightLeft":return l?"flex flex-col-reverse justify-center items-center gap-8 h-full":"grid grid-cols-5 h-full";case "TopBottom":return e;case "BottomTop":return "flex flex-col-reverse justify-center items-center gap-8 h-full";default:return "grid grid-cols-5 h-full"}},[i,l]),x=c?"rounded-2xl":"rounded-none";return jsxRuntime.jsxs("div",{className:L,children:[jsxRuntime.jsx("div",{className:o("col-span-3 flex justify-center items-center duration-100",i==="LeftRight"?"order-1":"order-2"),children:jsxRuntime.jsx("div",{className:"grid grid-cols-2 gap-2",children:t.map((e,u)=>{let a=d===e.title;return jsxRuntime.jsx("div",{className:z[u]??"flex",children:jsxRuntime.jsx("div",{style:{color:b},children:jsxRuntime.jsx(web.animated.div,{ref:a?w:void 0,style:E[u],onClick:()=>p(e.title),className:o("cursor-pointer duration-100",x,a?"px-6 py-4":"flex justify-center items-center"),children:jsxRuntime.jsxs("div",{className:a?"min-h-full":"max-sm:space-y-1 space-y-3",children:[jsxRuntime.jsxs("div",{className:"flex max-sm:flex-col-reverse justify-between items-center",children:[jsxRuntime.jsx("label",{className:o("capitalize font-bold duration-100",a?"max-sm:text-xl text-5xl":"max-sm:text-xs text-base"),children:e.title}),a&&e.image&&jsxRuntime.jsx("img",{className:"max-sm:w-12 max-sm:h-12 w-20 h-20",src:e.image,alt:`${e.title} card`})]}),a&&jsxRuntime.jsx("p",{className:"line-clamp-[8] text-justify max-sm:text-xs",children:e.description})]})})})},e.title)})})}),jsxRuntime.jsx("div",{className:o("col-span-2 gap-4 flex justify-center items-center duration-100",i==="RightLeft"?"order-1":"order-2"),children:t.map(e=>{let u=d===e.title;return jsxRuntime.jsx("div",{onClick:()=>p(e.title),className:o("bg-base-100 hover:scale-125 duration-200 cursor-pointer flex justify-center items-center","max-sm:w-[4rem] max-sm:h-[4rem] w-[5rem] h-[5rem] shadow-lg",x,u?"scale-105 shadow-xl":"scale-90"),children:jsxRuntime.jsx("label",{className:"text-center text-xs capitalize",children:e.title})},e.title)})})]})}exports.Card=P;//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/hooks/useMediaQuery.ts","../src/lib/utils/cn.ts","../src/lib/components/Card.tsx"],"names":["useMediaQuery","query","matches","setMatches","useState","useEffect","mediaQuery","handleChange","event","cn","classes","DEFAULT_BG_COLOR","DEFAULT_TENSION","DEFAULT_FRICTION","MINI_SIZE","ACTIVE_SIZE","ACTIVE_SIZE_MOBILE","CARD_ALIGNMENT_CLASSES","Card","data","bgColor","disposition","isRounded","tension","friction","isMobile","activeCardRef","useRef","activeTitle","setActiveTitle","textColor","useMemo","chroma","getSize","useCallback","title","springs","api","useSprings","index","handleCardClick","dispositionClass","verticalCenter","cornerClass","jsxs","jsx","item","isActive","animated"],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect, useState } from 'react'\n\n/**\n * Custom hook that listens to a CSS media query and returns whether it matches.\n * Replaces `react-responsive` with zero dependencies.\n *\n * @example\n * const isMobile = useMediaQuery(\"(max-width: 640px)\");\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n })\n\n useEffect(() => {\n const mediaQuery = window.matchMedia(query)\n\n const handleChange = (event: MediaQueryListEvent) => {\n setMatches(event.matches)\n }\n\n // Set initial value\n setMatches(mediaQuery.matches)\n\n mediaQuery.addEventListener('change', handleChange)\n return () => mediaQuery.removeEventListener('change', handleChange)\n }, [query])\n\n return matches\n}\n","/**\n * Utility for conditionally joining class names together.\n */\nexport function cn(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n","import { animated, useSprings } from '@react-spring/web'\nimport chroma from 'chroma-js'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useMediaQuery } from '../hooks/useMediaQuery'\nimport { cn } from '../utils/cn'\n\n// ─── Types ──────────────────────────────────────────────────────────\n\nexport interface CardItem {\n title: string\n description: string\n image?: string\n}\n\nexport interface CardProps {\n /** Array of exactly 4 card items to display */\n data: CardItem[]\n /** Background color of the cards (hex string) */\n bgColor?: string\n /** Layout disposition for the card grid */\n disposition?: 'LeftRight' | 'RightLeft' | 'TopBottom' | 'BottomTop'\n /** Whether cards have rounded corners */\n isRounded?: boolean\n /** Spring animation tension (higher = snappier) */\n tension?: number\n /** Spring animation friction (higher = more damped) */\n friction?: number\n}\n\n// ─── Constants ──────────────────────────────────────────────────────\n\nconst DEFAULT_BG_COLOR = '#e5e7eb'\nconst DEFAULT_TENSION = 120\nconst DEFAULT_FRICTION = 10\n\nconst MINI_SIZE = { width: '6rem', height: '6rem' }\nconst ACTIVE_SIZE = { width: '20rem', height: '20rem' }\nconst ACTIVE_SIZE_MOBILE = { width: '11rem', height: '11rem' }\n\nconst CARD_ALIGNMENT_CLASSES: Record<number, string> = {\n 0: 'flex justify-end items-end',\n 1: 'flex items-end',\n 2: 'flex justify-end',\n 3: 'flex'\n}\n\n// ─── Component ──────────────────────────────────────────────────────\n\nexport function Card({ data, bgColor = DEFAULT_BG_COLOR, disposition = 'LeftRight', isRounded = false, tension = DEFAULT_TENSION, friction = DEFAULT_FRICTION }: CardProps) {\n const isMobile = useMediaQuery('(max-width: 640px)')\n const activeCardRef = useRef<HTMLDivElement>(null)\n\n const [activeTitle, setActiveTitle] = useState(data[0]?.title ?? '')\n\n // Derive text color from background luminance\n const textColor = useMemo(() => {\n try {\n return chroma(bgColor).luminance() < 0.5 ? '#e5e5e5' : '#1c2531'\n } catch {\n return '#1c2531'\n }\n }, [bgColor])\n\n // Compute sizes for each card\n const getSize = useCallback(\n (title: string) => {\n if (title === activeTitle) {\n return isMobile ? ACTIVE_SIZE_MOBILE : ACTIVE_SIZE\n }\n return MINI_SIZE\n },\n [activeTitle, isMobile]\n )\n\n // Use react-spring's useSprings to avoid conditional hook calls\n const [springs, api] = useSprings(data.length, (index) => ({\n width: getSize(data[index].title).width,\n height: getSize(data[index].title).height,\n backgroundColor: bgColor,\n config: { tension, friction }\n }))\n\n // Update springs when state changes\n useEffect(() => {\n api.start((index) => ({\n width: getSize(data[index].title).width,\n height: getSize(data[index].title).height,\n backgroundColor: bgColor,\n config: { tension, friction }\n }))\n }, [activeTitle, bgColor, tension, friction, isMobile, data, api, getSize])\n\n const handleCardClick = useCallback((title: string) => {\n setActiveTitle(title)\n }, [])\n\n // Disposition class mapping\n const dispositionClass = useMemo(() => {\n const verticalCenter = 'flex flex-col justify-center items-center gap-8 h-full'\n\n switch (disposition) {\n case 'LeftRight':\n return isMobile ? verticalCenter : 'grid grid-cols-5 h-full'\n case 'RightLeft':\n return isMobile ? 'flex flex-col-reverse justify-center items-center gap-8 h-full' : 'grid grid-cols-5 h-full'\n case 'TopBottom':\n return verticalCenter\n case 'BottomTop':\n return 'flex flex-col-reverse justify-center items-center gap-8 h-full'\n default:\n return 'grid grid-cols-5 h-full'\n }\n }, [disposition, isMobile])\n\n const cornerClass = isRounded ? 'rounded-2xl' : 'rounded-none'\n\n return (\n <div className={dispositionClass}>\n {/* Main card grid */}\n <div className={cn('col-span-3 flex justify-center items-center duration-100', disposition === 'LeftRight' ? 'order-1' : 'order-2')}>\n <div className=\"grid grid-cols-2 gap-2\">\n {data.map((item, index) => {\n const isActive = activeTitle === item.title\n\n return (\n <div key={item.title} className={CARD_ALIGNMENT_CLASSES[index] ?? 'flex'}>\n <div style={{ color: textColor }}>\n <animated.div\n ref={isActive ? activeCardRef : undefined}\n style={springs[index]}\n onClick={() => handleCardClick(item.title)}\n className={cn('cursor-pointer duration-100', cornerClass, isActive ? 'px-6 py-4' : 'flex justify-center items-center')}\n >\n <div className={isActive ? 'min-h-full' : 'max-sm:space-y-1 space-y-3'}>\n <div className=\"flex max-sm:flex-col-reverse justify-between items-center\">\n <label className={cn('capitalize font-bold duration-100', isActive ? 'max-sm:text-xl text-5xl' : 'max-sm:text-xs text-base')}>{item.title}</label>\n {isActive && item.image && <img className=\"max-sm:w-12 max-sm:h-12 w-20 h-20\" src={item.image} alt={`${item.title} card`} />}\n </div>\n {isActive && <p className=\"line-clamp-[8] text-justify max-sm:text-xs\">{item.description}</p>}\n </div>\n </animated.div>\n </div>\n </div>\n )\n })}\n </div>\n </div>\n\n {/* Mini card navigation */}\n <div className={cn('col-span-2 gap-4 flex justify-center items-center duration-100', disposition === 'RightLeft' ? 'order-1' : 'order-2')}>\n {data.map((item) => {\n const isActive = activeTitle === item.title\n\n return (\n <div\n key={item.title}\n onClick={() => handleCardClick(item.title)}\n className={cn(\n 'bg-base-100 hover:scale-125 duration-200 cursor-pointer flex justify-center items-center',\n 'max-sm:w-[4rem] max-sm:h-[4rem] w-[5rem] h-[5rem] shadow-lg',\n cornerClass,\n isActive ? 'scale-105 shadow-xl' : 'scale-90'\n )}\n >\n <label className=\"text-center text-xs capitalize\">{item.title}</label>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import {useSprings,animated}from'@react-spring/web';import j from'chroma-js';import {useRef,useState,useMemo,useCallback,useEffect}from'react';import {jsxs,jsx}from'react/jsx-runtime';function v(t){let[s,i]=useState(()=>typeof window>"u"?false:window.matchMedia(t).matches);return useEffect(()=>{let c=window.matchMedia(t),n=m=>{i(m.matches);};return i(c.matches),c.addEventListener("change",n),()=>c.removeEventListener("change",n)},[t]),s}function o(...t){return t.filter(Boolean).join(" ")}var k="#e5e7eb",B=120,D=10,O={width:"6rem",height:"6rem"},F={width:"20rem",height:"20rem"},Q={width:"11rem",height:"11rem"},z={0:"flex justify-end items-end",1:"flex items-end",2:"flex justify-end",3:"flex"};function P({data:t,bgColor:s=k,disposition:i="LeftRight",isRounded:c=false,tension:n=B,friction:m=D}){let l=v("(max-width: 640px)"),w=useRef(null),[d,N]=useState(t[0]?.title??""),b=useMemo(()=>{try{return j(s).luminance()<.5?"#e5e5e5":"#1c2531"}catch{return "#1c2531"}},[s]),f=useCallback(e=>e===d?l?Q:F:O,[d,l]),[E,g]=useSprings(t.length,e=>({width:f(t[e].title).width,height:f(t[e].title).height,backgroundColor:s,config:{tension:n,friction:m}}));useEffect(()=>{g.start(e=>({width:f(t[e].title).width,height:f(t[e].title).height,backgroundColor:s,config:{tension:n,friction:m}}));},[d,s,n,m,l,t,g,f]);let p=useCallback(e=>{N(e);},[]),L=useMemo(()=>{let e="flex flex-col justify-center items-center gap-8 h-full";switch(i){case "LeftRight":return l?e:"grid grid-cols-5 h-full";case "RightLeft":return l?"flex flex-col-reverse justify-center items-center gap-8 h-full":"grid grid-cols-5 h-full";case "TopBottom":return e;case "BottomTop":return "flex flex-col-reverse justify-center items-center gap-8 h-full";default:return "grid grid-cols-5 h-full"}},[i,l]),x=c?"rounded-2xl":"rounded-none";return jsxs("div",{className:L,children:[jsx("div",{className:o("col-span-3 flex justify-center items-center duration-100",i==="LeftRight"?"order-1":"order-2"),children:jsx("div",{className:"grid grid-cols-2 gap-2",children:t.map((e,u)=>{let a=d===e.title;return jsx("div",{className:z[u]??"flex",children:jsx("div",{style:{color:b},children:jsx(animated.div,{ref:a?w:void 0,style:E[u],onClick:()=>p(e.title),className:o("cursor-pointer duration-100",x,a?"px-6 py-4":"flex justify-center items-center"),children:jsxs("div",{className:a?"min-h-full":"max-sm:space-y-1 space-y-3",children:[jsxs("div",{className:"flex max-sm:flex-col-reverse justify-between items-center",children:[jsx("label",{className:o("capitalize font-bold duration-100",a?"max-sm:text-xl text-5xl":"max-sm:text-xs text-base"),children:e.title}),a&&e.image&&jsx("img",{className:"max-sm:w-12 max-sm:h-12 w-20 h-20",src:e.image,alt:`${e.title} card`})]}),a&&jsx("p",{className:"line-clamp-[8] text-justify max-sm:text-xs",children:e.description})]})})})},e.title)})})}),jsx("div",{className:o("col-span-2 gap-4 flex justify-center items-center duration-100",i==="RightLeft"?"order-1":"order-2"),children:t.map(e=>{let u=d===e.title;return jsx("div",{onClick:()=>p(e.title),className:o("bg-base-100 hover:scale-125 duration-200 cursor-pointer flex justify-center items-center","max-sm:w-[4rem] max-sm:h-[4rem] w-[5rem] h-[5rem] shadow-lg",x,u?"scale-105 shadow-xl":"scale-90"),children:jsx("label",{className:"text-center text-xs capitalize",children:e.title})},e.title)})})]})}export{P as Card};//# sourceMappingURL=index.mjs.map
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/hooks/useMediaQuery.ts","../src/lib/utils/cn.ts","../src/lib/components/Card.tsx"],"names":["useMediaQuery","query","matches","setMatches","useState","useEffect","mediaQuery","handleChange","event","cn","classes","DEFAULT_BG_COLOR","DEFAULT_TENSION","DEFAULT_FRICTION","MINI_SIZE","ACTIVE_SIZE","ACTIVE_SIZE_MOBILE","CARD_ALIGNMENT_CLASSES","Card","data","bgColor","disposition","isRounded","tension","friction","isMobile","activeCardRef","useRef","activeTitle","setActiveTitle","textColor","useMemo","chroma","getSize","useCallback","title","springs","api","useSprings","index","handleCardClick","dispositionClass","verticalCenter","cornerClass","jsxs","jsx","item","isActive","animated"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useEffect, useState } from 'react'\n\n/**\n * Custom hook that listens to a CSS media query and returns whether it matches.\n * Replaces `react-responsive` with zero dependencies.\n *\n * @example\n * const isMobile = useMediaQuery(\"(max-width: 640px)\");\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n })\n\n useEffect(() => {\n const mediaQuery = window.matchMedia(query)\n\n const handleChange = (event: MediaQueryListEvent) => {\n setMatches(event.matches)\n }\n\n // Set initial value\n setMatches(mediaQuery.matches)\n\n mediaQuery.addEventListener('change', handleChange)\n return () => mediaQuery.removeEventListener('change', handleChange)\n }, [query])\n\n return matches\n}\n","/**\n * Utility for conditionally joining class names together.\n */\nexport function cn(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n","import { animated, useSprings } from '@react-spring/web'\nimport chroma from 'chroma-js'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useMediaQuery } from '../hooks/useMediaQuery'\nimport { cn } from '../utils/cn'\n\n// ─── Types ──────────────────────────────────────────────────────────\n\nexport interface CardItem {\n title: string\n description: string\n image?: string\n}\n\nexport interface CardProps {\n /** Array of exactly 4 card items to display */\n data: CardItem[]\n /** Background color of the cards (hex string) */\n bgColor?: string\n /** Layout disposition for the card grid */\n disposition?: 'LeftRight' | 'RightLeft' | 'TopBottom' | 'BottomTop'\n /** Whether cards have rounded corners */\n isRounded?: boolean\n /** Spring animation tension (higher = snappier) */\n tension?: number\n /** Spring animation friction (higher = more damped) */\n friction?: number\n}\n\n// ─── Constants ──────────────────────────────────────────────────────\n\nconst DEFAULT_BG_COLOR = '#e5e7eb'\nconst DEFAULT_TENSION = 120\nconst DEFAULT_FRICTION = 10\n\nconst MINI_SIZE = { width: '6rem', height: '6rem' }\nconst ACTIVE_SIZE = { width: '20rem', height: '20rem' }\nconst ACTIVE_SIZE_MOBILE = { width: '11rem', height: '11rem' }\n\nconst CARD_ALIGNMENT_CLASSES: Record<number, string> = {\n 0: 'flex justify-end items-end',\n 1: 'flex items-end',\n 2: 'flex justify-end',\n 3: 'flex'\n}\n\n// ─── Component ──────────────────────────────────────────────────────\n\nexport function Card({ data, bgColor = DEFAULT_BG_COLOR, disposition = 'LeftRight', isRounded = false, tension = DEFAULT_TENSION, friction = DEFAULT_FRICTION }: CardProps) {\n const isMobile = useMediaQuery('(max-width: 640px)')\n const activeCardRef = useRef<HTMLDivElement>(null)\n\n const [activeTitle, setActiveTitle] = useState(data[0]?.title ?? '')\n\n // Derive text color from background luminance\n const textColor = useMemo(() => {\n try {\n return chroma(bgColor).luminance() < 0.5 ? '#e5e5e5' : '#1c2531'\n } catch {\n return '#1c2531'\n }\n }, [bgColor])\n\n // Compute sizes for each card\n const getSize = useCallback(\n (title: string) => {\n if (title === activeTitle) {\n return isMobile ? ACTIVE_SIZE_MOBILE : ACTIVE_SIZE\n }\n return MINI_SIZE\n },\n [activeTitle, isMobile]\n )\n\n // Use react-spring's useSprings to avoid conditional hook calls\n const [springs, api] = useSprings(data.length, (index) => ({\n width: getSize(data[index].title).width,\n height: getSize(data[index].title).height,\n backgroundColor: bgColor,\n config: { tension, friction }\n }))\n\n // Update springs when state changes\n useEffect(() => {\n api.start((index) => ({\n width: getSize(data[index].title).width,\n height: getSize(data[index].title).height,\n backgroundColor: bgColor,\n config: { tension, friction }\n }))\n }, [activeTitle, bgColor, tension, friction, isMobile, data, api, getSize])\n\n const handleCardClick = useCallback((title: string) => {\n setActiveTitle(title)\n }, [])\n\n // Disposition class mapping\n const dispositionClass = useMemo(() => {\n const verticalCenter = 'flex flex-col justify-center items-center gap-8 h-full'\n\n switch (disposition) {\n case 'LeftRight':\n return isMobile ? verticalCenter : 'grid grid-cols-5 h-full'\n case 'RightLeft':\n return isMobile ? 'flex flex-col-reverse justify-center items-center gap-8 h-full' : 'grid grid-cols-5 h-full'\n case 'TopBottom':\n return verticalCenter\n case 'BottomTop':\n return 'flex flex-col-reverse justify-center items-center gap-8 h-full'\n default:\n return 'grid grid-cols-5 h-full'\n }\n }, [disposition, isMobile])\n\n const cornerClass = isRounded ? 'rounded-2xl' : 'rounded-none'\n\n return (\n <div className={dispositionClass}>\n {/* Main card grid */}\n <div className={cn('col-span-3 flex justify-center items-center duration-100', disposition === 'LeftRight' ? 'order-1' : 'order-2')}>\n <div className=\"grid grid-cols-2 gap-2\">\n {data.map((item, index) => {\n const isActive = activeTitle === item.title\n\n return (\n <div key={item.title} className={CARD_ALIGNMENT_CLASSES[index] ?? 'flex'}>\n <div style={{ color: textColor }}>\n <animated.div\n ref={isActive ? activeCardRef : undefined}\n style={springs[index]}\n onClick={() => handleCardClick(item.title)}\n className={cn('cursor-pointer duration-100', cornerClass, isActive ? 'px-6 py-4' : 'flex justify-center items-center')}\n >\n <div className={isActive ? 'min-h-full' : 'max-sm:space-y-1 space-y-3'}>\n <div className=\"flex max-sm:flex-col-reverse justify-between items-center\">\n <label className={cn('capitalize font-bold duration-100', isActive ? 'max-sm:text-xl text-5xl' : 'max-sm:text-xs text-base')}>{item.title}</label>\n {isActive && item.image && <img className=\"max-sm:w-12 max-sm:h-12 w-20 h-20\" src={item.image} alt={`${item.title} card`} />}\n </div>\n {isActive && <p className=\"line-clamp-[8] text-justify max-sm:text-xs\">{item.description}</p>}\n </div>\n </animated.div>\n </div>\n </div>\n )\n })}\n </div>\n </div>\n\n {/* Mini card navigation */}\n <div className={cn('col-span-2 gap-4 flex justify-center items-center duration-100', disposition === 'RightLeft' ? 'order-1' : 'order-2')}>\n {data.map((item) => {\n const isActive = activeTitle === item.title\n\n return (\n <div\n key={item.title}\n onClick={() => handleCardClick(item.title)}\n className={cn(\n 'bg-base-100 hover:scale-125 duration-200 cursor-pointer flex justify-center items-center',\n 'max-sm:w-[4rem] max-sm:h-[4rem] w-[5rem] h-[5rem] shadow-lg',\n cornerClass,\n isActive ? 'scale-105 shadow-xl' : 'scale-90'\n )}\n >\n <label className=\"text-center text-xs capitalize\">{item.title}</label>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"]}
package/package.json CHANGED
@@ -1,73 +1,74 @@
1
1
  {
2
2
  "name": "react-pop-cards",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
+ "description": "Animated pop card component for React",
4
5
  "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
5
7
  "types": "dist/index.d.ts",
6
- "module": "dist/index.js",
7
- "keywords": [
8
- "card",
9
- "react",
10
- "useSpring",
11
- "tailwindcss"
12
- ],
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./styles.css": "./dist/index.css"
15
+ },
13
16
  "files": [
14
17
  "dist",
15
18
  "index.d.ts",
16
19
  "README.md"
17
20
  ],
21
+ "keywords": [
22
+ "card",
23
+ "react",
24
+ "animation",
25
+ "spring",
26
+ "tailwindcss",
27
+ "typescript"
28
+ ],
18
29
  "repository": {
19
30
  "type": "git",
20
31
  "url": "git+https://github.com/thony32/react-pop-cards.git"
21
32
  },
22
- "dependencies": {
23
- "@react-spring/web": "^9.7.3",
24
- "chroma-js": "^2.4.2",
25
- "core-js": "^3.36.0",
26
- "prop-types": "^15.8.1",
27
- "react": "^18.2.0",
28
- "react-best-gradient-color-picker": "^3.0.5",
29
- "react-color": "^2.19.3",
30
- "react-dom": "^18.2.0",
31
- "react-hot-toast": "^2.4.1",
32
- "react-responsive": "^9.0.2",
33
- "react-scripts": "5.0.1"
34
- },
33
+ "license": "MIT",
35
34
  "scripts": {
36
- "dev": "react-scripts start",
37
- "build": "rm -rf dist && NODE_ENV=production babel src/index.js --out-dir dist --copy-files && babel src/index.d.ts --out-dir dist --copy-files && babel src/lib/components/Card.js --out-dir dist/lib/components --copy-files && postcss src/index.css -o dist/index.css",
38
- "build:vercel": "react-scripts build",
39
- "test": "react-scripts test",
40
- "eject": "react-scripts eject"
35
+ "dev": "vite",
36
+ "build": "tsup",
37
+ "build:vercel": "vite build",
38
+ "typecheck": "tsc --noEmit",
39
+ "preview": "vite preview",
40
+ "format": "biome format --write",
41
+ "lint": "biome lint --write",
42
+ "check": "biome check --write"
41
43
  },
42
- "eslintConfig": {
43
- "extends": [
44
- "react-app",
45
- "react-app/jest"
46
- ]
44
+ "peerDependencies": {
45
+ "react": ">=19",
46
+ "react-dom": ">=19",
47
+ "tailwindcss": ">=4",
48
+ "typescript": ">=5",
49
+ "vite": ">=7"
47
50
  },
48
- "browserslist": {
49
- "production": [
50
- ">0.2%",
51
- "not dead",
52
- "not op_mini all"
53
- ],
54
- "development": [
55
- "last 1 chrome version",
56
- "last 1 firefox version",
57
- "last 1 safari version"
58
- ]
51
+ "dependencies": {
52
+ "@react-spring/web": "^10.0.3",
53
+ "chroma-js": "^3.1.2"
59
54
  },
60
55
  "devDependencies": {
61
- "@babel/cli": "^7.23.9",
62
- "@babel/core": "^7.23.9",
63
- "@babel/preset-env": "^7.23.9",
64
- "@babel/preset-react": "^7.23.3",
65
- "@monaco-editor/react": "^4.6.0",
66
- "autoprefixer": "^10.4.17",
67
- "daisyui": "^4.7.2",
68
- "postcss-cli": "^11.0.0",
69
- "react-countup": "^6.5.0",
70
- "react-typed": "^2.0.12",
71
- "tailwindcss": "^3.4.1"
56
+ "@biomejs/biome": "2.4.2",
57
+ "@monaco-editor/react": "^4.7.0",
58
+ "@tailwindcss/vite": "^4.2.0",
59
+ "@types/chroma-js": "^3.1.2",
60
+ "@types/node": "^25.3.0",
61
+ "@types/react": "^19.0.0",
62
+ "@types/react-dom": "^19.0.0",
63
+ "@vercel/analytics": "^1.6.1",
64
+ "@vitejs/plugin-react-swc": "^4.2.3",
65
+ "daisyui": "^5.5.18",
66
+ "react": "^19.0.0",
67
+ "react-best-gradient-color-picker": "^3.0.10",
68
+ "react-dom": "^19.0.0",
69
+ "tailwindcss": "^4.2.0",
70
+ "tsup": "^8.3.6",
71
+ "typescript": "^5",
72
+ "vite": "^7"
72
73
  }
73
74
  }