|
@@ -0,0 +1,125 @@
|
|
|
+import { View, Text } from "@tarojs/components";
|
|
|
+import { useRef, useState } from "react";
|
|
|
+import classNames from "classnames";
|
|
|
+import styles from "./index.module.less";
|
|
|
+
|
|
|
+interface TAction {
|
|
|
+ text: string|JSX.Element;
|
|
|
+ color?: string;
|
|
|
+ width?: number;
|
|
|
+ onClick: () => void;
|
|
|
+}
|
|
|
+
|
|
|
+interface SliderDeleteActionProps {
|
|
|
+ actions: TAction[]; // 操作按钮列表
|
|
|
+ children: React.ReactNode;
|
|
|
+ disabled?: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+const THRESHOLD = 60; // 滑动阈值
|
|
|
+const DEFAULT_BUTTON_WIDTH = 80; // 默认每个按钮80px
|
|
|
+
|
|
|
+const SliderDeleteAction: React.FC<SliderDeleteActionProps> = ({
|
|
|
+ actions,
|
|
|
+ children,
|
|
|
+ disabled = false,
|
|
|
+}) => {
|
|
|
+ const [offset, setOffset] = useState(0);
|
|
|
+ const [open, setOpen] = useState(false);
|
|
|
+ const startXRef = useRef(0);
|
|
|
+ const touchingRef = useRef(false);
|
|
|
+
|
|
|
+ actions = actions.map(action => {
|
|
|
+ if(action.width === undefined){
|
|
|
+ action.width = DEFAULT_BUTTON_WIDTH
|
|
|
+ }
|
|
|
+ return action
|
|
|
+ })
|
|
|
+ // 计算所有按钮总宽度
|
|
|
+ const totalActionWidth = actions.reduce((prev, next)=> {
|
|
|
+ return prev + (next.width ?? DEFAULT_BUTTON_WIDTH)
|
|
|
+ }, 0);
|
|
|
+
|
|
|
+ // 触摸开始
|
|
|
+ const handleTouchStart = (e) => {
|
|
|
+ if (disabled) return;
|
|
|
+ touchingRef.current = true;
|
|
|
+ startXRef.current = e.touches[0].clientX;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 触摸移动
|
|
|
+ const handleTouchMove = (e) => {
|
|
|
+ if (!touchingRef.current || disabled) return;
|
|
|
+ const deltaX = e.touches[0].clientX - startXRef.current;
|
|
|
+ if (deltaX < 0) {
|
|
|
+ setOffset(Math.max(deltaX, -totalActionWidth));
|
|
|
+ } else if (open) {
|
|
|
+ setOffset(Math.min(deltaX - totalActionWidth, 0));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 关闭
|
|
|
+ const closeSlider = ()=> {
|
|
|
+ setOffset(0);
|
|
|
+ setOpen(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 触摸结束
|
|
|
+ const handleTouchEnd = () => {
|
|
|
+ if (disabled) return;
|
|
|
+ touchingRef.current = false;
|
|
|
+ if (Math.abs(offset) > THRESHOLD) {
|
|
|
+ setOffset(-totalActionWidth);
|
|
|
+ setOpen(true);
|
|
|
+ } else {
|
|
|
+ closeSlider()
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 点击内容关闭
|
|
|
+ const handleContentClick = () => {
|
|
|
+ if (open) {
|
|
|
+ closeSlider()
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleActionClick = (action: TAction)=> {
|
|
|
+ action.onClick()
|
|
|
+ closeSlider()
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View className={styles.sliderDeleteAction}>
|
|
|
+ <View
|
|
|
+ className={styles.actions}
|
|
|
+ style={{ width: `${totalActionWidth}px`, right: 0 }}
|
|
|
+ >
|
|
|
+ {actions.map((action, idx) => (
|
|
|
+ <View
|
|
|
+ key={idx}
|
|
|
+ className={styles.actionBtn}
|
|
|
+ style={{ background: action.color || "#ff4d4f" }}
|
|
|
+ onClick={()=> handleActionClick(action)}
|
|
|
+ >
|
|
|
+ {action.text}
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ <View
|
|
|
+ className={classNames(styles.content, { [styles.open]: open })}
|
|
|
+ style={{
|
|
|
+ transform: `translateX(${offset}px)`,
|
|
|
+ transition: touchingRef.current ? "none" : "transform 0.2s",
|
|
|
+ }}
|
|
|
+ onTouchStart={handleTouchStart}
|
|
|
+ onTouchMove={handleTouchMove}
|
|
|
+ onTouchEnd={handleTouchEnd}
|
|
|
+ onClick={handleContentClick}
|
|
|
+ >
|
|
|
+ {children}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default SliderDeleteAction;
|