Skip to content

Drag and drop

Select with drag & drop

tsx
import React from 'react';
import Button from 'intergalactic/button';
import Counter from 'intergalactic/counter';
import SettingsM from 'intergalactic/icon/Settings/m';
import Select from 'intergalactic/select';
import { Text } from 'intergalactic/typography';
import Link from 'intergalactic/link';
import { Flex } from 'intergalactic/flex-box';
import DnD from 'intergalactic/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 [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;
      });
    },
    [],
  );
  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 (
    <Select multiselect value={selectedColumns} onChange={setSelectedColumns}>
      <Select.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>
      </Select.Trigger>
      <Select.Menu hMax={800}>
        {({ highlightedIndex }) => {
          return (
            <DnD onDnD={handleDnD} customFocus={highlightedIndex}>
              <Flex direction='column' alignItems='flex-start' p={2} gap={2}>
                <Text bold>Show table columns</Text>
                <Link tag='button' size={200} onClick={resetToDefault}>
                  Reset to default
                </Link>
                <Link tag='button' size={200} onClick={toggleAll}>
                  {selectedColumns.length === columns.length ? 'Deselect' : 'Select'} all
                </Link>
              </Flex>
              {columns.map((column) => (
                <DnD.Draggable
                  tag={Select.Option}
                  id={column.id}
                  selected={selectedColumns.includes(column.id)}
                  value={column.id}
                  key={column.id}
                >
                  <Select.Option.Checkbox />
                  {column.label}
                </DnD.Draggable>
              ))}
            </DnD>
          );
        }}
      </Select.Menu>
    </Select>
  );
};

export default Demo;

Cards with drag & drop

tsx
import React from 'react';
import DnD from 'intergalactic/drag-and-drop';
import Card from 'intergalactic/card';
import { Flex } from 'intergalactic/flex-box';
import { Chart } from 'intergalactic/d3-chart';
import MathPlusL from 'intergalactic/icon/MathPlus/l';
import { Text } from 'intergalactic/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={300}>
      <Card.Header>
        <Card.Title>{title}</Card.Title>
      </Card.Header>
      <Card.Body>
        <Chart.Bar
          duration={0}
          mt={5}
          groupKey={'date'}
          data={data}
          plotWidth={200}
          plotHeight={200}
        />
      </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}>
      {widgets.map((id, index) => {
        if (!id) {
          return (
            <DnD.DropZone key={index}>
              <Flex
                alignItems='center'
                gap={1}
                justifyContent='center'
                h={300}
                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;

TabPanel with drag & drop

tsx
import React from 'react';
import DnD from 'intergalactic/drag-and-drop';
import Badge from 'intergalactic/badge';
import LinkedInM from 'intergalactic/icon/LinkedIn/m';
import TabPanel from 'intergalactic/tab-panel';
import Counter from 'intergalactic/counter';
import Flag from 'intergalactic/flags';

const renderTab = (tab: string) => {
  if (tab === 'overview') {
    return <TabPanel.Item.Text>Overview</TabPanel.Item.Text>;
  } else if (tab === 'activity') {
    return (
      <>
        <TabPanel.Item.Text>Activity</TabPanel.Item.Text> <Counter>23</Counter>
      </>
    );
  } else if (tab === 'users') {
    return <TabPanel.Item.Text>Users</TabPanel.Item.Text>;
  } else if (tab === 'query-log') {
    return <TabPanel.Item.Text>Query Log</TabPanel.Item.Text>;
  } else if (tab === 'geolocation') {
    return (
      <>
        <TabPanel.Item.Addon>
          <Flag iso2='US' />
        </TabPanel.Item.Addon>
        <TabPanel.Item.Text>Geolocation</TabPanel.Item.Text>
      </>
    );
  } else if (tab === 'disabled-tab') {
    return <TabPanel.Item.Text>Disabled Tab</TabPanel.Item.Text>;
  } else {
    return <TabPanel.Item.Text>{tab}</TabPanel.Item.Text>;
  }
};

const disabledTabs = ['disabled-tab'];

const Demo = () => {
  const [tabs, setTabs] = React.useState([
    'overview',
    'activity',
    'users',
    'query-log',
    'geolocation',
    'disabled-tab',
  ]);
  const [currentTab, setCurrentTab] = React.useState('overview');
  const handleDnD = React.useCallback(
    ({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
      setTabs((tabs) => {
        const newTabs = [...tabs];
        const shift = fromIndex < toIndex ? 1 : -1;
        for (let i = fromIndex; i !== toIndex; i += shift) {
          newTabs[i] = tabs[i + shift];
        }
        newTabs[toIndex] = tabs[fromIndex];
        return newTabs;
      });
    },
    [],
  );

  return (
    <DnD
      tag={TabPanel}
      value={currentTab}
      onChange={(tab) => setCurrentTab(tab as string)}
      onDnD={handleDnD}
    >
      {tabs.map((tab) => (
        <DnD.Draggable
          placement='bottom'
          tag={TabPanel.Item}
          value={tab}
          key={tab}
          pb={0}
          disabled={disabledTabs.includes(tab)}
        >
          {renderTab(tab)}
        </DnD.Draggable>
      ))}
    </DnD>
  );
};

export default Demo;

Secondary DataTable with drag & drop

tsx
import React from 'react';
import DataTable from 'intergalactic/data-table';
import Flag from 'intergalactic/flags';
import { Flex } from '@semcore/flex-box';
import DnD from 'intergalactic/drag-and-drop';

const data = [
  {
    countryCode: 'US',
    countryTitle: 'United States',
    cpc: 1,
    vol: 1.14,
    kd: 37.7,
  },
  {
    countryCode: 'RS',
    countryTitle: 'Serbia',
    cpc: 1,
    vol: 1.58,
    kd: 37.7,
  },
  {
    countryCode: 'ES',
    countryTitle: 'Spain',
    cpc: 3,
    vol: 2.3,
    kd: 37.7,
  },
  {
    countryCode: 'CY',
    countryTitle: 'Cyprus',
    cpc: 53,
    vol: 4.8,
    kd: 37.7,
  },
];

const Demo = () => {
  const [sortedData, setSortedData] = React.useState(data);

  const handleDnD = React.useCallback(
    ({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
      setSortedData((data) => {
        const newData = [...data];
        const shift = fromIndex < toIndex ? 1 : -1;
        for (let i = fromIndex; i !== toIndex; i += shift) {
          newData[i] = data[i + shift];
        }
        newData[toIndex] = data[fromIndex];
        return newData;
      });
    },
    [],
  );

  return (
    <DataTable use='secondary' data={sortedData} w={400} aria-label={'Table title'}>
      <DataTable.Head>
        <DataTable.Column name='countryTitle' children='Keyword' wMin={140} justifyContent='left' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' justifyContent='left' />
        <DataTable.Column name='kd' children='KD' justifyContent='right' />
      </DataTable.Head>
      <DnD onDnD={handleDnD}>
        <DataTable.Body>
          <DataTable.Cell data={data} name='countryTitle'>
            {(_, row) => ({
              children: (
                <Flex gap={1} alignItems='center'>
                  <Flag iso2={row.countryCode as any} />
                  <span>{row.countryTitle}</span>
                </Flex>
              ),
            })}
          </DataTable.Cell>
          <DataTable.Row>
            {(props) => {
              return {
                children: (
                  <DnD.Draggable tag={Flex} placement='left'>
                    {props.children}
                  </DnD.Draggable>
                ),
              };
            }}
          </DataTable.Row>
        </DataTable.Body>
      </DnD>
    </DataTable>
  );
};
export default Demo;

Primary DataTable with drag & drop

tsx
import React from 'react';
import DataTable from 'intergalactic/data-table';
import { Flex } from 'intergalactic/flex-box';
import DnD from 'intergalactic/drag-and-drop';
import Checkbox from 'intergalactic/checkbox';
import { Text } from 'intergalactic/typography';
import Link from 'intergalactic/link';
import LinkExternalM from 'intergalactic/icon/LinkExternal/m';
import { DescriptionTooltip } from 'intergalactic/tooltip';
import { ButtonLink } from 'intergalactic/button';

const data = [
  {
    domain: 'https://www.example.com',
    title: 'Example',
    cpc: 1,
    vol: 1.14,
    kd: 37.7,
  },
  {
    domain: 'https://semrush.com',
    title: 'Semrush',
    cpc: 1,
    vol: 1.58,
    kd: 37.7,
  },
  {
    domain: 'https://wikipedia.org',
    title: 'Wikipedia',
    cpc: 3,
    vol: 2.3,
    kd: 37.7,
  },
];

const Demo = () => {
  const [sortedData, setSortedData] = React.useState(data);

  const handleDnD = React.useCallback(
    ({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
      setSortedData((data) => {
        const newData = [...data];
        const shift = fromIndex < toIndex ? 1 : -1;
        for (let i = fromIndex; i !== toIndex; i += shift) {
          newData[i] = data[i + shift];
        }
        newData[toIndex] = data[fromIndex];
        return newData;
      });
    },
    [],
  );

  return (
    <DataTable data={sortedData} w={600} aria-label={'Table title'}>
      <DataTable.Head>
        <DataTable.Column name='checkbox' wMax={30}>
          <Checkbox />
        </DataTable.Column>
        <DataTable.Column name='title' children='Website' wMin={250} />
        <DataTable.Column name='hint' children='Hints' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
        <DataTable.Column name='kd' children='KD' />
      </DataTable.Head>
      <DnD onDnD={handleDnD}>
        <DataTable.Body>
          <DataTable.Cell data={data} name='checkbox'>
            {() => ({ children: <Checkbox /> })}
          </DataTable.Cell>
          <DataTable.Cell data={data} name='title'>
            {(_, row) => ({
              children: (
                <Flex direction='column' gap={1}>
                  <Text>{row.title}</Text>
                  <Link href={row.domain} addonRight={LinkExternalM}>
                    {row.domain}
                  </Link>
                </Flex>
              ),
            })}
          </DataTable.Cell>
          <DataTable.Cell data={data} name='hint' alignItems='start'>
            {() => ({
              children: (
                <DescriptionTooltip>
                  <DescriptionTooltip.Trigger tag={ButtonLink} use='secondary'>
                    About
                  </DescriptionTooltip.Trigger>
                  <DescriptionTooltip.Popper aria-label='About data'>
                    Some description of the data.
                  </DescriptionTooltip.Popper>
                </DescriptionTooltip>
              ),
            })}
          </DataTable.Cell>
          <DataTable.Row>
            {(props) => {
              return {
                children: (
                  <DnD.Draggable tag={Flex} placement='left'>
                    {props.children}
                  </DnD.Draggable>
                ),
              };
            }}
          </DataTable.Row>
        </DataTable.Body>
      </DnD>
    </DataTable>
  );
};
export default Demo;

Released under the MIT License.

Released under the MIT License.