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 +155 -27
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +20 -12
- package/dist/index.js +2 -43
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -53
- package/dist/index.css +0 -2568
- package/dist/lib/components/Card.js +0 -203
package/README.md
CHANGED
|
@@ -1,43 +1,171 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-pop-cards
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Animated pop card component for React with spring-based animations.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/react-pop-cards)
|
|
6
|
+
[](https://www.npmjs.com/package/react-pop-cards)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
> 📖 **[Live Playground](https://react-pop-cards.vercel.app)** — Try it in your browser
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
import { Card } from "react-pop-cards"
|
|
10
|
+
## Installation
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
title: "Design",
|
|
34
|
+
description: "Beautiful UI components",
|
|
35
|
+
image: "https://placehold.co/600x400",
|
|
17
36
|
},
|
|
18
37
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
title: "Animate",
|
|
39
|
+
description: "Spring-based animations",
|
|
40
|
+
image: "https://placehold.co/600x400",
|
|
22
41
|
},
|
|
23
42
|
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
title: "Build",
|
|
44
|
+
description: "Production ready",
|
|
45
|
+
image: "https://placehold.co/600x400",
|
|
27
46
|
},
|
|
28
47
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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)
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
24
|
+
export { Card, type CardItem, type CardProps };
|
package/dist/index.js
CHANGED
|
@@ -1,43 +1,2 @@
|
|
|
1
|
-
|
|
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": "
|
|
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
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
"
|
|
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": "
|
|
37
|
-
"build": "
|
|
38
|
-
"build:vercel": "
|
|
39
|
-
"
|
|
40
|
-
"
|
|
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
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": ">=19",
|
|
46
|
+
"react-dom": ">=19",
|
|
47
|
+
"tailwindcss": ">=4",
|
|
48
|
+
"typescript": ">=5",
|
|
49
|
+
"vite": ">=7"
|
|
47
50
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
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
|
-
"@
|
|
62
|
-
"@
|
|
63
|
-
"@
|
|
64
|
-
"@
|
|
65
|
-
"@
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"react-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
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
|
}
|