From 860976f1b42bfc75b0e6d70610e4b95127ead939 Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Tue, 16 Jul 2024 13:51:16 +0300 Subject: [PATCH 1/5] feat: moved overlay controls into context --- src/components/DashKitView/DashKitView.tsx | 4 ++-- src/components/GridItem/GridItem.js | 10 ++------ src/components/GridLayout/GridLayout.js | 2 -- .../OverlayControls/OverlayControls.tsx | 24 +++++++++++++++---- src/hocs/withContext.js | 4 +++- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/components/DashKitView/DashKitView.tsx b/src/components/DashKitView/DashKitView.tsx index d16c4b0..4dd9010 100644 --- a/src/components/DashKitView/DashKitView.tsx +++ b/src/components/DashKitView/DashKitView.tsx @@ -17,7 +17,7 @@ type DashKitViewProps = DashKitProps & { registerManager: RegisterManager; }; -function DashKitView(props: DashKitViewProps) { +function DashKitView() { const context = React.useContext(DashKitContext); const {registerManager, forwardedMetaRef} = context; return ( @@ -25,7 +25,7 @@ function DashKitView(props: DashKitViewProps) { {registerManager.settings.isMobile ? ( ) : ( - + )} ); diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index 6aaae80..dd3e7b4 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -51,7 +51,6 @@ class GridItem extends React.PureComponent { item: PropTypes.object, isDragging: PropTypes.bool, layout: PropTypes.array, - overlayControls: PropTypes.object, forwardedRef: PropTypes.any, forwardedPluginRef: PropTypes.any, @@ -77,7 +76,7 @@ class GridItem extends React.PureComponent { }; renderOverlay() { - const {overlayControls, isPlaceholder} = this.props; + const {isPlaceholder} = this.props; const {editMode} = this.context; if (!editMode || this.props.item.data._editActive || isPlaceholder) { @@ -85,16 +84,11 @@ class GridItem extends React.PureComponent { } const {item} = this.props; - const controls = overlayControls && overlayControls[item.type]; return (
- + ); } diff --git a/src/components/GridLayout/GridLayout.js b/src/components/GridLayout/GridLayout.js index c72acd0..37bfa27 100644 --- a/src/components/GridLayout/GridLayout.js +++ b/src/components/GridLayout/GridLayout.js @@ -283,7 +283,6 @@ export default class GridLayout extends React.PureComponent { isPlaceholder={true} noOverlay={noOverlay} withCustomHandle={Boolean(draggableHandleClassName)} - overlayControls={this.props.overlayControls} /> ); } @@ -348,7 +347,6 @@ export default class GridLayout extends React.PureComponent { noOverlay={noOverlay} focusable={focusable} withCustomHandle={Boolean(draggableHandleClassName)} - overlayControls={this.props.overlayControls} /> ); })} diff --git a/src/components/OverlayControls/OverlayControls.tsx b/src/components/OverlayControls/OverlayControls.tsx index ebed98c..a3bc360 100644 --- a/src/components/OverlayControls/OverlayControls.tsx +++ b/src/components/OverlayControls/OverlayControls.tsx @@ -47,6 +47,7 @@ export interface OverlayControlItem { handler?: (item: ConfigItem) => void; allWidgetsControls?: boolean; // флаг кастомного контрола (без кастомного виджета), которые показываются не в списке меню excludeWidgetsTypes?: Array; // массив с типами виджетов (плагинов), которые исключаем из отображения контрола по настройке allWidgetsControls + filterCustomControlItem?: (item: ConfigItem) => boolean; id?: string; // id дефолтного пункта меню для возможноти использования дефолтного action в кастомных контролах qa?: string; } @@ -70,8 +71,6 @@ interface OverlayControlsDefaultProps { interface OverlayControlsProps extends OverlayControlsDefaultProps { configItem: ConfigItem; - items?: OverlayControlItem[]; - overlayControls?: Record; } type PreparedCopyItemOptionsArg = Pick & { @@ -87,6 +86,7 @@ export type PreparedCopyItemOptions = PreparedCopyItemOpt }; type DashKitCtx = React.Context<{ + overlayControls?: Record; context: Record; registerManager: RegisterManager; itemsParams: Record; @@ -107,7 +107,8 @@ class OverlayControls extends React.Component { }; context!: React.ContextType; render() { - const {items = [], position} = this.props; + const {position} = this.props; + const items = this.getItems(); const hasCustomControlsWithWidgets = items.length > 0; const controls = hasCustomControlsWithWidgets @@ -116,6 +117,14 @@ class OverlayControls extends React.Component { return
{controls}
; } + + private getItems = () => { + const {overlayControls} = this.context; + const {configItem} = this.props; + + return (overlayControls && overlayControls[configItem.type]) || []; + }; + private renderControlsItem = (item: OverlayControlItem, index: number, length: number) => { const {view, size} = this.props; const {title, handler, icon, iconSize, qa} = item; @@ -294,7 +303,7 @@ class OverlayControls extends React.Component { // выбираем только items-ы у которых проставлено поле `allWidgetsControls:true` // те контролы, которые будут показываться слева от меню let controls: OverlayControlItem[] = []; - for (const controlItem of Object.values(this.props.overlayControls || {})) { + for (const controlItem of Object.values(this.context.overlayControls || {})) { controls = controls.concat( ( (controlItem as OverlayControlItem[]).filter((item) => { @@ -302,6 +311,11 @@ class OverlayControls extends React.Component { if (item.excludeWidgetsTypes?.includes(this.props.configItem.type)) { return false; } + + if (item.filterCustomControlItem) { + return item.filterCustomControlItem(this.props.configItem); + } + return item.allWidgetsControls; }) || [] ).map((item) => { @@ -379,7 +393,7 @@ class OverlayControls extends React.Component { iconSize: 12, handler: this.onRemoveItem, }; - const {items = []} = this.props; + const items = this.getItems(); const customOverlayControls = [...items, deleteControl]; return customOverlayControls.map( (item: OverlayControlItem, index: number, controlItems: OverlayControlItem[]) => diff --git a/src/hocs/withContext.js b/src/hocs/withContext.js index c270535..4ac51be 100644 --- a/src/hocs/withContext.js +++ b/src/hocs/withContext.js @@ -417,6 +417,7 @@ function useMemoStateContext(props) { draggableHandleClassName: props.draggableHandleClassName, outerDnDEnable, dragOverPlugin, + overlayControls: props.overlayControls, }), [ resultLayout, @@ -446,6 +447,7 @@ function useMemoStateContext(props) { props.draggableHandleClassName, outerDnDEnable, dragOverPlugin, + props.overlayControls, ], ); } @@ -456,7 +458,7 @@ export function withContext(Component) { return ( - + ); }; From e401d019a1e35c32e8fab890057cde4f1e839523 Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Wed, 17 Jul 2024 20:13:18 +0300 Subject: [PATCH 2/5] feat: separated context for overlays --- src/components/DashKit/DashKit.tsx | 2 + .../DashKit/__stories__/CssApiShowcase.tsx | 28 ------- .../__stories__/DashKitDnDShowcase.tsx | 28 ------- .../__stories__/DashKitGroupsShowcase.tsx | 41 +++++----- .../DashKit/__stories__/DashKitShowcase.tsx | 53 ++++++------ .../OverlayControls/OverlayControls.tsx | 82 +++++++++---------- src/context/DashKitContext.js | 4 +- src/hocs/withContext.js | 59 ++++++++++--- src/typings/common.ts | 1 + 9 files changed, 138 insertions(+), 160 deletions(-) diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index 392783d..6621d4b 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -14,6 +14,7 @@ import { AddConfigItem, ContextProps, DashKitGroup, + MenuItem, Plugin, SetConfigItem, SetNewItemOptions, @@ -30,6 +31,7 @@ interface DashKitGeneralProps { editMode: boolean; draggableHandleClassName?: string; overlayControls?: Record; + menuItems?: MenuItem[]; } interface DashKitDefaultProps { diff --git a/src/components/DashKit/__stories__/CssApiShowcase.tsx b/src/components/DashKit/__stories__/CssApiShowcase.tsx index 9c98ea6..76260ba 100644 --- a/src/components/DashKit/__stories__/CssApiShowcase.tsx +++ b/src/components/DashKit/__stories__/CssApiShowcase.tsx @@ -11,39 +11,11 @@ import { import {Icon} from '@gravity-ui/uikit'; import {ActionPanel, DashKit} from '../../..'; -import {MenuItems} from '../../../helpers'; -import {i18n} from '../../../i18n'; -import {CogIcon} from '../../../icons/CogIcon'; -import {CopyIcon} from '../../../icons/CopyIcon'; -import {DeleteIcon} from '../../../icons/DeleteIcon'; import {Demo, DemoRow} from './Demo'; import {getConfig} from './utils'; export const CssApiShowcase: React.FC = () => { - React.useEffect(() => { - DashKit.setSettings({ - menu: [ - { - id: 'settings', - title: 'Menu setting text', - icon: , - }, - { - id: MenuItems.Copy, - title: 'Menu setting copy', - icon: , - }, - { - id: MenuItems.Delete, - title: i18n('label_delete'), // for language change check - icon: , - className: 'dashkit-overlay-controls__item_danger', - }, - ], - }); - }, []); - const items = React.useMemo( () => [ { diff --git a/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx b/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx index 6d34395..4b59a16 100644 --- a/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx @@ -4,39 +4,11 @@ import {ChartColumn, Copy, Heading, Sliders, TextAlignLeft} from '@gravity-ui/ic import {Icon} from '@gravity-ui/uikit'; import {ActionPanel, DashKit, DashKitDnDWrapper, DashKitProps} from '../../..'; -import {MenuItems} from '../../../helpers'; -import {i18n} from '../../../i18n'; -import {CogIcon} from '../../../icons/CogIcon'; -import {CopyIcon} from '../../../icons/CopyIcon'; -import {DeleteIcon} from '../../../icons/DeleteIcon'; import {Demo, DemoRow} from './Demo'; import {getConfig} from './utils'; export const DashKitDnDShowcase: React.FC = () => { - React.useEffect(() => { - DashKit.setSettings({ - menu: [ - { - id: 'settings', - title: 'Menu setting text', - icon: , - }, - { - id: MenuItems.Copy, - title: 'Menu setting copy', - icon: , - }, - { - id: MenuItems.Delete, - title: i18n('label_delete'), // for language change check - icon: , - className: 'dashkit-overlay-controls__item_danger', - }, - ], - }); - }, []); - const onClick = () => { console.log('click'); }; diff --git a/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx b/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx index 8f09fb8..d32cad0 100644 --- a/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx @@ -24,27 +24,25 @@ import {fixedGroup, getConfig} from './utils'; export const DashKitGroupsShowcase: React.FC = () => { const [editMode, setEditMode] = React.useState(true); - React.useEffect(() => { - DashKit.setSettings({ - menu: [ - { - id: 'settings', - title: 'Menu setting text', - icon: , - }, - { - id: MenuItems.Copy, - title: 'Menu setting copy', - icon: , - }, - { - id: MenuItems.Delete, - title: i18n('label_delete'), // for language change check - icon: , - className: 'dashkit-overlay-controls__item_danger', - }, - ], - }); + const menuItems = React.useMemo(() => { + return [ + { + id: 'settings', + title: 'Menu setting text', + icon: , + }, + { + id: MenuItems.Copy, + title: 'Menu setting copy', + icon: , + }, + { + id: MenuItems.Delete, + title: i18n('label_delete'), // for language change check + icon: , + className: 'dashkit-overlay-controls__item_danger', + }, + ]; }, []); const onClick = () => { @@ -220,6 +218,7 @@ export const DashKitGroupsShowcase: React.FC = () => { config={config} onChange={onChange} onDrop={onDrop} + menuItems={menuItems} /> diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index fea6a49..b12a271 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -18,7 +18,7 @@ import {CopyIcon} from '../../../icons/CopyIcon'; import {DeleteIcon} from '../../../icons/DeleteIcon'; import {TickIcon} from '../../../icons/TickIcon'; import {WarningIcon} from '../../../icons/WarningIcon'; -import type {ConfigItem} from '../../../index'; +import type {ConfigItem, MenuItem, OverlayControlItem} from '../../../index'; import {cn} from '../../../utils/cn'; import {Demo, DemoRow} from './Demo'; @@ -38,9 +38,9 @@ type DashKitDemoState = { lastAction: string; customControlsActionData: number; - showCustomMenu: boolean; enableActionPanel: boolean; enableAnimations: boolean; + menuItems: MenuItem[]; }; export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { @@ -57,21 +57,19 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { lastAction: 'Nothing', customControlsActionData: 0, - showCustomMenu: true, enableActionPanel: false, enableAnimations: true, + menuItems: [], }; + private controls: Record; + private dashKitRef = React.createRef(); - componentDidMount() { - this.toggleCustomMenu(true); - } + constructor(props: {}) { + super(props); - render() { - console.log('customControlsActionData', this.state.customControlsActionData); - const {editMode} = this.state; - const controls = { + this.controls = { custom: [ { title: 'Edit custom widget', @@ -87,6 +85,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { icon: TickIcon, handler: () => console.log('overlayControls::custom click'), iconSize: 16, + visible: (item) => item.type !== 'text', }, { allWidgetsControls: true, @@ -94,14 +93,21 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { id: MenuItems.Settings, title: 'Settings default', icon: WarningIcon, + visible: (item) => item.type !== 'text', }, { allWidgetsControls: true, title: 'Icon tooltip 2', handler: () => console.log('overlayControls::custom click'), + visible: (item) => item.type !== 'text', }, ], }; + } + + render() { + console.log('customControlsActionData', this.state.customControlsActionData); + const {editMode} = this.state; return ( @@ -128,11 +134,11 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { @@ -207,7 +213,8 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { onChange={this.onChange} settings={this.state.settings} ref={this.dashKitRef} - overlayControls={controls} + overlayControls={this.controls} + menuItems={this.state.menuItems} focusable={!editMode} /> @@ -346,13 +353,14 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { return Boolean(this.state.config.items.find((item) => item.id === titleId)); } - private toggleCustomMenu = (init = false) => { - const {showCustomMenu} = this.state; - if (showCustomMenu) { - DashKit.setSettings({menu: []}); + private toggleCustomMenu = () => { + const {menuItems} = this.state; + + if (menuItems.length > 0) { + this.setState({menuItems: []}); } else { - DashKit.setSettings({ - menu: [ + this.setState({ + menuItems: [ { id: 'settings', title: 'Menu setting text', @@ -360,6 +368,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { handler: () => { console.log('menu::settings::click'); }, + visible: (item) => item.type === 'title', }, { id: MenuItems.Copy, @@ -375,12 +384,6 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { ], }); } - this.setState({ - showCustomMenu: !showCustomMenu, - lastAction: init - ? this.state.lastAction - : `[DashKit.setSettings] toggle show custom widget menu: ${new Date().toISOString()}`, - }); }; private toggleActionPanel() { diff --git a/src/components/OverlayControls/OverlayControls.tsx b/src/components/OverlayControls/OverlayControls.tsx index a3bc360..7031efe 100644 --- a/src/components/OverlayControls/OverlayControls.tsx +++ b/src/components/OverlayControls/OverlayControls.tsx @@ -5,6 +5,7 @@ import { ButtonSize, ButtonView, DropdownMenu, + DropdownMenuItem, Icon, IconProps, MenuItemProps, @@ -12,22 +13,14 @@ import { import noop from 'lodash/noop'; import {COPIED_WIDGET_STORE_KEY, MenuItems, OVERLAY_CONTROLS_CLASS_NAME} from '../../constants'; -import {DashKitContext} from '../../context/DashKitContext'; +import {DashkitOvelayControlsContext} from '../../context/DashKitContext'; import {i18n} from '../../i18n'; import {CloseIcon} from '../../icons/CloseIcon'; import {CogIcon} from '../../icons/CogIcon'; import {DotsIcon} from '../../icons/DotsIcon'; -import type { - Config, - ConfigItem, - ConfigLayout, - ItemState, - PluginBase, - StringParams, -} from '../../shared'; -import {Settings} from '../../typings'; +import type {ConfigItem, ConfigLayout, ItemState, PluginBase, StringParams} from '../../shared'; +import {MenuItem, Settings} from '../../typings'; import {cn} from '../../utils/cn'; -import type {RegisterManager} from '../../utils/register-manager'; import './OverlayControls.scss'; @@ -47,7 +40,7 @@ export interface OverlayControlItem { handler?: (item: ConfigItem) => void; allWidgetsControls?: boolean; // флаг кастомного контрола (без кастомного виджета), которые показываются не в списке меню excludeWidgetsTypes?: Array; // массив с типами виджетов (плагинов), которые исключаем из отображения контрола по настройке allWidgetsControls - filterCustomControlItem?: (item: ConfigItem) => boolean; + visible?: (item: ConfigItem) => boolean; id?: string; // id дефолтного пункта меню для возможноти использования дефолтного action в кастомных контролах qa?: string; } @@ -88,18 +81,18 @@ export type PreparedCopyItemOptions = PreparedCopyItemOpt type DashKitCtx = React.Context<{ overlayControls?: Record; context: Record; - registerManager: RegisterManager; + menu: MenuItem[]; itemsParams: Record; itemsState: Record; editItem: (item: ConfigItem) => void; removeItem: (id: string) => void; - config: Config; + getLayoutItem: (id: string) => ConfigLayout | void; }>; const DEFAULT_DROPDOWN_MENU = [MenuItems.Copy, MenuItems.Delete]; class OverlayControls extends React.Component { - static contextType = DashKitContext; + static contextType = DashkitOvelayControlsContext; static defaultProps: OverlayControlsDefaultProps = { position: OverlayControlsPosition.TopRight, view: 'flat', @@ -210,9 +203,8 @@ class OverlayControls extends React.Component { } private renderMenu() { const {view, size} = this.props; - const {registerManager} = this.context; - const withMenu = - Array.isArray(registerManager.settings.menu) && registerManager.settings.menu.length; + const {menu} = this.context; + const withMenu = Array.isArray(menu) && menu.length; return ( @@ -240,28 +232,32 @@ class OverlayControls extends React.Component { } private renderDropdownMenu() { const {view, size} = this.props; - const {registerManager, itemsParams, itemsState} = this.context; + const {menu: contextMenu, itemsParams, itemsState} = this.context; const configItem = this.props.configItem; const itemParams = itemsParams[configItem.id]; const itemState = itemsState[configItem.id]; - let menu = registerManager.settings.menu as any; - if (!menu.length) { - menu = DEFAULT_DROPDOWN_MENU; - } + const menu = contextMenu?.length > 0 ? contextMenu : DEFAULT_DROPDOWN_MENU; const isDefaultMenu = this.isDefaultMenu(menu); - let items = isDefaultMenu - ? (menu || []).map((name: string) => this.getDropDownMenuItemConfig(name, true)) - : menu.map((item: OverlayCustomControlItem) => { + const items: DropdownMenuItem[] = isDefaultMenu + ? ((menu || []) as string[]).reduce((memo, name: string) => { + const item = this.getDropDownMenuItemConfig(name, true); + if (item) { + memo.push(item); + } + + return memo; + }, []) + : menu.reduce((memo, item: MenuItem) => { if (typeof item === 'string') { - return null; + return memo; } // custom menu dropdown item filter if (item.visible && !item.visible(configItem)) { - return null; + return memo; } const itemHandler = item.handler; @@ -271,16 +267,17 @@ class OverlayControls extends React.Component { ? () => itemHandler(configItem, itemParams, itemState) : this.getDropDownMenuItemConfig(item.id)?.action || (() => {}); - return { + memo.push({ // @ts-expect-error text: item.title || i18n(item.id), icon: item.icon, action: itemAction, className: item.className, qa: item.qa, - }; - }); - items = items.filter(Boolean); + }); + + return memo; + }, []); return ( { return false; } - if (item.filterCustomControlItem) { - return item.filterCustomControlItem(this.props.configItem); - } - - return item.allWidgetsControls; + return item.visible + ? item.visible(this.props.configItem) + : item.allWidgetsControls; }) || [] ).map((item) => { if (!item?.id) { @@ -335,16 +330,15 @@ class OverlayControls extends React.Component { return controls; }; private onCopyItem = () => { - const correspondedItemLayout = this.context.config.layout.find((item: ConfigLayout) => { - return item.i === this.props.configItem.id; - }); + const {configItem} = this.props; + const correspondedItemLayout = this.context.getLayoutItem(configItem.id); let options: PreparedCopyItemOptions = { timestamp: Date.now(), - data: this.props.configItem.data, - type: this.props.configItem.type, - defaults: this.props.configItem.defaults, - namespace: this.props.configItem.namespace, + data: configItem.data, + type: configItem.type, + defaults: configItem.defaults, + namespace: configItem.namespace, layout: { w: correspondedItemLayout!.w, h: correspondedItemLayout!.h, diff --git a/src/context/DashKitContext.js b/src/context/DashKitContext.js index b0ae923..817fc6f 100644 --- a/src/context/DashKitContext.js +++ b/src/context/DashKitContext.js @@ -2,6 +2,8 @@ import React from 'react'; const DashKitContext = React.createContext(); +const DashkitOvelayControlsContext = React.createContext(); + const DashKitDnDContext = React.createContext(); -export {DashKitContext, DashKitDnDContext}; +export {DashKitContext, DashkitOvelayControlsContext, DashKitDnDContext}; diff --git a/src/hocs/withContext.js b/src/hocs/withContext.js index 4ac51be..aebc0e5 100644 --- a/src/hocs/withContext.js +++ b/src/hocs/withContext.js @@ -10,7 +10,11 @@ import { DEFAULT_WIDGET_WIDTH, TEMPORARY_ITEM_ID, } from '../constants/common'; -import {DashKitContext, DashKitDnDContext} from '../context/DashKitContext'; +import { + DashKitContext, + DashKitDnDContext, + DashkitOvelayControlsContext, +} from '../context/DashKitContext'; import {useDeepEqualMemo} from '../hooks/useDeepEqualMemo'; import {getItemsParams, getItemsState} from '../shared'; import {UpdateManager} from '../utils'; @@ -59,6 +63,13 @@ function useMemoStateContext(props) { [props.config, props.itemsStateAndParams, props.onChange], ); + const getLayoutItem = React.useCallback( + (widgetId) => { + return props.config.layout.find(({i}) => i === widgetId); + }, + [props.config.layout], + ); + // каллбэк вызывающийся при изменение лэйаута сетки, первым аргументом приходит актуальный конфиг лэйаута, // т.е. если на текущей сетке есть виджеты, с активированной опцией автовысоты, их параметр "h" будет // "подстроенный"; чтобы, для сохранения в сторе "ушли" значения без учёта подстройки (как если бы у этих @@ -270,7 +281,7 @@ function useMemoStateContext(props) { return props.layout.map((item) => { const widgetId = item.i; - if (adjusted[widgetId] || nowrapAdjust[widgetId]) { + if (widgetId in adjusted || widgetId in nowrapAdjust) { original[widgetId] = item; // eslint-disable-next-line no-unused-vars const {parent, ...adjustedItem} = adjusted[widgetId] || item; @@ -283,13 +294,13 @@ function useMemoStateContext(props) { adjustedItem.parent = item.parent; } - if (nowrapAdjust[widgetId]) { + if (widgetId in nowrapAdjust) { adjustedItem.maxW = nowrapAdjust[widgetId]; } return adjustedItem; } else { - if (original[widgetId]) { + if (widgetId in original) { delete original[widgetId]; } return item; @@ -387,7 +398,7 @@ function useMemoStateContext(props) { [dragProps, onDropProp, setTemporaryLayout, resetTemporaryLayout], ); - return React.useMemo( + const dashkitContextValue = React.useMemo( () => ({ layout: resultLayout, temporaryLayout, @@ -404,10 +415,8 @@ function useMemoStateContext(props) { itemsParams, registerManager: props.registerManager, onItemStateAndParamsChange, - removeItem: onItemRemove, onDrop, onDropDragOver, - editItem: props.onItemEdit, layoutChange: onLayoutChange, getItemsMeta, reloadItems, @@ -417,7 +426,6 @@ function useMemoStateContext(props) { draggableHandleClassName: props.draggableHandleClassName, outerDnDEnable, dragOverPlugin, - overlayControls: props.overlayControls, }), [ resultLayout, @@ -434,10 +442,8 @@ function useMemoStateContext(props) { itemsParams, props.registerManager, onItemStateAndParamsChange, - onItemRemove, onDrop, onDropDragOver, - props.onItemEdit, onLayoutChange, getItemsMeta, reloadItems, @@ -447,18 +453,45 @@ function useMemoStateContext(props) { props.draggableHandleClassName, outerDnDEnable, dragOverPlugin, + ], + ); + + const menuItems = props.menuItems || props.registerManager.settings.menu; + const controlsContextValue = React.useMemo( + () => ({ + overlayControls: props.overlayControls, + context: props.context, + menu: menuItems, + itemsParams: itemsParams, + itemsState: itemsState, + editItem: props.onItemEdit, + removeItem: onItemRemove, + getLayoutItem: getLayoutItem, + }), + [ + itemsParams, + itemsState, + props.context, + props.onItemEdit, + onItemRemove, props.overlayControls, + menuItems, + getLayoutItem, ], ); + + return {controlsContextValue, dashkitContextValue}; } export function withContext(Component) { const WithContext = (props) => { - const contextValue = useMemoStateContext(props); + const {dashkitContextValue, controlsContextValue} = useMemoStateContext(props); return ( - - + + + + ); }; diff --git a/src/typings/common.ts b/src/typings/common.ts index 051758a..9d2f8fc 100644 --- a/src/typings/common.ts +++ b/src/typings/common.ts @@ -8,6 +8,7 @@ export interface Settings { gridLayout?: ReactGridLayout.ReactGridLayoutProps; theme?: string; isMobile?: boolean; + // @deprecated as it's possibly mutable property use Dashkit menuItems property instead menu?: Array; } From cb88d0e2cf344036f1ca8b74a44bc9258e2b128e Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Thu, 18 Jul 2024 00:36:36 +0300 Subject: [PATCH 3/5] feat: clean up code and added README --- README.md | 14 +++++++--- src/components/DashKit/DashKit.tsx | 4 +-- .../__stories__/DashKitDnDShowcase.tsx | 8 +++++- .../__stories__/DashKitGroupsShowcase.tsx | 4 +-- .../DashKit/__stories__/DashKitShowcase.tsx | 18 ++++++------ .../OverlayControls/OverlayControls.tsx | 21 ++++++++++---- src/hocs/withContext.js | 28 +++++++++---------- src/typings/common.ts | 2 +- 8 files changed, 59 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 453ad8c..b21ee44 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ interface DashKitProps { itemsStateAndParams: ItemsStateAndParams; settings: SettingsProps; context: ContextProps; - overlayControls?: Record; + overlayControls?: Record | null; + overlayMenuItems?: MenuItems[] | null; noOverlay?: boolean; focusable?: boolean; draggableHandleClassName?: string; @@ -51,7 +52,8 @@ interface DashKitProps { - **itemsStateAndParams**: [itemsStateAndParams](#temsStateAndParams). - **settings**: DashKit settings. - **context**: Object that will be propped up on all widgets. -- **overlayControls**: Object that overrides widget controls at the time of editing. If not transmitted, basic controls will be displayed. +- **overlayControls**: Object that overrides widget controls at the time of editing. If not transmitted, basic controls will be displayed. If `null` passed only close button or custom menu will be displayed. +- **overlayMenuItems**: Custom dropdown menu items - **noOverlay**: If `true`, overlay and controls are not displayed while editing. - **focusable**: If `true`, grid items will be focusable. - **draggableHandleClassName** : СSS class name of the element that makes the widget draggable. @@ -343,6 +345,10 @@ type MenuItem = { }; // use array of menu items in settings + | null} /> + +[deprecated] +// overlayMenuItems property has greater priority over setSettings menu DashKit.setSettings({menu: [] as Array}); ``` @@ -386,7 +392,7 @@ type ItemDropProps = { #### Example: ```jsx -const menuItems = [ +const overlayMenuItems = [ { id: 'chart', icon: , @@ -405,7 +411,7 @@ const onDrop = (dropProps: ItemDropProps) => { - + ``` diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index 6621d4b..40619a0 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -30,8 +30,8 @@ interface DashKitGeneralProps { config: Config; editMode: boolean; draggableHandleClassName?: string; - overlayControls?: Record; - menuItems?: MenuItem[]; + overlayControls?: Record | null; + overlayMenuItems?: MenuItem[] | null; } interface DashKitDefaultProps { diff --git a/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx b/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx index 4b59a16..07b4518 100644 --- a/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitDnDShowcase.tsx @@ -128,7 +128,13 @@ export const DashKitDnDShowcase: React.FC = () => { > - + diff --git a/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx b/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx index d32cad0..f89c76e 100644 --- a/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx @@ -24,7 +24,7 @@ import {fixedGroup, getConfig} from './utils'; export const DashKitGroupsShowcase: React.FC = () => { const [editMode, setEditMode] = React.useState(true); - const menuItems = React.useMemo(() => { + const overlayMenuItems = React.useMemo(() => { return [ { id: 'settings', @@ -218,7 +218,7 @@ export const DashKitGroupsShowcase: React.FC = () => { config={config} onChange={onChange} onDrop={onDrop} - menuItems={menuItems} + overlayMenuItems={overlayMenuItems} /> diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index b12a271..c4a1750 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -18,7 +18,7 @@ import {CopyIcon} from '../../../icons/CopyIcon'; import {DeleteIcon} from '../../../icons/DeleteIcon'; import {TickIcon} from '../../../icons/TickIcon'; import {WarningIcon} from '../../../icons/WarningIcon'; -import type {ConfigItem, MenuItem, OverlayControlItem} from '../../../index'; +import type {ConfigItem, OverlayControlItem} from '../../../index'; import {cn} from '../../../utils/cn'; import {Demo, DemoRow} from './Demo'; @@ -40,7 +40,7 @@ type DashKitDemoState = { customControlsActionData: number; enableActionPanel: boolean; enableAnimations: boolean; - menuItems: MenuItem[]; + overlayMenuItems: DashKitProps['overlayMenuItems']; }; export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { @@ -59,7 +59,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { customControlsActionData: 0, enableActionPanel: false, enableAnimations: true, - menuItems: [], + overlayMenuItems: null, }; private controls: Record; @@ -138,7 +138,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { className={b('btn-contol')} disabled={!editMode} > - {this.state.menuItems.length > 0 + {this.state.overlayMenuItems ? 'Disable custom menu' : 'Enable custom menu'} @@ -214,7 +214,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { settings={this.state.settings} ref={this.dashKitRef} overlayControls={this.controls} - menuItems={this.state.menuItems} + overlayMenuItems={this.state.overlayMenuItems} focusable={!editMode} /> @@ -354,13 +354,11 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { } private toggleCustomMenu = () => { - const {menuItems} = this.state; - - if (menuItems.length > 0) { - this.setState({menuItems: []}); + if (this.state.overlayMenuItems) { + this.setState({overlayMenuItems: null}); } else { this.setState({ - menuItems: [ + overlayMenuItems: [ { id: 'settings', title: 'Menu setting text', diff --git a/src/components/OverlayControls/OverlayControls.tsx b/src/components/OverlayControls/OverlayControls.tsx index 7031efe..a48bd48 100644 --- a/src/components/OverlayControls/OverlayControls.tsx +++ b/src/components/OverlayControls/OverlayControls.tsx @@ -38,9 +38,9 @@ export interface OverlayControlItem { icon?: IconProps['data']; iconSize?: number | string; handler?: (item: ConfigItem) => void; + visible?: (item: ConfigItem) => boolean; allWidgetsControls?: boolean; // флаг кастомного контрола (без кастомного виджета), которые показываются не в списке меню excludeWidgetsTypes?: Array; // массив с типами виджетов (плагинов), которые исключаем из отображения контрола по настройке allWidgetsControls - visible?: (item: ConfigItem) => boolean; id?: string; // id дефолтного пункта меню для возможноти использования дефолтного action в кастомных контролах qa?: string; } @@ -164,12 +164,15 @@ class OverlayControls extends React.Component { } return null; } - private renderControls() { + + private getDefaultControls = () => { const {view, size} = this.props; - const customLeftControls = this.getCustomLeftOverlayControls(); - const hasCustomOverlayLeftControls = Boolean(customLeftControls.length); - const defaultControl = ( + if (this.context.overlayControls === null) { + return null; + } + + return ( ); + }; + + private renderControls() { + const customLeftControls = this.getCustomLeftOverlayControls(); + const hasCustomOverlayLeftControls = Boolean(customLeftControls.length); + + const defaultControl = this.getDefaultControls(); const controls = hasCustomOverlayLeftControls ? customLeftControls.map( (item: OverlayControlItem, index: number, items: OverlayControlItem[]) => @@ -300,6 +310,7 @@ class OverlayControls extends React.Component { // выбираем только items-ы у которых проставлено поле `allWidgetsControls:true` // те контролы, которые будут показываться слева от меню let controls: OverlayControlItem[] = []; + for (const controlItem of Object.values(this.context.overlayControls || {})) { controls = controls.concat( ( diff --git a/src/hocs/withContext.js b/src/hocs/withContext.js index aebc0e5..bca1217 100644 --- a/src/hocs/withContext.js +++ b/src/hocs/withContext.js @@ -63,13 +63,6 @@ function useMemoStateContext(props) { [props.config, props.itemsStateAndParams, props.onChange], ); - const getLayoutItem = React.useCallback( - (widgetId) => { - return props.config.layout.find(({i}) => i === widgetId); - }, - [props.config.layout], - ); - // каллбэк вызывающийся при изменение лэйаута сетки, первым аргументом приходит актуальный конфиг лэйаута, // т.е. если на текущей сетке есть виджеты, с активированной опцией автовысоты, их параметр "h" будет // "подстроенный"; чтобы, для сохранения в сторе "ушли" значения без учёта подстройки (как если бы у этих @@ -101,13 +94,18 @@ function useMemoStateContext(props) { [props.config, onChange], ); + const getLayoutItem = React.useCallback( + (id) => { + return props.config.layout.find(({i}) => i === id); + }, + [props.config.layout], + ); + const onItemRemove = React.useCallback( (id) => { - if (nowrapAdjustedLayouts.current[id]) { - delete nowrapAdjustedLayouts.current[id]; - delete adjustedLayouts.current[id]; - delete originalLayouts.current[id]; - } + delete nowrapAdjustedLayouts.current[id]; + delete adjustedLayouts.current[id]; + delete originalLayouts.current[id]; if (id === TEMPORARY_ITEM_ID) { resetTemporaryLayout(); @@ -456,12 +454,12 @@ function useMemoStateContext(props) { ], ); - const menuItems = props.menuItems || props.registerManager.settings.menu; + const overlayMenuItems = props.overlayMenuItems || props.registerManager.settings.menu; const controlsContextValue = React.useMemo( () => ({ overlayControls: props.overlayControls, context: props.context, - menu: menuItems, + menu: overlayMenuItems, itemsParams: itemsParams, itemsState: itemsState, editItem: props.onItemEdit, @@ -475,7 +473,7 @@ function useMemoStateContext(props) { props.onItemEdit, onItemRemove, props.overlayControls, - menuItems, + overlayMenuItems, getLayoutItem, ], ); diff --git a/src/typings/common.ts b/src/typings/common.ts index 9d2f8fc..f1670bc 100644 --- a/src/typings/common.ts +++ b/src/typings/common.ts @@ -8,7 +8,7 @@ export interface Settings { gridLayout?: ReactGridLayout.ReactGridLayoutProps; theme?: string; isMobile?: boolean; - // @deprecated as it's possibly mutable property use Dashkit menuItems property instead + // @deprecated as it's possibly mutable property use Dashkit overlayMenuItems property instead menu?: Array; } From c2a8e2542f1477e0af6bc30881c313a6c9713a48 Mon Sep 17 00:00:00 2001 From: Serge Pavlyuk Date: Thu, 18 Jul 2024 12:00:09 +0300 Subject: [PATCH 4/5] feat: added hide left controls to example --- .../DashKit/__stories__/DashKitShowcase.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index c4a1750..7418cc4 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -40,6 +40,7 @@ type DashKitDemoState = { customControlsActionData: number; enableActionPanel: boolean; enableAnimations: boolean; + enableOverlayControls: boolean; overlayMenuItems: DashKitProps['overlayMenuItems']; }; @@ -59,6 +60,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { customControlsActionData: 0, enableActionPanel: false, enableAnimations: true, + enableOverlayControls: true, overlayMenuItems: null, }; @@ -131,6 +133,17 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { ? 'Disable animations' : 'Enable animations'} + ); }; + private getDropDownMenuItemConfig(menuName: string, isDefaultMenu?: boolean) { switch (menuName) { case MenuItems.Copy: { @@ -213,28 +214,28 @@ class OverlayControls extends React.Component { } private renderMenu() { const {view, size} = this.props; - const {menu} = this.context; - const withMenu = Array.isArray(menu) && menu.length; + + const dropdown = this.renderDropdownMenu(); + + if (dropdown) { + return dropdown; + } return ( - - {withMenu ? ( - this.renderDropdownMenu() - ) : ( - - )} - + ); } + private isDefaultMenu(menu: Settings['menu']) { return menu?.every((item) => (Object.values(MenuItems) as Array).includes(String(item)), @@ -289,6 +290,10 @@ class OverlayControls extends React.Component { return memo; }, []); + if (items.length === 0) { + return null; + } + return ( { }), ); } + return controls; }; private onCopyItem = () => { @@ -391,19 +397,17 @@ class OverlayControls extends React.Component { return 'brick-brick'; } private getCustomControlsWithWidgets() { - // Добавляем контрол удаления виджета по умолчанию - const deleteControl = { - title: i18n('label_delete'), - icon: CloseIcon, - iconSize: 12, - handler: this.onRemoveItem, - }; const items = this.getItems(); - const customOverlayControls = [...items, deleteControl]; - return customOverlayControls.map( + + const result = items.map( (item: OverlayControlItem, index: number, controlItems: OverlayControlItem[]) => this.renderControlsItem(item, index, controlItems.length), ); + + // Добавляем контрол удаления или меню виджета по умолчанию + result.push(this.renderMenu()); + + return result; } }