Skip to content

Commit

Permalink
feat: drag n drop from action panel (#114)
Browse files Browse the repository at this point in the history
* feat: drag n drop from action panel

* feat: fixes for dnd showcase

* feat: fix linter errors

* feat: fix typing merge errors

* fix: dragEnd handler when it's out of screen

* fix: removed placeholder

* fix: setItem typings

* feat: add placeholder grid item

* feat: added plugin type for action panel items

* fix: fix dnd events handling and simplify plugin type context

* feat: add custom dimenstions values for dragProps

* fix: null drag props handling fix

* fix: empty drag over plugin placeholder rendering

* feat: refactoring new dnd code components

* feat: add extra field for drag props

* feat: add extra field for drag props

* fix: action panel key value moved back to list iterator

* feat: add drop props typing

* feat: added dragstart and dragend props

* fix: temporary layout render fix

* feat: updated README

* fix: remove item while drag not finished

* fix: documentation typo
  • Loading branch information
flops authored May 21, 2024
1 parent ea978ec commit 1d2e44a
Show file tree
Hide file tree
Showing 24 changed files with 739 additions and 56 deletions.
95 changes: 95 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,24 @@ interface DashKitProps {
editMode: boolean;
onItemEdit: ({id}: {id: string}) => void;
onChange: (data: {config: Config; itemsStateAndParams: ItemsStateAndParams}) => void;
onDrop: (dropProps: ItemDropProps) => void;
defaultGlobalParams: GlobalParams;
globalParams: GlobalParams;
itemsStateAndParams: ItemsStateAndParams;
settings: SettingsProps;
context: ContextProps;
overlayControls?: Record<string, OverlayControlItem[]>;
noOverlay?: boolean;
focusable?: boolean;
draggableHandleClassName?: string;
}
```

- **config**: [сonfig](#Config).
- **editMode**: Whether edit mode is enabled.
- **onItemEdit**: Called when you click to edit a widget.
- **onChange**: Called when config or [itemsStateAndParams](#temsStateAndParams) are changed.
- **onDrop**: Called when item dropped from ActionPanel using (#DashKitDnDWrapper)
- **defaultGlobalParams**, **globalParams**: [Parameters](#Params) that affect all widgets. In DataLens, `defaultGlobalParams` are global parameters set in the dashboard settings. `globalParams` are globals parameters that can be set in the url.
- **itemsStateAndParams**: [itemsStateAndParams](#temsStateAndParams).
- **settings**: DashKit settings.
Expand Down Expand Up @@ -208,15 +212,43 @@ const config: DashKitProps['config'] = {
Add a new item to the config:

```ts
const newLayout = updateLayout: [
{
h: 6,
i: 'Ea',
w: 12,
x: 0,
y: 6,
},
{
h: 4,
i: 'Dk',
w: 8,
x: 0,
y: 12,
},
];

const newConfig = DashKit.setItem({
item: {
data: {
text: `Some text`,
},
namespace: 'default',
type: 'text',
// Optional. If new item needed to be inserted in current layout with predefined dimensions
layout: { // Current item inseterted before 'Ea'
h: 6,
w: 12,
x: 0,
y: 2,
},,
},
config: config,
options: {
// Optional. New layout values for existing items when new element is dropped from ActionPanel
updateLayout: newLayout,
},
});
```

Expand Down Expand Up @@ -314,6 +346,69 @@ type MenuItem = {
DashKit.setSettings({menu: [] as Array<MenuItem>});
```

### Draggable items from ActionPanel

#### DashKitDnDWrapper

```ts
interface DashKitDnDWrapperProps {
dragImageSrc?: string;
onDragStart?: (dragProps: ItemDragProps) => void;
onDragEnd?: () => void;
}
```

**dragImageSrc**: Drag image preview, by default used transparent 1px png base64
**onDragStart**: Callback called when element is dragged from ActionPanel
**onDragEnd**: Callback called when element dropped or drag canceled

```ts
type ItemDragProps = {
type: string; // Plugin type
layout?: { // Optional. Layout item size for preview and init
w?: number;
h?: number;
};
extra?: any; // Custom user context
};
```

```ts
type ItemDropProps = {
commit: () => void; // Callback should be called after all config operations are made
dragProps: ItemDragProps; // Item drag props
itemLayout: ConfigLayout; // Calculated item layout dimensions
newLayout: ConfigLayout[]; // New layout after element is dropped
};
```


#### Example:

```jsx
const menuItems = [
{
id: 'chart',
icon: <Icon data={ChartColumn} />,
title: 'Chart',
qa: 'chart',
dragProps: { // ItemDragProps
type: 'custom', // Registered plugin type
},
}
]

const onDrop = (dropProps: ItemDropProps) => {
// ... add element to your config
dropProps.commit();
}

<DashKitDnDWrapper>
<DashKit editMode={true} config={config} onChange={onChange} onDrop={onDrop} />
<ActionPanel items={menuItems} />
</DashKitDnDWrapper>
```

### CSS API

| Name | Description |
Expand Down
41 changes: 24 additions & 17 deletions src/components/ActionPanel/ActionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,44 @@ import React from 'react';

import {CSSTransition} from 'react-transition-group';

import {useDnDItemProps} from '../../hooks/useDnDItemProps';
import {cn} from '../../utils/cn';

import {ActionPanelProps} from './types';
import {ActionPanelItem, ActionPanelProps} from './types';

import './ActionPanel.scss';

const b = cn('dashkit-action-panel');

export const ActionPanelItemContainer = ({item}: {item: ActionPanelItem}) => {
const dndProps = useDnDItemProps(item);

return (
<div
role="button"
className={b('item', {draggable: Boolean(dndProps)}, item.className)}
onClick={item.onClick}
data-qa={item.qa}
{...dndProps}
>
<div className={b('icon')}>{item.icon}</div>
<div className={b('title')} title={item.title}>
{item.title}
</div>
</div>
);
};

export const ActionPanel = (props: ActionPanelProps) => {
const isDisabled = props.disable ?? false;
const isAnimated = props.toggleAnimation ?? false;
const nodeRef = React.useRef<HTMLDivElement | null>(null);

const content = (
<div ref={nodeRef} className={b(null, props.className)}>
{props.items.map((item) => {
return (
<div
role="button"
className={b('item', item.className)}
key={`dk-action-panel-${item.id}`}
onClick={item.onClick}
data-qa={item.qa}
>
<div className={b('icon')}>{item.icon}</div>
<div className={b('title')} title={item.title}>
{item.title}
</div>
</div>
);
})}
{props.items.map((item) => (
<ActionPanelItemContainer key={`dk-action-panel-${item.id}`} item={item} />
))}
</div>
);

Expand Down
5 changes: 4 additions & 1 deletion src/components/ActionPanel/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react';

import type {ItemDragProps} from '../../shared';

export type ActionPanelItem = {
id: string;
icon: React.ReactNode;
title: string;
onClick?: () => void;
className?: string;
qa?: string;
onClick?: () => void;
dragProps?: ItemDragProps;
};

export type ActionPanelProps = {
Expand Down
14 changes: 11 additions & 3 deletions src/components/DashKit/DashKit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import React from 'react';
import noop from 'lodash/noop';

import {DEFAULT_NAMESPACE} from '../../constants';
import type {Config, ConfigItem, GlobalParams, ItemsStateAndParams} from '../../shared';
import type {
Config,
ConfigItem,
GlobalParams,
ItemDropProps,
ItemsStateAndParams,
} from '../../shared';
import {
AddConfigItem,
ContextProps,
Plugin,
SetConfigItem,
SetItemOptions,
SetNewItemOptions,
Settings,
SettingsProps,
} from '../../typings';
Expand All @@ -28,6 +34,7 @@ interface DashKitGeneralProps {
interface DashKitDefaultProps {
onItemEdit: (item: ConfigItem) => void;
onChange: (data: {config: Config; itemsStateAndParams: ItemsStateAndParams}) => void;
onDrop: (dropProps: ItemDropProps) => void;
defaultGlobalParams: GlobalParams;
globalParams: GlobalParams;
itemsStateAndParams: ItemsStateAndParams;
Expand All @@ -47,6 +54,7 @@ export class DashKit extends React.PureComponent<DashKitInnerProps> {
static defaultProps: DashKitDefaultProps = {
onItemEdit: noop,
onChange: noop,
onDrop: noop,
defaultGlobalParams: {},
globalParams: {},
itemsStateAndParams: {},
Expand Down Expand Up @@ -78,7 +86,7 @@ export class DashKit extends React.PureComponent<DashKitInnerProps> {
item: SetConfigItem;
namespace?: string;
config: Config;
options?: SetItemOptions;
options?: SetNewItemOptions;
}): Config {
if (setItem.id) {
return UpdateManager.editItem({item: setItem, namespace, config, options});
Expand Down
4 changes: 4 additions & 0 deletions src/components/DashKit/__stories__/DashKit.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {cn} from '../../../utils/cn';
import {DashKit, DashKitProps} from '../DashKit';

import {CssApiShowcase} from './CssApiShowcase';
import {DashKitDnDShowcase} from './DashKitDnDShowcase';
import {DashKitShowcase} from './DashKitShowcase';
import {getConfig} from './utils';

Expand Down Expand Up @@ -77,3 +78,6 @@ export const Showcase = ShowcaseTemplate.bind({});

const CssApiShowcaseTemplate: Story<DashKitProps> = () => <CssApiShowcase />;
export const CSS_API = CssApiShowcaseTemplate.bind({});

const DndShowcaseTemplate: Story<DashKitProps> = () => <DashKitDnDShowcase />;
export const DragNDrop = DndShowcaseTemplate.bind({});
Loading

0 comments on commit 1d2e44a

Please sign in to comment.