swipe-row 1.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 +143 -0
- package/dist/components/SwipeRow/SwipeRow.d.ts +29 -0
- package/dist/components/SwipeRow/index.d.ts +2 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/style.css +1 -0
- package/dist/swipe-row.cjs.js +2 -0
- package/dist/swipe-row.cjs.js.map +1 -0
- package/dist/swipe-row.es.js +58 -0
- package/dist/swipe-row.es.js.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# React Swipe Row Component 🚀
|
|
2
|
+
|
|
3
|
+
A highly customizable, iOS-style swipeable row component for React applications. Built with `framer-motion` for buttery-smooth spring physics animations. Perfect for creating lists with interactive hidden actions like "Archive", "Delete", or "Reply".
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ✨ Features
|
|
13
|
+
|
|
14
|
+
- **Fluid Animations:** Powered by [Framer Motion](https://www.framer.com/motion/) with native-feeling spring physics.
|
|
15
|
+
- **Dynamic Actions:** Pass an unlimited number of custom action buttons (Edit, Delete, Archive, etc.).
|
|
16
|
+
- **Automatic Styling:** Built-in basic styling with CSS Modules so it just works out of the box without polluting global scopes.
|
|
17
|
+
- **Dark Mode Support:** Automatically detects system preferences and adjusts action background colors.
|
|
18
|
+
- **Fully Typed:** Written in TypeScript for excellent Developer Experience and IntelliSense.
|
|
19
|
+
- **Zero Config:** Auto-calculates swipe distances based on your button widths.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 📦 Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install swipe-row framer-motion
|
|
27
|
+
# or
|
|
28
|
+
yarn add swipe-row framer-motion
|
|
29
|
+
# or
|
|
30
|
+
pnpm add swipe-row framer-motion
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
*Note: `framer-motion` is required as a peer dependency for animations.*
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 💻 Usage
|
|
38
|
+
|
|
39
|
+
First, import the component and the necessary CSS styles into your React file:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { SwipeRow } from 'swipe-row';
|
|
43
|
+
import 'swipe-row/style.css'; // Important for default styling!
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Basic Example (Single Action)
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
function BasicList() {
|
|
50
|
+
return (
|
|
51
|
+
<SwipeRow
|
|
52
|
+
actions={[
|
|
53
|
+
{
|
|
54
|
+
label: 'Delete',
|
|
55
|
+
onClick: () => console.log('Deleted!'),
|
|
56
|
+
backgroundColor: '#ef4444', // Tailwind red-500
|
|
57
|
+
}
|
|
58
|
+
]}
|
|
59
|
+
>
|
|
60
|
+
<div style={{ padding: '16px' }}>
|
|
61
|
+
Swipe me to the left!
|
|
62
|
+
</div>
|
|
63
|
+
</SwipeRow>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Advanced Example (Multiple Actions)
|
|
69
|
+
|
|
70
|
+
You can pass multiple action objects. The component will automatically calculate how far the user needs to swipe.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
function EmailList() {
|
|
74
|
+
const handleArchive = () => alert('Archived');
|
|
75
|
+
const handleReply = () => alert('Replying...');
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<SwipeRow
|
|
79
|
+
actions={[
|
|
80
|
+
{
|
|
81
|
+
label: 'Reply',
|
|
82
|
+
onClick: handleReply,
|
|
83
|
+
backgroundColor: '#3b82f6', // Tailwind blue-500
|
|
84
|
+
width: 80, // Default is 80px
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: 'Archive',
|
|
88
|
+
onClick: handleArchive,
|
|
89
|
+
backgroundColor: '#f59e0b', // Tailwind amber-500
|
|
90
|
+
width: 90,
|
|
91
|
+
}
|
|
92
|
+
]}
|
|
93
|
+
>
|
|
94
|
+
<div style={{ padding: '20px', borderBottom: '1px solid #eee' }}>
|
|
95
|
+
<h4>Job Application Updates</h4>
|
|
96
|
+
<p>You have successfully submitted your resume...</p>
|
|
97
|
+
</div>
|
|
98
|
+
</SwipeRow>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🛠Props
|
|
106
|
+
|
|
107
|
+
### `SwipeRow`
|
|
108
|
+
|
|
109
|
+
| Prop | Type | Default | Description |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| `children` | `React.ReactNode` | **Required** | The main visible content of your row. |
|
|
112
|
+
| `actions` | `SwipeAction[]` | **Required** | Array of action configurations to be revealed on swipe. |
|
|
113
|
+
| `maxSwipe` | `number` | `undefined` | Override the automatic swipe width calculation (px). |
|
|
114
|
+
|
|
115
|
+
### `SwipeAction` Object
|
|
116
|
+
|
|
117
|
+
Configure each button in the `actions` array:
|
|
118
|
+
|
|
119
|
+
| Property | Type | Default | Description |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| `label` | `React.ReactNode` | **Required** | The text or icon to show inside the button. |
|
|
122
|
+
| `onClick` | `() => void` | **Required** | Function to call when clicked. |
|
|
123
|
+
| `backgroundColor` | `string` | `'#64748b'` | CSS background color for the button. |
|
|
124
|
+
| `color` | `string` | `'white'` | CSS text color. |
|
|
125
|
+
| `width` | `number` | `80` | Width of the button in pixels. Used for auto element calculation. |
|
|
126
|
+
| `className` | `string` | `undefined` | Custom CSS class applied to the button element. |
|
|
127
|
+
| `ariaLabel` | `string` | `label` string value | For screen readers. |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🧪 Local Development
|
|
132
|
+
|
|
133
|
+
1. Clone the repository
|
|
134
|
+
2. Install dependencies: `npm install`
|
|
135
|
+
3. Start the Vite playground: `npm run dev`
|
|
136
|
+
4. Run unit tests: `npm run test`
|
|
137
|
+
5. Build the library: `npm run build`
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 📄 License
|
|
142
|
+
|
|
143
|
+
MIT © nahkar - Feel free to use in personal and commercial projects!
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type SwipeAction = {
|
|
2
|
+
/** The visual content of the button (e.g. text or icon) */
|
|
3
|
+
label: React.ReactNode;
|
|
4
|
+
/** Callback triggered when this action is clicked */
|
|
5
|
+
onClick: () => void;
|
|
6
|
+
/** Background color for this action button */
|
|
7
|
+
backgroundColor?: string;
|
|
8
|
+
/** Text color for this action button */
|
|
9
|
+
color?: string;
|
|
10
|
+
/** Width of the button. Defaults to 80px */
|
|
11
|
+
width?: number;
|
|
12
|
+
/** Additional CSS class for the button */
|
|
13
|
+
className?: string;
|
|
14
|
+
/** Accessibility label. Defaults to label if it is a string a fallback string */
|
|
15
|
+
ariaLabel?: string;
|
|
16
|
+
};
|
|
17
|
+
export type SwipeRowProps = {
|
|
18
|
+
/** The content to be displayed in the row */
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
/** An array of action objects to render behind the row */
|
|
21
|
+
actions: SwipeAction[];
|
|
22
|
+
/** Optional custom swipe threshold. Computed automatically if not provided. */
|
|
23
|
+
maxSwipe?: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* SwipeRow creates an iOS style swipeable row element that reveals generic actions behind it.
|
|
27
|
+
* Highly configurable and built for generic use across various projects.
|
|
28
|
+
*/
|
|
29
|
+
export declare function SwipeRow({ children, actions, maxSwipe }: SwipeRowProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SwipeRow';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components';
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._container_1y7jq_1{position:relative;overflow:hidden;border-radius:12px;background-color:#f3f4f6;touch-action:pan-y;-webkit-user-select:none;user-select:none;box-shadow:0 2px 8px #0000000d}._actionsBackground_1y7jq_11{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;justify-content:flex-end;background-color:#f3f4f6}._actionButton_1y7jq_19{display:flex;align-items:center;justify-content:center;border:none;font-family:inherit;font-size:14px;font-weight:500;cursor:pointer;transition:filter .2s}._actionButton_1y7jq_19:hover{filter:brightness(.9)}._foreground_1y7jq_35{position:relative;background-color:#fff;padding:16px;width:100%;box-sizing:border-box;border-radius:12px;box-shadow:-2px 0 10px #00000008}@media (prefers-color-scheme: dark){._container_1y7jq_1,._actionsBackground_1y7jq_11{background-color:#171717}._foreground_1y7jq_35{background-color:#262626;color:#fff}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react/jsx-runtime"),e=require("framer-motion"),u=require("react"),b="_container_1y7jq_1",p="_actionsBackground_1y7jq_11",_="_actionButton_1y7jq_19",j="_foreground_1y7jq_35",s={container:b,actionsBackground:p,actionButton:_,foreground:j};function h({children:l,actions:c,maxSwipe:i}){const o=e.useMotionValue(0),d=u.useRef(null),g=u.useMemo(()=>-c.reduce((n,r)=>n+(r.width||80),0),[c]),t=i!==void 0?i:g,f=e.useTransform(o,[t/2,0],[1,0]),m=()=>{o.get()<t/2?e.animate(o,t,{type:"spring",bounce:0,duration:.3}):e.animate(o,0,{type:"spring",bounce:0,duration:.3})};return a.jsxs("div",{ref:d,className:s.container,children:[a.jsx(e.motion.div,{style:{opacity:f},className:s.actionsBackground,children:c.map((n,r)=>{const y=typeof n.label=="string"?n.label:`Action ${r}`;return a.jsx("button",{onClick:n.onClick,className:`${s.actionButton} ${n.className||""}`.trim(),style:{width:n.width||80,backgroundColor:n.backgroundColor||"#64748b",color:n.color||"white"},"aria-label":n.ariaLabel||y,children:n.label},r)})}),a.jsx(e.motion.div,{drag:"x",dragConstraints:{left:t,right:0},dragElastic:.1,style:{x:o},onDragEnd:m,className:s.foreground,children:l})]})}exports.SwipeRow=h;
|
|
2
|
+
//# sourceMappingURL=swipe-row.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swipe-row.cjs.js","sources":["../src/components/SwipeRow/SwipeRow.tsx"],"sourcesContent":["import { motion, useMotionValue, useTransform, animate } from 'framer-motion';\nimport { useRef, useMemo } from 'react';\nimport styles from './SwipeRow.module.css';\n\nexport type SwipeAction = {\n /** The visual content of the button (e.g. text or icon) */\n label: React.ReactNode;\n /** Callback triggered when this action is clicked */\n onClick: () => void;\n /** Background color for this action button */\n backgroundColor?: string;\n /** Text color for this action button */\n color?: string;\n /** Width of the button. Defaults to 80px */\n width?: number;\n /** Additional CSS class for the button */\n className?: string;\n /** Accessibility label. Defaults to label if it is a string a fallback string */\n ariaLabel?: string;\n};\n\nexport type SwipeRowProps = {\n /** The content to be displayed in the row */\n children: React.ReactNode;\n /** An array of action objects to render behind the row */\n actions: SwipeAction[];\n /** Optional custom swipe threshold. Computed automatically if not provided. */\n maxSwipe?: number;\n};\n\n/**\n * SwipeRow creates an iOS style swipeable row element that reveals generic actions behind it.\n * Highly configurable and built for generic use across various projects.\n */\nexport function SwipeRow({ children, actions, maxSwipe }: SwipeRowProps) {\n const x = useMotionValue(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const calculatedMaxSwipe = useMemo(() => {\n return -actions.reduce((acc, action) => acc + (action.width || 80), 0);\n }, [actions]);\n\n const swipeDistance = maxSwipe !== undefined ? maxSwipe : calculatedMaxSwipe;\n\n const backgroundOpacity = useTransform(x, [swipeDistance / 2, 0], [1, 0]);\n\n const handleDragEnd = () => {\n const current = x.get();\n\n // Swipe threshold to snap open\n if (current < swipeDistance / 2) {\n animate(x, swipeDistance, { type: 'spring', bounce: 0, duration: 0.3 });\n } else {\n animate(x, 0, { type: 'spring', bounce: 0, duration: 0.3 });\n }\n };\n\n return (\n <div ref={containerRef} className={styles.container}>\n {/* ACTIONS BACKGROUND */}\n <motion.div\n style={{ opacity: backgroundOpacity }}\n className={styles.actionsBackground}\n >\n {actions.map((action, index) => {\n const defaultAria = typeof action.label === 'string' ? action.label : `Action ${index}`;\n return (\n <button\n key={index}\n onClick={action.onClick}\n className={`${styles.actionButton} ${action.className || ''}`.trim()}\n style={{\n width: action.width || 80,\n backgroundColor: action.backgroundColor || '#64748b', // Slate\n color: action.color || 'white',\n }}\n aria-label={action.ariaLabel || defaultAria}\n >\n {action.label}\n </button>\n );\n })}\n </motion.div>\n\n {/* FOREGROUND */}\n <motion.div\n drag=\"x\"\n dragConstraints={{ left: swipeDistance, right: 0 }}\n dragElastic={0.1}\n style={{ x }}\n onDragEnd={handleDragEnd}\n className={styles.foreground}\n >\n {children}\n </motion.div>\n </div>\n );\n}\n"],"names":["SwipeRow","children","actions","maxSwipe","x","useMotionValue","containerRef","useRef","calculatedMaxSwipe","useMemo","acc","action","swipeDistance","backgroundOpacity","useTransform","handleDragEnd","animate","styles","jsx","motion","index","defaultAria"],"mappings":"8UAkCO,SAASA,EAAS,CAAE,SAAAC,EAAU,QAAAC,EAAS,SAAAC,GAA2B,CACvE,MAAMC,EAAIC,EAAAA,eAAe,CAAC,EACpBC,EAAeC,EAAAA,OAAuB,IAAI,EAE1CC,EAAqBC,EAAAA,QAAQ,IAC1B,CAACP,EAAQ,OAAO,CAACQ,EAAKC,IAAWD,GAAOC,EAAO,OAAS,IAAK,CAAC,EACpE,CAACT,CAAO,CAAC,EAENU,EAAgBT,IAAa,OAAYA,EAAWK,EAEpDK,EAAoBC,EAAAA,aAAaV,EAAG,CAACQ,EAAgB,EAAG,CAAC,EAAG,CAAC,EAAG,CAAC,CAAC,EAElEG,EAAgB,IAAM,CACVX,EAAE,IAAA,EAGJQ,EAAgB,EAC5BI,UAAQZ,EAAGQ,EAAe,CAAE,KAAM,SAAU,OAAQ,EAAG,SAAU,GAAK,EAEtEI,UAAQZ,EAAG,EAAG,CAAE,KAAM,SAAU,OAAQ,EAAG,SAAU,GAAK,CAE9D,EAEA,cACG,MAAA,CAAI,IAAKE,EAAc,UAAWW,EAAO,UAExC,SAAA,CAAAC,EAAAA,IAACC,EAAAA,OAAO,IAAP,CACC,MAAO,CAAE,QAASN,CAAA,EAClB,UAAWI,EAAO,kBAEjB,SAAAf,EAAQ,IAAI,CAACS,EAAQS,IAAU,CAC9B,MAAMC,EAAc,OAAOV,EAAO,OAAU,SAAWA,EAAO,MAAQ,UAAUS,CAAK,GACrF,OACEF,EAAAA,IAAC,SAAA,CAEC,QAASP,EAAO,QAChB,UAAW,GAAGM,EAAO,YAAY,IAAIN,EAAO,WAAa,EAAE,GAAG,KAAA,EAC9D,MAAO,CACL,MAAOA,EAAO,OAAS,GACvB,gBAAiBA,EAAO,iBAAmB,UAC3C,MAAOA,EAAO,OAAS,OAAA,EAEzB,aAAYA,EAAO,WAAaU,EAE/B,SAAAV,EAAO,KAAA,EAVHS,CAAA,CAaX,CAAC,CAAA,CAAA,EAIHF,EAAAA,IAACC,EAAAA,OAAO,IAAP,CACC,KAAK,IACL,gBAAiB,CAAE,KAAMP,EAAe,MAAO,CAAA,EAC/C,YAAa,GACb,MAAO,CAAE,EAAAR,CAAA,EACT,UAAWW,EACX,UAAWE,EAAO,WAEjB,SAAAhB,CAAA,CAAA,CACH,EACF,CAEJ"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsxs as y, jsx as c } from "react/jsx-runtime";
|
|
2
|
+
import { useMotionValue as b, useTransform as _, motion as i, animate as l } from "framer-motion";
|
|
3
|
+
import { useRef as h, useMemo as k } from "react";
|
|
4
|
+
const B = "_container_1y7jq_1", w = "_actionsBackground_1y7jq_11", j = "_actionButton_1y7jq_19", C = "_foreground_1y7jq_35", e = {
|
|
5
|
+
container: B,
|
|
6
|
+
actionsBackground: w,
|
|
7
|
+
actionButton: j,
|
|
8
|
+
foreground: C
|
|
9
|
+
};
|
|
10
|
+
function x({ children: u, actions: a, maxSwipe: s }) {
|
|
11
|
+
const n = b(0), d = h(null), g = k(() => -a.reduce((o, r) => o + (r.width || 80), 0), [a]), t = s !== void 0 ? s : g, f = _(n, [t / 2, 0], [1, 0]), m = () => {
|
|
12
|
+
n.get() < t / 2 ? l(n, t, { type: "spring", bounce: 0, duration: 0.3 }) : l(n, 0, { type: "spring", bounce: 0, duration: 0.3 });
|
|
13
|
+
};
|
|
14
|
+
return /* @__PURE__ */ y("div", { ref: d, className: e.container, children: [
|
|
15
|
+
/* @__PURE__ */ c(
|
|
16
|
+
i.div,
|
|
17
|
+
{
|
|
18
|
+
style: { opacity: f },
|
|
19
|
+
className: e.actionsBackground,
|
|
20
|
+
children: a.map((o, r) => {
|
|
21
|
+
const p = typeof o.label == "string" ? o.label : `Action ${r}`;
|
|
22
|
+
return /* @__PURE__ */ c(
|
|
23
|
+
"button",
|
|
24
|
+
{
|
|
25
|
+
onClick: o.onClick,
|
|
26
|
+
className: `${e.actionButton} ${o.className || ""}`.trim(),
|
|
27
|
+
style: {
|
|
28
|
+
width: o.width || 80,
|
|
29
|
+
backgroundColor: o.backgroundColor || "#64748b",
|
|
30
|
+
// Slate
|
|
31
|
+
color: o.color || "white"
|
|
32
|
+
},
|
|
33
|
+
"aria-label": o.ariaLabel || p,
|
|
34
|
+
children: o.label
|
|
35
|
+
},
|
|
36
|
+
r
|
|
37
|
+
);
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
/* @__PURE__ */ c(
|
|
42
|
+
i.div,
|
|
43
|
+
{
|
|
44
|
+
drag: "x",
|
|
45
|
+
dragConstraints: { left: t, right: 0 },
|
|
46
|
+
dragElastic: 0.1,
|
|
47
|
+
style: { x: n },
|
|
48
|
+
onDragEnd: m,
|
|
49
|
+
className: e.foreground,
|
|
50
|
+
children: u
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
] });
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
x as SwipeRow
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=swipe-row.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swipe-row.es.js","sources":["../src/components/SwipeRow/SwipeRow.tsx"],"sourcesContent":["import { motion, useMotionValue, useTransform, animate } from 'framer-motion';\nimport { useRef, useMemo } from 'react';\nimport styles from './SwipeRow.module.css';\n\nexport type SwipeAction = {\n /** The visual content of the button (e.g. text or icon) */\n label: React.ReactNode;\n /** Callback triggered when this action is clicked */\n onClick: () => void;\n /** Background color for this action button */\n backgroundColor?: string;\n /** Text color for this action button */\n color?: string;\n /** Width of the button. Defaults to 80px */\n width?: number;\n /** Additional CSS class for the button */\n className?: string;\n /** Accessibility label. Defaults to label if it is a string a fallback string */\n ariaLabel?: string;\n};\n\nexport type SwipeRowProps = {\n /** The content to be displayed in the row */\n children: React.ReactNode;\n /** An array of action objects to render behind the row */\n actions: SwipeAction[];\n /** Optional custom swipe threshold. Computed automatically if not provided. */\n maxSwipe?: number;\n};\n\n/**\n * SwipeRow creates an iOS style swipeable row element that reveals generic actions behind it.\n * Highly configurable and built for generic use across various projects.\n */\nexport function SwipeRow({ children, actions, maxSwipe }: SwipeRowProps) {\n const x = useMotionValue(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const calculatedMaxSwipe = useMemo(() => {\n return -actions.reduce((acc, action) => acc + (action.width || 80), 0);\n }, [actions]);\n\n const swipeDistance = maxSwipe !== undefined ? maxSwipe : calculatedMaxSwipe;\n\n const backgroundOpacity = useTransform(x, [swipeDistance / 2, 0], [1, 0]);\n\n const handleDragEnd = () => {\n const current = x.get();\n\n // Swipe threshold to snap open\n if (current < swipeDistance / 2) {\n animate(x, swipeDistance, { type: 'spring', bounce: 0, duration: 0.3 });\n } else {\n animate(x, 0, { type: 'spring', bounce: 0, duration: 0.3 });\n }\n };\n\n return (\n <div ref={containerRef} className={styles.container}>\n {/* ACTIONS BACKGROUND */}\n <motion.div\n style={{ opacity: backgroundOpacity }}\n className={styles.actionsBackground}\n >\n {actions.map((action, index) => {\n const defaultAria = typeof action.label === 'string' ? action.label : `Action ${index}`;\n return (\n <button\n key={index}\n onClick={action.onClick}\n className={`${styles.actionButton} ${action.className || ''}`.trim()}\n style={{\n width: action.width || 80,\n backgroundColor: action.backgroundColor || '#64748b', // Slate\n color: action.color || 'white',\n }}\n aria-label={action.ariaLabel || defaultAria}\n >\n {action.label}\n </button>\n );\n })}\n </motion.div>\n\n {/* FOREGROUND */}\n <motion.div\n drag=\"x\"\n dragConstraints={{ left: swipeDistance, right: 0 }}\n dragElastic={0.1}\n style={{ x }}\n onDragEnd={handleDragEnd}\n className={styles.foreground}\n >\n {children}\n </motion.div>\n </div>\n );\n}\n"],"names":["SwipeRow","children","actions","maxSwipe","x","useMotionValue","containerRef","useRef","calculatedMaxSwipe","useMemo","acc","action","swipeDistance","backgroundOpacity","useTransform","handleDragEnd","animate","styles","jsx","motion","index","defaultAria"],"mappings":";;;;;;;;;AAkCO,SAASA,EAAS,EAAE,UAAAC,GAAU,SAAAC,GAAS,UAAAC,KAA2B;AACvE,QAAMC,IAAIC,EAAe,CAAC,GACpBC,IAAeC,EAAuB,IAAI,GAE1CC,IAAqBC,EAAQ,MAC1B,CAACP,EAAQ,OAAO,CAACQ,GAAKC,MAAWD,KAAOC,EAAO,SAAS,KAAK,CAAC,GACpE,CAACT,CAAO,CAAC,GAENU,IAAgBT,MAAa,SAAYA,IAAWK,GAEpDK,IAAoBC,EAAaV,GAAG,CAACQ,IAAgB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAElEG,IAAgB,MAAM;AAI1B,IAHgBX,EAAE,IAAA,IAGJQ,IAAgB,IAC5BI,EAAQZ,GAAGQ,GAAe,EAAE,MAAM,UAAU,QAAQ,GAAG,UAAU,KAAK,IAEtEI,EAAQZ,GAAG,GAAG,EAAE,MAAM,UAAU,QAAQ,GAAG,UAAU,KAAK;AAAA,EAE9D;AAEA,2BACG,OAAA,EAAI,KAAKE,GAAc,WAAWW,EAAO,WAExC,UAAA;AAAA,IAAA,gBAAAC;AAAA,MAACC,EAAO;AAAA,MAAP;AAAA,QACC,OAAO,EAAE,SAASN,EAAA;AAAA,QAClB,WAAWI,EAAO;AAAA,QAEjB,UAAAf,EAAQ,IAAI,CAACS,GAAQS,MAAU;AAC9B,gBAAMC,IAAc,OAAOV,EAAO,SAAU,WAAWA,EAAO,QAAQ,UAAUS,CAAK;AACrF,iBACE,gBAAAF;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,SAASP,EAAO;AAAA,cAChB,WAAW,GAAGM,EAAO,YAAY,IAAIN,EAAO,aAAa,EAAE,GAAG,KAAA;AAAA,cAC9D,OAAO;AAAA,gBACL,OAAOA,EAAO,SAAS;AAAA,gBACvB,iBAAiBA,EAAO,mBAAmB;AAAA;AAAA,gBAC3C,OAAOA,EAAO,SAAS;AAAA,cAAA;AAAA,cAEzB,cAAYA,EAAO,aAAaU;AAAA,cAE/B,UAAAV,EAAO;AAAA,YAAA;AAAA,YAVHS;AAAA,UAAA;AAAA,QAaX,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAIH,gBAAAF;AAAA,MAACC,EAAO;AAAA,MAAP;AAAA,QACC,MAAK;AAAA,QACL,iBAAiB,EAAE,MAAMP,GAAe,OAAO,EAAA;AAAA,QAC/C,aAAa;AAAA,QACb,OAAO,EAAE,GAAAR,EAAA;AAAA,QACT,WAAWW;AAAA,QACX,WAAWE,EAAO;AAAA,QAEjB,UAAAhB;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GACF;AAEJ;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swipe-row",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A highly customizable, iOS-style swipeable row component for React applications.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/swipe-row.cjs.js",
|
|
7
|
+
"module": "./dist/swipe-row.es.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/swipe-row.es.js",
|
|
12
|
+
"require": "./dist/swipe-row.cjs.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./dist/style.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "vite",
|
|
22
|
+
"build": "tsc && vite build",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:ui": "vitest --ui"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": ">=18.2.0",
|
|
28
|
+
"react-dom": ">=18.2.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@testing-library/dom": "^10.4.1",
|
|
32
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
33
|
+
"@testing-library/react": "^16.3.2",
|
|
34
|
+
"@types/node": "^25.3.0",
|
|
35
|
+
"@types/react": "^18.2.0",
|
|
36
|
+
"@types/react-dom": "^18.2.0",
|
|
37
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
38
|
+
"@vitest/ui": "^3.2.4",
|
|
39
|
+
"jsdom": "^27.0.1",
|
|
40
|
+
"typescript": "^5.2.2",
|
|
41
|
+
"vite": "^5.0.0",
|
|
42
|
+
"vite-plugin-dts": "^3.9.0",
|
|
43
|
+
"vitest": "^3.2.4"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"react",
|
|
47
|
+
"framer-motion",
|
|
48
|
+
"swipe",
|
|
49
|
+
"swipeable",
|
|
50
|
+
"list",
|
|
51
|
+
"row",
|
|
52
|
+
"ios",
|
|
53
|
+
"gesture",
|
|
54
|
+
"components"
|
|
55
|
+
],
|
|
56
|
+
"author": "nahkar",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/nahkar/swipe-row.git"
|
|
60
|
+
},
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/nahkar/swipe-row/issues"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/nahkar/swipe-row#readme",
|
|
65
|
+
"license": "MIT",
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"framer-motion": "^12.34.3"
|
|
68
|
+
}
|
|
69
|
+
}
|