Skip to content

Commit

Permalink
Merge pull request #31 from agentlab/issue-29-add-scrolling-horizonta…
Browse files Browse the repository at this point in the history
…l-cards-list

GH-29 Add horizontal scroll data control
  • Loading branch information
amivanoff authored Sep 29, 2021
2 parents 04f02c3 + 4b3f73e commit 287c25d
Show file tree
Hide file tree
Showing 11 changed files with 715 additions and 96 deletions.
48 changes: 25 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,56 +62,58 @@
"mobx-react-lite": ">=3.2.1",
"mobx-state-tree": ">=5.0.3",
"moment": ">=2.29.1",
"rc-util": ">=5.14.0",
"react": ">=17.0.2",
"react-base-table": ">=1.12.0",
"react-dom": ">=17.0.2",
"react-dnd": ">=13.1.1",
"react-dnd-html5-backend": ">=12.1.1",
"react-dom": ">=17.0.2",
"react-error-boundary": ">=3.1.3",
"react-horizontal-scrolling-menu": ">=2.3.3",
"react-router": ">=6.0.0-beta.4",
"react-router-dom": ">=6.0.0-beta.4",
"react-sortable-hoc": ">=2.0.0",
"react-virtualized": ">=9.22.3",
"rc-util": ">=5.14.0",
"react-split-pane": ">=2.0.3",
"react-virtualized": ">=9.22.3",
"styled-components": ">=5.3.1",
"tinymce": ">=5.5.1",
"uri-js": ">=4.4.1",
"uuid62": ">=1.0.1"
},
"dependencies": {
"@agentlab/sparql-jsld-client": "^5.0.0-rc.13",
"@ant-design/icons": "^4.6.4",
"@tinymce/tinymce-react": "^3.12.6",
"@agentlab/sparql-jsld-client": ">=5.0.0-rc.13",
"@ant-design/icons": ">=4.7.0",
"@tinymce/tinymce-react": ">=3.12.6",
"@types/react": "^17.0.24",
"@types/react-dom": "^17.0.9",
"@types/react-router": "^5.1.16",
"@types/react-router-dom": "^5.3.0",
"@types/react-virtualized": "^9.21.13",
"@types/styled-components": "^5.1.14",
"@types/tinymce": "^4.6.4",
"antd": "^4.16.13",
"history": "^5.0.1",
"mobx-react-lite": "^3.2.1",
"antd": ">=4.16.13",
"history": ">=5.0.1",
"mobx-react-lite": ">=3.2.1",
"mst-middlewares": "^5.0.3",
"react": "^17.0.2",
"react-base-table": "^1.12.0",
"react-dom": "^17.0.2",
"react-dnd": "^13.1.1",
"react-dnd-html5-backend": "^12.1.1",
"react-error-boundary": "^3.1.3",
"react": ">=17.0.2",
"react-base-table": ">=1.12.0",
"react-dnd": ">=13.1.1",
"react-dnd-html5-backend": ">=12.1.1",
"react-dom": ">=17.0.2",
"react-error-boundary": ">=3.1.3",
"react-horizontal-scrolling-menu": "^2.3.3",
"react-is": "^17.0.2",
"react-redux": "^7.2.5",
"react-router": "^6.0.0-beta.4",
"react-router-dom": "^6.0.0-beta.4",
"react-sortable-hoc": "^2.0.0",
"react-virtualized": "^9.22.3",
"styled-components": "^5.3.1",
"react-router": ">=6.0.0-beta.4",
"react-router-dom": ">=6.0.0-beta.4",
"react-sortable-hoc": ">=2.0.0",
"react-split-pane": ">=2.0.3",
"react-virtualized": ">=9.22.3",
"redux": "^4.1.1",
"remotedev": "^0.2.9",
"react-split-pane": "^2.0.3",
"tinymce": "5.5.1",
"uri-js": "^4.4.1"
"styled-components": ">=5.3.1",
"tinymce": ">=5.5.1",
"uri-js": ">=4.4.1"
},
"devDependencies": {
"@babel/core": "^7.15.5",
Expand Down
2 changes: 2 additions & 0 deletions src/data-controls/DataControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { withStoreToDataControlProps, treeify, strcmp } from '../util/ContextToP
import { TableRenderer } from './TableRenderer';
import { TreeRenderer } from './TreeRenderer';
import { GridRenderer } from './GridRenderer';
import { HorizontalScrollRenderer } from './HorizontalScroll';

const renderType: any = {
tree: TreeRenderer,
table: TableRenderer,
grid: GridRenderer,
horizontalScroll: HorizontalScrollRenderer,
};

export const AntdDataLayout: React.FC<any> = React.memo(
Expand Down
19 changes: 19 additions & 0 deletions src/data-controls/HorizontalScroll/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

export const Card = ({ itemId, onClick, children, style }: any) => {
return (
<div
onClick={() => onClick()}
style={{
display: 'inline-block',
margin: '0 10px',
width: '260px',
userSelect: 'none',
...style,
}}
tabIndex={0}
className='card'>
{children}
</div>
);
};
109 changes: 109 additions & 0 deletions src/data-controls/HorizontalScroll/HorizontalScroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useState, useEffect } from 'react';
import { IViewKindElement } from '../../models/uischema';
import { Card } from './Card';
import { DispatchCell } from '../../DispatchCell';
import { ScrollMenu, VisibilityContext } from 'react-horizontal-scrolling-menu';
import { LeftArrow, RightArrow } from './arrows';
import useDrag from './useDrag';

import './styles.css';
import './hideScrollbar.css';

type scrollVisibilityApiType = React.ContextType<typeof VisibilityContext>;

export const HorizontalScrollRenderer: React.FC<any> = (props) => {
const { viewKind, viewKindElement, viewDescr, viewDescrElement, child, schema, getData, onSelect } = props;
const [dataSource, setDataSource] = useState(child);
const [selected, setSelected] = useState<string>('');
const template = viewKindElement?.options?.elementTemplate || null;

const { dragStart, dragStop, dragMove, dragging } = useDrag();
const handleDrag =
({ scrollContainer }: scrollVisibilityApiType) =>
(ev: React.MouseEvent) =>
dragMove(ev, (newPos) => {
if (scrollContainer.current) {
const currentScroll = scrollContainer.current.scrollLeft;
scrollContainer.current.scrollLeft = currentScroll + newPos;
}
});

const handleItemClick = (itemId: string) => () => {
if (dragging) {
return false;
}
if (selected !== itemId) {
setSelected(itemId);
onSelect(itemId);
}
};

const createCell = (data: any, id: string | number) =>
template ? (
template.map((e: IViewKindElement, idx: number) => (
<DispatchCell
id={String(id) + String(idx)}
key={String(id) + String(idx)}
viewKind={viewKind}
viewKindElement={e}
viewDescr={viewDescr}
viewDescrElement={viewDescrElement}
schema={schema}
data={data}
rowData={data}
/>
))
) : (
<span key={id}>{data['@id']}</span>
);

const onUpdate = ({ isLastItemVisible }: scrollVisibilityApiType) => {
if (isLastItemVisible) {
const newDataSource = dataSource.concat(getData());
console.log('push new items');
setDataSource(newDataSource);
}
};

useEffect(() => {
setDataSource(child);
}, [child]);

return (
<div onMouseLeave={dragStop}>
<ScrollMenu
LeftArrow={LeftArrow}
RightArrow={RightArrow}
onWheel={onWheel}
onMouseDown={() => dragStart}
onMouseUp={() => dragStop}
onMouseMove={handleDrag}
onUpdate={onUpdate}>
{dataSource.map((data: any, idx: number) => (
<Card
key={data['@id']}
itemId={data['@id']}
onClick={handleItemClick(data['@id'])}
style={viewKindElement.options.style}>
{createCell(data, idx)}
</Card>
))}
</ScrollMenu>
</div>
);
};

function onWheel(apiObj: scrollVisibilityApiType, ev: React.WheelEvent): void {
const isThouchpad = Math.abs(ev.deltaX) !== 0 || Math.abs(ev.deltaY) < 15;

if (isThouchpad) {
ev.stopPropagation();
return;
}

if (ev.deltaY < 0) {
apiObj.scrollNext();
} else if (ev.deltaY > 0) {
apiObj.scrollPrev();
}
}
54 changes: 54 additions & 0 deletions src/data-controls/HorizontalScroll/arrows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';

import { VisibilityContext } from 'react-horizontal-scrolling-menu';
import './styles.css';

function Arrow({
children,
disabled,
onClick,
}: {
children: React.ReactNode;
disabled: boolean;
onClick: VoidFunction;
}) {
return (
<button disabled={disabled} onClick={onClick} className='arrow'>
{children}
</button>
);
}

export function LeftArrow() {
const { isFirstItemVisible, scrollPrev, visibleItemsWithoutSeparators } = React.useContext(VisibilityContext);

const [disabled, setDisabled] = React.useState(!visibleItemsWithoutSeparators.length && isFirstItemVisible);
React.useEffect(() => {
if (visibleItemsWithoutSeparators.length) {
setDisabled(isFirstItemVisible);
}
}, [isFirstItemVisible, visibleItemsWithoutSeparators]);

return (
<Arrow disabled={disabled} onClick={() => scrollPrev()}>
{'<'}
</Arrow>
);
}

export function RightArrow() {
const { isLastItemVisible, scrollNext, visibleItemsWithoutSeparators } = React.useContext(VisibilityContext);

const [disabled, setDisabled] = React.useState(!visibleItemsWithoutSeparators.length && isLastItemVisible);
React.useEffect(() => {
if (visibleItemsWithoutSeparators.length) {
setDisabled(isLastItemVisible);
}
}, [isLastItemVisible, visibleItemsWithoutSeparators]);

return (
<Arrow disabled={disabled} onClick={() => scrollNext()}>
{'>'}
</Arrow>
);
}
8 changes: 8 additions & 0 deletions src/data-controls/HorizontalScroll/hideScrollbar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.react-horizontal-scrolling-menu--scroll-container::-webkit-scrollbar {
display: none;
}

.react-horizontal-scrolling-menu--scroll-container {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
1 change: 1 addition & 0 deletions src/data-controls/HorizontalScroll/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './HorizontalScroll';
21 changes: 21 additions & 0 deletions src/data-controls/HorizontalScroll/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
* {
box-sizing: border-box;
}

.arrow {
width: 50px;
height: 50px;
align-self: center;
border-radius: 8px;
margin: 0 3px 0 3px;
background-color: white;
box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.12);
border: 1px solid rgb(222, 222, 222);
cursor: pointer;
text-align: center;
vertical-align: middle;
right: 1%;
user-select: none;
font-size: 1.2rem;
color: rgb(136, 135, 135);
}
54 changes: 54 additions & 0 deletions src/data-controls/HorizontalScroll/useDrag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';

export default function useDrag() {
const [clicked, setClicked] = React.useState(false);
const [dragging, setDragging] = React.useState(false);
const [position, setPosition] = React.useState(0);
const [diff, setDiff] = React.useState(0);

const dragStart = React.useCallback((ev: React.MouseEvent) => {
setPosition(ev.clientX);
setDiff(0);
setClicked(true);
}, []);

const dragStop = React.useCallback(
() =>
window.requestAnimationFrame(() => {
setDragging(false);
setClicked(false);
}),
[],
);

const dragMove = React.useCallback(
(ev: React.MouseEvent, cb: (newPos: number) => void) => {
const newDiff = position - ev.clientX;

const movedEnough = Math.abs(newDiff) > 5;

if (clicked && movedEnough) {
setDragging(true);
}

if (dragging && movedEnough) {
setPosition(ev.clientX);
setDiff(newDiff);
cb(newDiff);
}
},
[clicked, dragging, position],
);

return {
dragStart,
dragStop,
dragMove,
diff,
dragging,
position,
setDragging,
setDiff,
setPosition,
};
}
Loading

0 comments on commit 287c25d

Please sign in to comment.