Skip to content

Commit

Permalink
feat: ✨ implement swipe-cell (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
huang-xiao-jian authored Jul 16, 2020
1 parent f7f4bf5 commit 870895c
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 1 deletion.
18 changes: 18 additions & 0 deletions packages/SwipeCell/SwipeCell.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.van-swipe-cell {
position: relative;
overflow: hidden;
}
.van-swipe-cell__left,
.van-swipe-cell__right {
position: absolute;
top: 0;
height: 100%;
}
.van-swipe-cell__left {
left: 0;
transform: translate3d(-100%, 0, 0);
}
.van-swipe-cell__right {
right: 0;
transform: translate3d(100%, 0, 0);
}
176 changes: 176 additions & 0 deletions packages/SwipeCell/SwipeCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// packages
import React, {
FunctionComponent,
useRef,
useState,
CSSProperties,
useMemo,
ReactElement,
isValidElement,
} from 'react';
import clsx from 'clsx';
import { View } from 'remax/wechat';
import { useNativeEffect } from 'remax';
// internal
import { Select } from '../tools/Switch';
import { range } from '../tools/range';
import withDefaultProps from '../tools/with-default-props-advance';
import './SwipeCell.css';
import uuid from '../tools/uuid';

// 默认值填充属性
interface NeutralSwipeCellProps {
asyncClose: boolean;
disabled: boolean;
}

interface ExogenousSwipeCellProps {
// 标识符
name: string;
left?: ReactElement;
right?: ReactElement;
// 容器类名,用以覆盖内部
className?: string;
onClick?: (event: unknown) => void;
}

type SwipeCellProps = NeutralSwipeCellProps & ExogenousSwipeCellProps;

// scope
const DefaultSwipeCellProps: NeutralSwipeCellProps = {
asyncClose: false,
disabled: false,
};

const SwipeCell: FunctionComponent<SwipeCellProps> = (props) => {
const threshold = 0.35;
const { className, left, right, onClick, children } = props;
const classnames = {
container: clsx(className, 'van-swipe-cell'),
};

const visibility = {
left: isValidElement(left),
right: isValidElement(right),
};

const ids = useMemo(
() => ({
left: uuid(),
right: uuid(),
}),
[]
);
// 计算左右元素宽度
const $width$ = useRef({
left: 0,
right: 0,
});

// run only once, declare left, right one time
useNativeEffect(() => {
const qs = wx.createSelectorQuery();

qs.select(`#${ids.left}`).boundingClientRect();
qs.select(`#${ids.right}`).boundingClientRect();

qs.exec(
(
results: [
BoundingClientRectResult | undefined,
BoundingClientRectResult | undefined
]
) => {
$width$.current.left = results[0]?.width || 0;
$width$.current.right = results[1]?.width || 0;
}
);
}, []);

// 滑动逻辑处理
const [offset, setOffset] = useState(0);
// 滑动状态
const $touch$ = useRef({
status: 'SILENT',
startX: 0,
startOffset: 0,
});

const onTouchStart = (event: any) => {
$touch$.current = {
status: 'START',
startX: event.touches[0].clientX,
startOffset: offset,
};
};

const onTouchMove = (event: any) => {
// 状态标记,用以 transition 样式计算
$touch$.current.status = 'DRAGING';

// 滑动距离计算 value 变化
const deltaX = event.touches[0].clientX - $touch$.current.startX;
const next = $touch$.current.startOffset + deltaX;

setOffset(range(next, -$width$.current.right, $width$.current.left));
};

const onTouchEnd = () => {
// 状态标记,用以 transition 样式计算
$touch$.current.status = 'SILENT';

// 计算终值
if (visibility.right && -offset > $width$.current.right * threshold) {
setOffset(-$width$.current.right);
} else if (visibility.left && offset > $width$.current.left * threshold) {
setOffset($width$.current.left);
} else {
setOffset(0);
}
};

const onClickWrap = (event: any) => {
// 关闭 swipe 状态
setOffset(0);

if (typeof onClick === 'function') {
onClick(event);
}
};

const swipeStyle: CSSProperties = {
transform: `translate3d(${offset}px, 0, 0)`,
transition:
$touch$.current.status === 'DRAGGING'
? 'none'
: 'transform .6s cubic-bezier(0.18, 0.89, 0.32, 1)',
};

return (
<View
className={classnames.container}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
onClick={onClickWrap}
>
<View style={swipeStyle}>
<Select in={visibility.left}>
<View id={ids.left} className="van-swipe-cell__left">
{left}
</View>
</Select>
{children}
<Select in={visibility.right}>
<View id={ids.right} className="van-swipe-cell__right">
{right}
</View>
</Select>
</View>
</View>
);
};

export default withDefaultProps<ExogenousSwipeCellProps, NeutralSwipeCellProps>(
DefaultSwipeCellProps
)(SwipeCell);
4 changes: 4 additions & 0 deletions packages/SwipeCell/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @description - nothing but export component
*/
export { default } from './SwipeCell';
4 changes: 4 additions & 0 deletions packages/tools/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// eslint-disable-next-line import/prefer-default-export
export function range(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
4 changes: 4 additions & 0 deletions storyboard/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ page {
.demo-block__checkbox {
margin-top: 6px;
}

.demo-block__card {
--card-background-color: #fff;
}
58 changes: 58 additions & 0 deletions storyboard/pages/swipe-cell/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// packages
import React from 'react';
import { View, Text } from 'remax/wechat';

// internal
import CellGroup from '../../../packages/CellGroup';
import Cell from '../../../packages/Cell';
import Button from '../../../packages/Button';
import Image from '../../../packages/Image';
import Card from '../../../packages/Card';
import SwipeCell from '../../../packages/SwipeCell';

export default () => {
const left = <Button type="info">收藏</Button>;
const right = (
<>
<Button type="info" style={{ height: '100%' }}>
收藏
</Button>
<Button type="danger" style={{ height: '100%' }}>
删除
</Button>
</>
);

return (
<View className="demo-block">
<Text className="demo-block__title">基础用法</Text>
<View>
<SwipeCell name="2048" left={left} right={right}>
<CellGroup>
<Cell title="太平洋土著" />
</CellGroup>
</SwipeCell>
</View>

<Text className="demo-block__title">基础用法</Text>
<View>
<SwipeCell name="二氧化碳" right={right}>
<Card
num={2}
price="2.00"
desc="描述信息"
title="2018秋冬新款男士休闲时尚军绿飞行夹克秋冬新款男"
className="demo-block__card"
thumb={
<Image
mode="aspectFit"
className="van-card__img"
src="https://img.yzcdn.cn/upload_files/2017/07/02/af5b9f44deaeb68000d7e4a711160c53.jpg"
/>
}
/>
</SwipeCell>
</View>
</View>
);
};
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"esModuleInterop": true,
"jsx": "preserve",
"rootDir": "packages",
"baseUrl": "./",
"baseUrl": "./packages",
"paths": {},
"lib": ["DOM", "ES2015"]
},
Expand Down

0 comments on commit 870895c

Please sign in to comment.