Skip to content

Commit

Permalink
✨ feat: 初步完成 SortableList
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Feb 2, 2022
1 parent c0c170c commit 00ae177
Show file tree
Hide file tree
Showing 27 changed files with 811 additions and 582 deletions.
9 changes: 4 additions & 5 deletions packages/sortable-list/demos/Basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import React, { useState } from 'react';
import SortableList from '@arvinxu/sortable-list';

const Demo = () => {
const [list, setList] = useState([
{ text: 'hello', id: 'hello' },
{ text: 'world', id: 'world' },
]);
const [list, setList] = useState([{ id: 'hello' }, { id: 'world' }]);

return <SortableList value={list} onChange={setList} />;
return (
<SortableList dataSource={list} onChange={(value) => setList(value)} />
);
};

export default Demo;
81 changes: 81 additions & 0 deletions packages/sortable-list/demos/CustomRender.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useState } from 'react';
import { Badge, Button } from 'antd';

import SortableList from '@arvinxu/sortable-list';
import { Flexbox } from '@arvinxu/layout-kit';

interface Item {
id: string;
text: string;
}

const Demo = () => {
const [list, setList] = useState<Item[]>([
{ id: '1', text: '关关雎鸠' },
{ id: '2', text: '在河之洲' },
{ id: '3', text: '窈窕淑女' },
{ id: '4', text: '君子好逑' },
]);

return (
<SortableList<Item[]>
dataSource={list}
onChange={setList}
getItemStyles={() => ({ padding: '16px' })}
renderItem={(item: Item, { onRemove, onAddItem, index }) => {
return (
<Flexbox
horizontal
width={'100%'}
distribution={'space-between'}
gap={12}
>
<Flexbox horizontal gap={8}>
<div>
<Badge count={item.id} />
</div>
<div>{item.text}</div>
</Flexbox>
<Flexbox
horizontal // 由于拖拽事件是通过监听 onMouseDown 来识别用户动作
// 因此针对相关用户操作,需要终止 onMouseDown 的冒泡行为
onMouseDown={(e) => {
e.stopPropagation();
}}
>
<Button
size={'small'}
type={'link'}
onClick={() => {
onAddItem(index, {
id: Math.ceil(Math.random() * 100000).toString(16),
text: 'new',
});
}}
>
上方
</Button>
<Button
size={'small'}
type={'link'}
onClick={() => {
onAddItem(index + 1, {
id: Math.ceil(Math.random() * 1000).toString(16),
text: 'new',
});
}}
>
下方
</Button>
<Button size={'small'} danger type={'text'} onClick={onRemove}>
删除
</Button>
</Flexbox>
</Flexbox>
);
}}
/>
);
};

export default Demo;
39 changes: 39 additions & 0 deletions packages/sortable-list/demos/CustomStyle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState } from 'react';

import SortableList from '@arvinxu/sortable-list';

const Demo = () => {
const [list, setList] = useState([
{ id: '关关雎鸠' },
{ id: '在河之洲' },
{ id: '窈窕淑女' },
{ id: '君子好逑' },
]);

return (
<SortableList
dataSource={list}
onChange={setList}
style={{ background: 'rgb(255,224,224)', padding: 24, borderRadius: 12 }}
removable={false}
className={'custom-class'}
gap={24}
getItemStyles={({ isSorting, isDragging, isDragOverlay }) => {
// overlay 使用默认样式
if (isDragOverlay) return;

return {
padding: 24,
// 拖拽项修改背景色
background: isDragging ? 'rgb(74,135,82)' : 'pink',
color: isDragging ? 'rgb(139,212,148)' : 'rgb(135,74,74)',
// 在 拖拽过程中放大所有item的圆角
borderRadius: isSorting ? 100 : 16,
boxShadow: 'none',
};
}}
/>
);
};

export default Demo;
129 changes: 129 additions & 0 deletions packages/sortable-list/src/BaseItem/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
@background-color: #fff;
@border-color: #efefef;
@handle-color: rgba(0, 0, 0, 0.25);
@box-shadow-border: 0 0 0 calc(1px / var(--scale-x, 1)) hsla(240, 0%, 26%, 0.05);
@box-shadow-common: 0 1px calc(3px / var(--scale-x, 1)) hsla(240, 0%, 22%, 0.15);
@box-shadow: @box-shadow-border, @box-shadow-common;

@keyframes pop {
0% {
transform: scale(1);
box-shadow: var(--box-shadow);
}
100% {
transform: scale(var(--scale));
box-shadow: var(--box-shadow-picked-up);
}
}

@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

.avx-sortable {
&-item-container {
display: flex;
box-sizing: border-box;
transform: translate3d(var(--translate-x, 0), var(--translate-y, 0), 0)
scaleX(var(--scale-x, 1)) scaleY(var(--scale-y, 1));
transform-origin: 0 0;
touch-action: manipulation;
}

&-fadeIn {
animation: fadeIn 500ms ease;
}

&-dragOverlay {
--scale: 1.05;
--box-shadow: @box-shadow;
--box-shadow-picked-up: @box-shadow-border,
-1px 0 15px 0 rgba(34, 33, 81, 0.01),
0px 15px 15px 0 rgba(34, 33, 81, 0.25);
z-index: 999;
}

&-item {
position: relative;
display: flex;
flex-grow: 1;
align-items: center;
padding: 16px 24px;
background-color: @background-color;
box-shadow: @box-shadow;
outline: none;
border-radius: 4px;
box-sizing: border-box;
list-style: none;
transform-origin: 50% 50%;

transform: scale(var(--scale, 1));
transition: box-shadow 200ms ease-in-out;

&:hover {
.avx-sortable-action-remove {
visibility: visible;
}
}

&:not(.avx-sortable-withHandle) {
touch-action: manipulation;
cursor: grab;
}

&-dragging:not(&-dragOverlay) {
opacity: var(--dragging-opacity, 0.5);
z-index: 0;

&:focus {
box-shadow: @box-shadow;
}
}

&-disabled {
color: #999;
background-color: #f1f1f1;
&:focus {
box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.1), @box-shadow;
}
cursor: not-allowed;
}

&-dragOverlay {
cursor: inherit;
animation: pop 200ms cubic-bezier(0.18, 0.67, 0.6, 1.22);
transform: scale(var(--scale));
box-shadow: var(--box-shadow-picked-up);
opacity: 1;
}

&-color:before {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
height: 100%;
width: 3px;
display: block;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
background-color: var(--color);
}
}

&-action {
display: flex;
align-self: flex-start;
margin: -12px -10px -15px auto;

&-remove {
visibility: hidden;
}
}
}
130 changes: 130 additions & 0 deletions packages/sortable-list/src/BaseItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { CSSProperties } from 'react';
import React, { forwardRef, memo, useEffect } from 'react';
import classNames from 'classnames';

import { Handle, Remove } from '../components';

import type { BaseItemProps } from '../types';

import './index.less';

export const Item = memo(
forwardRef<HTMLLIElement, BaseItemProps>((p, ref) => {
const {
color,
dragOverlay,
dragging,
disabled,
fadeIn,
handle,
height,
index,
listeners,
onRemove,
renderItem,
sorting,
style,
transition,
transform,
item,
wrapperStyle,
removable,
onAddItem,
...props
} = p;

const prefix = 'avx-sortable';

// 添加 overlay 抓手
useEffect(() => {
if (!dragOverlay) {
return;
}

document.body.style.cursor = 'grabbing';

return () => {
document.body.style.cursor = '';
};
}, [dragOverlay]);

const containerStyle = {
...wrapperStyle,
transition: [transition, wrapperStyle?.transition]
.filter(Boolean)
.join(', '),
'--translate-x': transform ? `${Math.round(transform.x)}px` : undefined,
'--translate-y': transform ? `${Math.round(transform.y)}px` : undefined,
'--scale-x': transform?.scaleX ? `${transform.scaleX}` : undefined,
'--scale-y': transform?.scaleY ? `${transform.scaleY}` : undefined,
'--index': index,
'--color': color,
} as CSSProperties;

return (
<li
className={classNames(
`${prefix}-item-container`,
fadeIn && `${prefix}-fadeIn`,
sorting && `${prefix}-sorting`,
dragOverlay && `${prefix}-dragOverlay`,
)}
style={containerStyle}
ref={ref}
>
<div
className={classNames(
`${prefix}-item`,
dragging && `${prefix}-item-dragging`,
handle && `${prefix}-withHandle`,
dragOverlay && `${prefix}-item-dragOverlay`,
disabled && `${prefix}-item-disable`,
color && `${prefix}-item-color`,
)}
style={style}
{...(!handle ? listeners : undefined)}
{...props}
tabIndex={!handle ? 0 : undefined}
>
{renderItem ? (
renderItem(item, {
dragOverlay: Boolean(dragOverlay),
dragging: Boolean(dragging),
sorting: Boolean(sorting),
index,
fadeIn: Boolean(fadeIn),
listeners,
ref,
style,
transform,
transition,
onRemove: removable ? onRemove : undefined,
onAddItem: onAddItem,
})
) : (
<>
{item.id}
<span
className={`${prefix}-action`}
onClick={() => {
console.log('123');
}}
>
{removable ? (
<Remove
className={`${prefix}-action-remove`}
onMouseDown={(e) => {
e.stopPropagation();
}}
onClick={onRemove}
/>
) : null}
{handle ? <Handle {...listeners} /> : null}
</span>
</>
)}
</div>
</li>
);
}),
);
Loading

0 comments on commit 00ae177

Please sign in to comment.