Skip to content

Commit

Permalink
feat(sheets-data-validation): move draggable-list to design (#1822)
Browse files Browse the repository at this point in the history
* feat: move draggable list to design

* feat: storybook name

* feat: update storybook

* fix: devDeps

* feat: lock

* feat: delete useless deps

* fix: clsx deps

* feat: update lock
  • Loading branch information
weird94 committed Apr 7, 2024
1 parent d0cac23 commit 3acf286
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 147 deletions.
2 changes: 2 additions & 0 deletions packages/design/package.json
Expand Up @@ -87,12 +87,14 @@
"rc-tooltip": "^6.2.0",
"rc-util": "^5.39.1",
"react-draggable": "^4.4.6",
"react-grid-layout": "^1.4.4",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
"@testing-library/react": "^14.2.2",
"@types/react": "^18.2.73",
"@types/react-dom": "^18.2.23",
"@types/react-grid-layout": "^1.3.5",
"@types/react-transition-group": "^4.4.10",
"@univerjs/shared": "workspace:*",
"clsx": "^2.1.0",
Expand Down
@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { Meta } from '@storybook/react';
import React, { useState } from 'react';

import { DraggableList } from './DraggableList';

const meta: Meta<typeof DraggableList> = {
title: 'Components / DraggableList',
component: DraggableList,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};

export default meta;

export const DraggableListDemo = {
render() {
const [list, setList] = useState([{ title: '1', key: '1' }, { title: '11', key: '2' }]);

return (
<DraggableList
list={list}
onListChange={setList}
idKey="key"
itemRender={(item) => (
<div
style={{ width: 120, border: '1px solid #ccc', borderRadius: 4 }}
>
{item.title}
</div>
)}
rowHeight={32}
margin={[0, 12]}
/>
);
},
};
72 changes: 72 additions & 0 deletions packages/design/src/components/draggable-list/DraggableList.tsx
@@ -0,0 +1,72 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { useMemo } from 'react';
import RGL, { WidthProvider } from 'react-grid-layout';

const ReactGridLayout = WidthProvider(RGL);

export interface IDraggableListProps<T> extends Omit<RGL.ReactGridLayoutProps, 'layout' | 'onLayoutChange' | 'cols' | 'isResizable'> {
list: T[];
onListChange: (list: T[]) => void;
idKey: keyof T;
itemRender: (item: T, index: number) => React.ReactNode;
}

export function DraggableList<T = any>(props: IDraggableListProps<T>) {
const { list, onListChange, idKey, itemRender, ...gridProps } = props;

const listMap = useMemo(() => {
const listMap = new Map<unknown, T>();
list.forEach((item: T) => {
const key = item[idKey];
listMap.set(key, item);
});
return listMap;
}, [idKey, list]);

const layouts: RGL.Layout[] = useMemo(() => {
return list.map((item, index) => ({
i: item[idKey] as string,
w: 12,
h: 1,
x: 0,
y: index,
col: 12,
}));
}, [idKey, list]);

return (
<ReactGridLayout
useCSSTransforms={false}
{...gridProps}
cols={12}
preventCollision={false}
isResizable={false}
isDraggable
onLayoutChange={(layout) => {
const newList = layout.sort((prev, aft) => prev.y - aft.y).map((item) => listMap.get(item.i)!);
onListChange(newList);
}}
>
{layouts.map((item, index) => (
<div key={item.i} data-grid={item}>
{itemRender(listMap.get(item.i)!, index)}
</div>
))}
</ReactGridLayout>
);
}
18 changes: 18 additions & 0 deletions packages/design/src/components/draggable-list/index.ts
@@ -0,0 +1,18 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { DraggableList } from './DraggableList';
export type { IDraggableListProps } from './DraggableList';
1 change: 1 addition & 0 deletions packages/design/src/index.ts
Expand Up @@ -47,3 +47,4 @@ export { type ITreeNodeProps, type ITreeProps, Tree, TreeSelectionMode } from '.
export { enUS, zhCN } from './locale';
export { type ILocale } from './locale/interface';
export { defaultTheme, greenTheme, themeInstance } from './themes';
export { DraggableList, type IDraggableListProps } from './components/draggable-list';
6 changes: 4 additions & 2 deletions packages/sheets-data-validation/package.json
Expand Up @@ -57,6 +57,7 @@
"test:watch": "vitest",
"coverage": "vitest run --coverage",
"build": "tsc && vite build"

},
"peerDependencies": {
"@univerjs/core": "workspace:*",
Expand All @@ -69,13 +70,13 @@
"@univerjs/sheets-ui": "workspace:*",
"@univerjs/ui": "workspace:*",
"@wendellhu/redi": "0.13.0",
"clsx": ">=2.0.0",
"react": "^16.9.0 || ^17.0.0 || ^18.0.0",
"rxjs": ">=7.0.0"
},
"dependencies": {
"@univerjs/icons": "^0.1.43",
"dayjs": "^1.11.10",
"react-draggable-list": "^4.2.0"
"dayjs": "^1.11.10"
},
"devDependencies": {
"@univerjs/core": "workspace:*",
Expand All @@ -89,6 +90,7 @@
"@univerjs/sheets-ui": "workspace:*",
"@univerjs/ui": "workspace:*",
"@wendellhu/redi": "^0.13.0",
"clsx": "^2.1.0",
"less": "^4.2.0",
"react": "^18.2.0",
"rxjs": "^7.8.1",
Expand Down
Expand Up @@ -16,14 +16,14 @@

import { RangeSelector, useEvent } from '@univerjs/ui';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { FormLayout, Input, Radio, RadioGroup, Select } from '@univerjs/design';
import { DraggableList, FormLayout, Input, Radio, RadioGroup, Select } from '@univerjs/design';
import { deserializeRangeWithSheet, isReferenceString, serializeRangeWithSheet } from '@univerjs/engine-formula';
import { useDependency } from '@wendellhu/redi/react-bindings';
import type { IRange } from '@univerjs/core';
import { IUniverInstanceService, LocaleService, Tools } from '@univerjs/core';
import type { IFormulaInputProps } from '@univerjs/data-validation';
import { DeleteSingle, IncreaseSingle, SequenceSingle } from '@univerjs/icons';
import DraggableList from 'react-draggable-list';
import cs from 'clsx';
import { deserializeListOptions, getSheetRangeValueSet, serializeListOptions } from '../../validators/util';
import { DROP_DOWN_DEFAULT_COLOR } from '../../common/const';
import styles from './index.module.less';
Expand Down Expand Up @@ -108,25 +108,15 @@ const ColorSelect = (props: IColorSelectProps) => {
);
};

const Template = (props: { item: IDropdownItem; dragHandleProps: any; commonProps: any }) => {
const { item, dragHandleProps, commonProps } = props;
const { onMouseDown, onTouchStart } = dragHandleProps;
const Template = (props: { item: IDropdownItem; commonProps: any; style?: React.CSSProperties }) => {
const { item, commonProps, style } = props;
const { onItemChange, onItemDelete } = commonProps;

return (
<div className={styles.dataValidationFormulaListItem}>
<div className={styles.dataValidationFormulaListItem} style={style}>
{!item.isRef
? (
<div
className={styles.dataValidationFormulaListItemDrag}
onTouchStart={(e) => {
e.preventDefault();
onTouchStart(e);
}}
onMouseDown={(e) => {
onMouseDown(e);
}}
>
<div className={cs(styles.dataValidationFormulaListItemDrag, 'draggableHandle')}>
<SequenceSingle />
</div>
)
Expand All @@ -136,6 +126,7 @@ const Template = (props: { item: IDropdownItem; dragHandleProps: any; commonProp
onChange={(color) => {
onItemChange(item.id, item.label, color);
}}

/>
<Input
disabled={item.isRef}
Expand Down Expand Up @@ -276,76 +267,73 @@ export function ListFormulaInput(props: IFormulaInputProps) {
<Radio value="1">{localeService.t('dataValidation.list.refOptions')}</Radio>
</RadioGroup>
</FormLayout>
{isRefRange === '1' ? (
<>
{isRefRange === '1'
? (
<>
<FormLayout error={formula1Res}>
<RangeSelector
id={`list-ref-range-${unitId}-${subUnitId}`}
value={refRange}
openForSheetUnitId={unitId}
openForSheetSubUnitId={subUnitId}
onChange={(ranges) => {
const range = ranges[0];
if (!range || isRangeInValid(range.range)) {
onChange?.({
formula1: '',
formula2,
});
setRefRange('');
} else {
const workbook = univerInstanceService.getUniverSheetInstance(range.unitId) ?? univerInstanceService.getCurrentUniverSheetInstance();
const worksheet = workbook?.getSheetBySheetId(range.sheetId) ?? workbook.getActiveSheet();
const rangeStr = serializeRangeWithSheet(worksheet.getName(), range.range);
onChange?.({
formula1: rangeStr,
formula2,
});
setRefRange(rangeStr);
}
}}
isSingleChoice
/>
</FormLayout>
<FormLayout>
<div ref={containerRef}>
{refFinalList.map((item) => {
return <Template key={item.id} item={item} commonProps={{ onItemChange: handleRefItemChange }} style={{ marginBottom: 12 }} />;
})}
</div>
</FormLayout>
</>
)
: (
<FormLayout error={formula1Res}>
<RangeSelector
id={`list-ref-range-${unitId}-${subUnitId}`}
value={refRange}
openForSheetUnitId={unitId}
openForSheetSubUnitId={subUnitId}
onChange={(ranges) => {
const range = ranges[0];
if (!range || isRangeInValid(range.range)) {
onChange?.({
formula1: '',
formula2,
});
setRefRange('');
} else {
const workbook = univerInstanceService.getUniverSheetInstance(range.unitId) ?? univerInstanceService.getCurrentUniverSheetInstance();
const worksheet = workbook?.getSheetBySheetId(range.sheetId) ?? workbook.getActiveSheet();
const rangeStr = serializeRangeWithSheet(worksheet.getName(), range.range);
onChange?.({
formula1: rangeStr,
formula2,
});
setRefRange(rangeStr);
}
}}
isSingleChoice
/>
</FormLayout>
<FormLayout>
<div ref={containerRef}>
<div ref={containerRef} style={{ margin: '-12px 0' }}>
<DraggableList
itemKey="id"
// @ts-ignore
template={Template}
list={refFinalList}
container={() => containerRef.current}
commonProps={{
onItemChange: handleRefItemChange,
}}
list={strList}
onListChange={setStrList}
rowHeight={32}
margin={[0, 12]}
itemRender={(item) => (
<Template
key={item.id}
item={item}
commonProps={{
onItemChange: handleStrItemChange,
onItemDelete: handleStrItemDelete,
}}
/>
)}
idKey="id"
/>
<a className={styles.dataValidationFormulaListAdd} onClick={handleAdd}>
<IncreaseSingle />
{localeService.t('dataValidation.list.add')}
</a>
</div>
</FormLayout>
</>
) : (
<FormLayout error={formula1Res}>
<div ref={containerRef}>
<DraggableList
itemKey="id"
// @ts-ignore
template={Template}
list={strList}
onMoveEnd={(newList) => {
// @ts-ignore
setStrList(newList);
}}
container={() => containerRef.current}
commonProps={{
onItemChange: handleStrItemChange,
onItemDelete: handleStrItemDelete,
}}
/>
<a className={styles.dataValidationFormulaListAdd} onClick={handleAdd}>
<IncreaseSingle />
{localeService.t('dataValidation.list.add')}
</a>
</div>
</FormLayout>
)}
)}
</>
);
}

0 comments on commit 3acf286

Please sign in to comment.