Drag and drop
DropdownMenu with drag & drop
tsx
import React from 'react';
import Button, { ButtonLink } from '@semcore/button';
import Counter from '@semcore/counter';
import SettingsM from '@semcore/icon/Settings/m';
import DropdownMenu from '@semcore/dropdown-menu';
import { Text } from '@semcore/typography';
import { Flex } from '@semcore/flex-box';
import DnD from '@semcore/drag-and-drop';
const defeaultColumns = [
{ id: 'uniquePageviews', label: 'Unique Pageviews' },
{ id: 'uniqueVisitors', label: 'Unique Visitors' },
{ id: 'entranceSources', label: 'Entrance Sources' },
{ id: 'desktop', label: 'Desktop' },
{ id: 'mobile', label: 'Mobile' },
];
const defaultSelectedColumns = ['uniquePageviews', 'entranceSources'];
const Demo = () => {
const [highlightedIndex, setHighlightedIndex] = React.useState<number | null>(null);
const [columns, setColumns] = React.useState(defeaultColumns);
const handleDnD = React.useCallback(
({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
setColumns((columns) => {
const newColumns = [...columns];
const shift = fromIndex < toIndex ? 1 : -1;
for (let i = fromIndex; i !== toIndex; i += shift) {
newColumns[i] = columns[i + shift];
}
newColumns[toIndex] = columns[fromIndex];
return newColumns;
});
setHighlightedIndex(toIndex);
},
[],
);
const [selectedColumns, setSelectedColumns] = React.useState<string[]>(defaultSelectedColumns);
const resetToDefault = React.useCallback(() => {
setSelectedColumns(defaultSelectedColumns);
}, []);
const toggleAll = React.useCallback(() => {
const allSelected = selectedColumns.length === columns.length;
const allColumns = columns.map((column) => column.id);
if (allSelected) {
setSelectedColumns([]);
} else {
setSelectedColumns(allColumns);
}
}, [selectedColumns, columns]);
return (
<DropdownMenu
selectable
multiselect
highlightedIndex={highlightedIndex}
onHighlightedIndexChange={setHighlightedIndex}
>
<DropdownMenu.Trigger mt={2} mr='auto' id='dropdown-menu-basic' tag={Button}>
<Button.Addon>
<SettingsM />
</Button.Addon>
<Button.Text>Manage columns</Button.Text>
<Button.Addon>
<Counter>
{selectedColumns.length}/{columns.length}
</Counter>
</Button.Addon>
</DropdownMenu.Trigger>
<DropdownMenu.Popper hMax={800} aria-labelledby={'popper_id'}>
<Flex direction='column' alignItems='flex-start' p={2} gap={2}>
<Text bold id={'popper_id'}>
Show table columns
</Text>
<ButtonLink onClick={resetToDefault}>Reset to default</ButtonLink>
<ButtonLink onClick={toggleAll}>
{selectedColumns.length === columns.length ? 'Deselect' : 'Select'} all
</ButtonLink>
</Flex>
<DropdownMenu.List hMax={800}>
<DnD onDnD={handleDnD} aria-label={'drag-and-drop container'}>
{columns.map((column, index) => (
<DropdownMenu.Item
tag={DnD.Draggable}
isCustomFocus={true}
key={column.id}
selected={selectedColumns.includes(column.id)}
onClick={(e) => {
if (
e.target instanceof HTMLElement &&
e.target.getAttribute('role') === 'menuitemcheckbox'
) {
if (!selectedColumns.includes(column.id)) {
setSelectedColumns([...selectedColumns, column.id]);
} else {
setSelectedColumns(selectedColumns.filter((i) => i !== column.id));
}
}
}}
>
{column.label}
</DropdownMenu.Item>
))}
</DnD>
</DropdownMenu.List>
</DropdownMenu.Popper>
</DropdownMenu>
);
};
export default Demo;
Cards with drag & drop
Place widget hereChange the order of the widgets!
Market traffic
Backlinks
Place widget hereChange the order of the widgets!
tsx
import React from 'react';
import DnD from '@semcore/drag-and-drop';
import Card from '@semcore/card';
import { Flex } from '@semcore/flex-box';
import { Chart } from '@semcore/d3-chart';
import MathPlusL from '@semcore/icon/MathPlus/l';
import { Text } from '@semcore/typography';
const stableRandom = (seed: number) => {
let randomIndex = seed;
return () => {
if (randomIndex > 20) randomIndex = 1;
return Math.abs(Math.sin(Math.PI * randomIndex * Math.cos(100 - randomIndex++)));
};
};
const Widget: React.FC<{ title: string }> = ({ title }) => {
const data = React.useMemo(() => {
const random = stableRandom(title.length);
const dateFormatter = new Intl.DateTimeFormat('en', { month: 'numeric', day: 'numeric' });
return Array(3)
.fill(0)
.map((_, i) => ({
date: dateFormatter.format(new Date(Date.now() - 1000 * 60 * 60 * 24 * 3 * i)),
value: Math.round(random() * 10),
}));
}, [title]);
return (
<Card w={240} h={280}>
<Card.Header>
<Card.Title>{title}</Card.Title>
</Card.Header>
<Card.Body>
<Chart.Bar
duration={0}
groupKey={'date'}
data={data}
plotWidth={200}
plotHeight={200}
aria-label={`${title} chart`}
/>
</Card.Body>
</Card>
);
};
const widgetsSetup = [
{
title: 'Market traffic',
id: 'market-traffic',
},
{
title: 'Backlinks',
id: 'backlinks',
},
];
const defaultWidgets = [null, 'market-traffic', 'backlinks', null];
const Demo = () => {
const [widgets, setWidgets] = React.useState(defaultWidgets);
const handleDnD = React.useCallback(
({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
setWidgets((widgets) => {
const newWidgets = [...widgets];
const shift = fromIndex < toIndex ? 1 : -1;
for (let i = fromIndex; i !== toIndex; i += shift) {
newWidgets[i] = widgets[i + shift];
}
newWidgets[toIndex] = widgets[fromIndex];
return newWidgets;
});
},
[],
);
return (
<DnD tag={Flex} flexWrap gap={4} onDnD={handleDnD} aria-label={'Draggable charts'}>
{widgets.map((id, index) => {
if (!id) {
return (
<DnD.DropZone key={index} aria-label={`Drop zone ${index + 1}`}>
<Flex
alignItems='center'
gap={1}
justifyContent='center'
w={240}
h={280}
direction='column'
p={5}
style={{
border: '1px dashed var(--intergalactic-border-primary, #c4c7cf)',
borderRadius: '6px',
}}
>
<Text color='text-secondary'>
<MathPlusL />
</Text>
<Text color='text-secondary' bold size={200}>
Place widget here
</Text>
<Text color='text-secondary' textAlign='center' size={200}>
Change the order of the widgets!
</Text>
</Flex>
</DnD.DropZone>
);
}
const widget = widgetsSetup.find((widget) => widget.id === id)!;
return (
<DnD.Draggable placement='top' key={id} aria-label={`${widget.title} widget`} h='100%'>
<Widget title={widget.title} />
</DnD.Draggable>
);
})}
</DnD>
);
};
export default Demo;