From b7ebc7501e33b2aa1ac6801352b68ba03315d797 Mon Sep 17 00:00:00 2001 From: Vicky Bansal <67091118+vicky-primathon@users.noreply.github.com> Date: Fri, 4 Feb 2022 16:29:54 +0530 Subject: [PATCH] fix: Column tile reposition on focus state of Input element inside property pane configuration for Table and Tabs widget (#10046) --- app/client/cypress/locators/Layout.json | 20 +- .../src/components/ads/DraggableList.tsx | 200 +++++++++++++----- .../src/components/ads/DraggableListCard.tsx | 162 ++++++++++++++ .../components/ads/DraggableListComponent.tsx | 36 +++- .../appsmith/DraggableListComponent.tsx | 4 + .../propertyControls/ButtonListControl.tsx | 119 ++++------- .../propertyControls/MenuItemsControl.tsx | 121 ++++------- .../PrimaryColumnsControl.tsx | 162 +++----------- .../propertyControls/TabControl.tsx | 110 ++++------ 9 files changed, 500 insertions(+), 434 deletions(-) create mode 100644 app/client/src/components/ads/DraggableListCard.tsx diff --git a/app/client/cypress/locators/Layout.json b/app/client/cypress/locators/Layout.json index a9799b5de4..1b79137407 100644 --- a/app/client/cypress/locators/Layout.json +++ b/app/client/cypress/locators/Layout.json @@ -1,11 +1,11 @@ { - "tabWidget": ".t--draggable-tabswidget", - "tabInput": ".t--draggable-tabswidget span.t--widget-name", - "tabName": ".t--property-control-tabs input", - "tabDefault": ".t--property-control-defaulttab .CodeMirror-code", - "tabButton": ".t--property-control-tabs button", - "tabDelete": ".t--property-control-tabs .t--delete-tab-btn", - "tabContainer": "div[type='TABS_WIDGET']", - "tabEdit": ".t--property-control-tabs .t--edit-column-btn", - "tabVisibility": ".t--property-control-visible .bp3-control-indicator" -} \ No newline at end of file + "tabWidget": ".t--draggable-tabswidget", + "tabInput": ".t--draggable-tabswidget span.t--widget-name", + "tabName": ".t--property-control-tabs input", + "tabDefault": ".t--property-control-defaulttab .CodeMirror-code", + "tabButton": ".t--property-control-tabs button", + "tabDelete": ".t--property-control-tabs .t--delete-column-btn", + "tabContainer": "div[type='TABS_WIDGET']", + "tabEdit": ".t--property-control-tabs .t--edit-column-btn", + "tabVisibility": ".t--property-control-visible .bp3-control-indicator" +} diff --git a/app/client/src/components/ads/DraggableList.tsx b/app/client/src/components/ads/DraggableList.tsx index a22cb86a61..e9b824cec9 100644 --- a/app/client/src/components/ads/DraggableList.tsx +++ b/app/client/src/components/ads/DraggableList.tsx @@ -60,10 +60,27 @@ const DraggableListWrapper = styled.div` `; function DraggableList(props: any) { - const { itemHeight, ItemRenderer, items, onUpdate } = props; + const { + fixedHeight, + focusedIndex, + itemHeight, + ItemRenderer, + items, + onUpdate, + updateDragging, + } = props; + + const listContainerHeight = + fixedHeight && fixedHeight < items.length * itemHeight + ? fixedHeight + : items.length * itemHeight; const shouldReRender = get(props, "shouldReRender", true); // order of items in the list const order = useRef(items.map((_: any, index: any) => index)); + const displacement = useRef(0); + const dragging = useRef(false); + + const listRef = useRef(null); const onDrop = (originalIndex: number, newIndex: number) => { onUpdate(order.current, originalIndex, newIndex); @@ -82,6 +99,29 @@ function DraggableList(props: any) { } }, [items]); + useEffect(() => { + if (focusedIndex && listRef && listRef.current) { + const container = listRef.current; + + if (focusedIndex * itemHeight < container.scrollTop) { + listRef.current.scrollTo({ + top: (focusedIndex - 1) * itemHeight, + left: 0, + behavior: "smooth", + }); + } else if ( + (focusedIndex + 1) * itemHeight > + listRef.current.scrollTop + listRef.current.clientHeight + ) { + listRef.current.scrollTo({ + top: (focusedIndex + 1) * itemHeight - listRef.current.clientHeight, + left: 0, + behavior: "smooth", + }); + } + } + }, [focusedIndex]); + const [springs, setSprings] = useSprings( items.length, updateSpringStyles(order.current, itemHeight), @@ -90,60 +130,122 @@ function DraggableList(props: any) { const bind: any = useDrag((props: any) => { const originalIndex = props.args[0]; const curIndex = order.current.indexOf(originalIndex); - const curRow = clamp( - Math.round((curIndex * itemHeight + props.movement[1]) / itemHeight), - 0, - items.length - 1, - ); - const newOrder = swap(order.current, curIndex, curRow); - setSprings( - dragIdleSpringStyles(newOrder, { - down: props.down, - originalIndex, - curIndex, - y: props.movement[1], - itemHeight, - }), - ); - if (curRow !== curIndex) { - // Feed springs new style data, they'll animate the view without causing a single render - if (!props.down) { - order.current = newOrder; - setSprings(updateSpringStyles(order.current, itemHeight)); - debounce(onDrop, 400)(curIndex, curRow); + const pointerFromTop = props.xy[1]; + if (listRef && listRef.current) { + const containerCoordinates = listRef?.current.getBoundingClientRect(); + const container = listRef.current; + if (containerCoordinates) { + const containerDistanceFromTop = containerCoordinates.top; + if (props.dragging) { + if (pointerFromTop < containerDistanceFromTop + itemHeight / 2) { + // Scroll inside container till first element in list is completely visible + if (container.scrollTop > 0) { + container.scrollTop -= itemHeight / 10; + } + } else if ( + pointerFromTop >= + containerDistanceFromTop + container.clientHeight - itemHeight / 2 + ) { + // Scroll inside container till container cannnot be scrolled more towards bottom + if ( + container.scrollTop <= + springs.length * itemHeight - + container.clientHeight - + itemHeight / 2 + ) { + container.scrollTop += itemHeight / 10; + } + } + // finding distance of current pointer from the top of the container to find the final position + // currIndex * itemHeight for the initial position + // subtraction formar with latter for displacement + displacement.current = + pointerFromTop - + containerDistanceFromTop + + container.scrollTop - + curIndex * itemHeight - + itemHeight / 2; + + if (!dragging.current && Math.abs(displacement.current) > 10) { + dragging.current = props.dragging; + updateDragging(dragging.current); + } + } else { + if (dragging.current) { + dragging.current = props.dragging; + updateDragging(dragging.current); + } + } + + const curRow = clamp( + Math.round( + (curIndex * itemHeight + displacement.current) / itemHeight, + ), + 0, + items.length - 1, + ); + const newOrder = swap(order.current, curIndex, curRow); + setSprings( + dragIdleSpringStyles(newOrder, { + down: props.down, + originalIndex, + curIndex, + y: Math.abs(displacement.current) > 10 ? displacement.current : 0, + itemHeight, + }), + ); + if (curRow !== curIndex) { + // Feed springs new style data, they'll animate the view without causing a single render + if (!props.down) { + order.current = newOrder; + setSprings(updateSpringStyles(order.current, itemHeight)); + debounce(onDrop, 400)(curIndex, curRow); + } + } } } }); return ( - { - // set events to null to stop other parent draggable elements execution(ex: Property pane) - document.onmouseup = null; - document.onmousemove = null; +
- {springs.map(({ scale, y, zIndex }, i) => ( - `translate3d(0,${y}px,0) scale(${s})`, - ), - }} - > -
- -
-
- ))} - + { + // set events to null to stop other parent draggable elements execution(ex: Property pane) + document.onmouseup = null; + document.onmousemove = null; + }} + style={{ + height: "100%", + }} + > + {springs.map(({ scale, y, zIndex }, i) => ( + `translate3d(0,${y}px,0) scale(${s})`, + ), + }} + > +
+ +
+
+ ))} +
+
); } DraggableList.displayName = "DraggableList"; diff --git a/app/client/src/components/ads/DraggableListCard.tsx b/app/client/src/components/ads/DraggableListCard.tsx new file mode 100644 index 0000000000..2e4b43664b --- /dev/null +++ b/app/client/src/components/ads/DraggableListCard.tsx @@ -0,0 +1,162 @@ +import React, { useCallback, useState, useRef, useEffect } from "react"; +import styled from "styled-components"; + +import _ from "lodash"; +import { + StyledDragIcon, + StyledOptionControlInputGroup, + StyledEditIcon, + StyledDeleteIcon, + StyledVisibleIcon, + StyledHiddenIcon, +} from "components/propertyControls/StyledControls"; +import { Colors } from "constants/Colors"; + +const ItemWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + &.has-duplicate-label > div:nth-child(2) { + border: 1px solid ${Colors.DANGER_SOLID}; + } +`; + +type RenderComponentProps = { + focusedIndex: number | null | undefined; + index: number; + item: { + label: string; + isDerived?: boolean; + isVisible?: boolean; + isDuplicateLabel?: boolean; + }; + isDelete?: boolean; + isDragging: boolean; + placeholder: string; + updateFocus?: (index: number, isFocused: boolean) => void; + updateOption: (index: number, value: string) => void; + onEdit?: (index: number) => void; + deleteOption: (index: number) => void; + toggleVisibility?: (index: number) => void; +}; + +export function DraggableListCard(props: RenderComponentProps) { + const [value, setValue] = useState(props.item.label); + const [isEditing, setEditing] = useState(false); + + const { + deleteOption, + focusedIndex, + index, + isDelete, + isDragging, + item, + onEdit, + placeholder, + toggleVisibility, + updateFocus, + updateOption, + } = props; + const [visibility, setVisibility] = useState(item.isVisible); + const ref = useRef(null); + const debouncedUpdate = _.debounce(updateOption, 1000); + + useEffect(() => { + if (!isEditing && item && item.label) setValue(item.label); + }, [item?.label, isEditing]); + + useEffect(() => { + if (focusedIndex !== null && focusedIndex === index && !isDragging) { + if (ref && ref.current) { + ref?.current.focus(); + } + } else if (isDragging && focusedIndex === index) { + if (ref && ref.current) { + ref?.current.blur(); + } + } + }, [focusedIndex, isDragging]); + + const onChange = useCallback( + (index: number, value: string) => { + setValue(value); + debouncedUpdate(index, value); + }, + [updateOption], + ); + + const onFocus = () => { + setEditing(false); + if (updateFocus) { + updateFocus(index, true); + } + }; + + const onBlur = () => { + if (!isDragging) { + setEditing(false); + if (updateFocus) { + updateFocus(index, false); + } + } + }; + + return ( + + + { + onChange(index, value); + }} + onFocus={onFocus} + placeholder={placeholder} + ref={ref} + value={value} + width="100%" + /> + { + onEdit && onEdit(index); + }} + width={20} + /> + {!!item.isDerived || isDelete ? ( + { + deleteOption && deleteOption(index); + }} + width={20} + /> + ) : visibility ? ( + { + setVisibility(!visibility); + toggleVisibility && toggleVisibility(index); + }} + width={20} + /> + ) : ( + { + setVisibility(!visibility); + toggleVisibility && toggleVisibility(index); + }} + width={20} + /> + )} + + ); +} diff --git a/app/client/src/components/ads/DraggableListComponent.tsx b/app/client/src/components/ads/DraggableListComponent.tsx index c4e778ac6b..256bf269c7 100644 --- a/app/client/src/components/ads/DraggableListComponent.tsx +++ b/app/client/src/components/ads/DraggableListComponent.tsx @@ -3,11 +3,13 @@ import React from "react"; import DraggableList from "./DraggableList"; type RenderComponentProps = { + focusedIndex: number | null | undefined; index: number; item: { label: string; isDerived?: boolean; }; + isDragging: boolean; deleteOption: (index: number) => void; updateOption: (index: number, value: string) => void; toggleVisibility?: (index: number) => void; @@ -16,6 +18,8 @@ type RenderComponentProps = { }; interface DroppableComponentProps { + fixedHeight?: number | boolean; + focusedIndex?: number | null | undefined; items: Array>; itemHeight: number; renderComponent: (props: RenderComponentProps) => JSX.Element; @@ -34,11 +38,18 @@ export class DroppableComponent extends React.Component< super(props); } - shouldComponentUpdate(prevProps: DroppableComponentProps) { + public readonly state = { + isDragging: false, + }; + + shouldComponentUpdate(prevProps: DroppableComponentProps, prevState: any) { const presentOrder = this.props.items.map(this.getVisibleObject); const previousOrder = prevProps.items.map(this.getVisibleObject); - - return !isEqual(presentOrder, previousOrder); + return ( + !isEqual(presentOrder, previousOrder) || + this.props.focusedIndex !== prevProps.focusedIndex || + prevState.isDragging !== this.state.isDragging + ); } getVisibleObject(item: Record) { @@ -52,14 +63,26 @@ export class DroppableComponent extends React.Component< }; } - onUpdate = (itemsOrder: number[]) => { + onUpdate = ( + itemsOrder: number[], + originalIndex: number, + newIndex: number, + ) => { const newOrderedItems = itemsOrder.map((each) => this.props.items[each]); this.props.updateItems(newOrderedItems); + if (this.props.updateFocus && originalIndex !== newIndex) { + this.props.updateFocus(newIndex, true); + } + }; + + updateDragging = (isDragging: boolean) => { + this.setState({ isDragging }); }; renderItem = ({ index, item }: any) => { const { deleteOption, + focusedIndex, onEdit, renderComponent, toggleVisibility, @@ -73,8 +96,10 @@ export class DroppableComponent extends React.Component< updateOption, toggleVisibility, onEdit, + focusedIndex, item, index, + isDragging: this.state.isDragging, }); }; @@ -82,10 +107,13 @@ export class DroppableComponent extends React.Component< return ( ); } diff --git a/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx b/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx index 967ef2a981..1c551ffcb7 100644 --- a/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx @@ -17,6 +17,7 @@ type RenderComponentProps = { isDerived?: boolean; }; deleteOption: (index: number) => void; + updateCurrentFocusedInput: (index: number | null) => void; updateOption: (index: number, value: string) => void; toggleVisibility?: (index: number) => void; onEdit?: (index: number) => void; @@ -26,6 +27,7 @@ interface DroppableComponentProps { items: Array>; renderComponent: (props: RenderComponentProps) => JSX.Element; deleteOption: (index: number) => void; + updateCurrentFocusedInput: (index: number | null) => void; updateOption: (index: number, value: string) => void; toggleVisibility?: (index: number) => void; updateItems: (items: Array>) => void; @@ -85,6 +87,7 @@ export class DroppableComponent extends React.Component< onEdit, renderComponent, toggleVisibility, + updateCurrentFocusedInput, updateOption, } = this.props; return ( @@ -116,6 +119,7 @@ export class DroppableComponent extends React.Component< > {renderComponent({ deleteOption, + updateCurrentFocusedInput, updateOption, toggleVisibility, onEdit, diff --git a/app/client/src/components/propertyControls/ButtonListControl.tsx b/app/client/src/components/propertyControls/ButtonListControl.tsx index fc3ee9f239..a8c7e8ed85 100644 --- a/app/client/src/components/propertyControls/ButtonListControl.tsx +++ b/app/client/src/components/propertyControls/ButtonListControl.tsx @@ -1,20 +1,15 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React from "react"; import BaseControl, { ControlProps } from "./BaseControl"; -import { - StyledPropertyPaneButton, - StyledDragIcon, - StyledDeleteIcon, - StyledEditIcon, - StyledOptionControlInputGroup, -} from "./StyledControls"; +import { StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; import { generateReactKey } from "utils/generators"; import { DroppableComponent } from "components/ads/DraggableListComponent"; import { getNextEntityName } from "utils/AppsmithUtils"; -import _, { debounce } from "lodash"; +import _ from "lodash"; import { Category, Size } from "components/ads/Button"; import { Colors } from "constants/Colors"; import { ButtonPlacementTypes } from "components/constants"; +import { DraggableListCard } from "components/ads/DraggableListCard"; const StyledPropertyPaneButtonWrapper = styled.div` display: flex; @@ -23,12 +18,6 @@ const StyledPropertyPaneButtonWrapper = styled.div` margin-top: 10px; `; -const ButtonWrapper = styled.div` - display: flex; - justify-content: flex-start; - align-items: center; -`; - const ButtonListWrapper = styled.div` width: 100%; display: flex; @@ -40,77 +29,28 @@ const AddNewButton = styled(StyledPropertyPaneButton)` flex-grow: 1; `; -type RenderComponentProps = { - index: number; - item: { - label: string; - isVisible?: boolean; - }; - deleteOption: (index: number) => void; - updateOption: (index: number, value: string) => void; - toggleVisibility?: (index: number) => void; - onEdit?: (props: any) => void; +type State = { + focusedIndex: number | null; }; -function GroupButtonComponent(props: RenderComponentProps) { - const { deleteOption, index, item, updateOption } = props; +class ButtonListControl extends BaseControl { + constructor(props: ControlProps) { + super(props); - const [value, setValue] = useState(item.label); - const [isEditing, setEditing] = useState(false); + this.state = { + focusedIndex: null, + }; + } - useEffect(() => { - if (!isEditing && item && item.label) setValue(item.label); - }, [item?.label, isEditing]); - - const debouncedUpdate = debounce(updateOption, 1000); - const onChange = useCallback( - (index: number, value: string) => { - setValue(value); - debouncedUpdate(index, value); - }, - [updateOption], - ); - const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [ - index, - ]); - - const onFocus = () => setEditing(true); - const onBlur = () => setEditing(false); - - return ( - - - { - onChange(index, value); - }} - onFocus={onFocus} - placeholder="Button label" - trimValue={false} - value={value} - /> - { - deleteOption(index); - }} - width={20} - /> - - - ); -} - -class ButtonListControl extends BaseControl { + componentDidUpdate(prevProps: ControlProps): void { + //on adding a new column last column should get focused + if ( + Object.keys(prevProps.propertyValue).length + 1 === + Object.keys(this.props.propertyValue).length + ) { + this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true); + } + } updateItems = (items: Array>) => { const menuItems = items.reduce((obj: any, each: any, index: number) => { obj[each.id] = { @@ -146,11 +86,20 @@ class ButtonListControl extends BaseControl { + DraggableListCard({ + ...props, + isDelete: true, + placeholder: "Button label", + }) + } toggleVisibility={this.toggleVisibility} + updateFocus={this.updateFocus} updateItems={this.updateItems} updateOption={this.updateOption} /> @@ -246,6 +195,10 @@ class ButtonListControl extends BaseControl { this.updateProperty(this.props.propertyName, groupButtons); }; + updateFocus = (index: number, isFocused: boolean) => { + this.setState({ focusedIndex: isFocused ? index : null }); + }; + static getControlType() { return "GROUP_BUTTONS"; } diff --git a/app/client/src/components/propertyControls/MenuItemsControl.tsx b/app/client/src/components/propertyControls/MenuItemsControl.tsx index 4cca6181ae..abd8c86a20 100644 --- a/app/client/src/components/propertyControls/MenuItemsControl.tsx +++ b/app/client/src/components/propertyControls/MenuItemsControl.tsx @@ -1,18 +1,13 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React from "react"; import BaseControl, { ControlProps } from "./BaseControl"; -import { - StyledPropertyPaneButton, - StyledDragIcon, - StyledDeleteIcon, - StyledEditIcon, - StyledOptionControlInputGroup, -} from "./StyledControls"; +import { StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; import { generateReactKey } from "utils/generators"; import { DroppableComponent } from "components/ads/DraggableListComponent"; import { getNextEntityName } from "utils/AppsmithUtils"; -import _, { debounce, orderBy } from "lodash"; +import _, { orderBy } from "lodash"; import { Category, Size } from "components/ads/Button"; +import { DraggableListCard } from "components/ads/DraggableListCard"; const StyledPropertyPaneButtonWrapper = styled.div` display: flex; @@ -21,12 +16,6 @@ const StyledPropertyPaneButtonWrapper = styled.div` margin-top: 10px; `; -const ItemWrapper = styled.div` - display: flex; - justify-content: flex-start; - align-items: center; -`; - const MenuItemsWrapper = styled.div` width: 100%; display: flex; @@ -38,77 +27,30 @@ const AddMenuItemButton = styled(StyledPropertyPaneButton)` flex-grow: 1; `; -type RenderComponentProps = { - index: number; - item: { - label: string; - isVisible?: boolean; - }; - deleteOption: (index: number) => void; - updateOption: (index: number, value: string) => void; - toggleVisibility?: (index: number) => void; - onEdit?: (props: any) => void; +type State = { + focusedIndex: number | null; }; -function MenuItemComponent(props: RenderComponentProps) { - const { deleteOption, index, item, updateOption } = props; +class MenuItemsControl extends BaseControl { + constructor(props: ControlProps) { + super(props); - const [value, setValue] = useState(item.label); - const [isEditing, setEditing] = useState(false); + this.state = { + focusedIndex: null, + }; + } - useEffect(() => { - if (!isEditing && item && item.label) setValue(item.label); - }, [item?.label, isEditing]); - - const debouncedUpdate = debounce(updateOption, 1000); - const onChange = useCallback( - (index: number, value: string) => { - setValue(value); - debouncedUpdate(index, value); - }, - [updateOption], - ); - const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [ - index, - ]); - - const onFocus = () => setEditing(true); - const onBlur = () => setEditing(false); - - return ( - - - { - onChange(index, value); - }} - onFocus={onFocus} - placeholder="Menu item label" - trimValue={false} - value={value} - /> - { - deleteOption(index); - }} - width={20} - /> - - - ); -} - -class MenuItemsControl extends BaseControl { + componentDidUpdate(prevProps: ControlProps): void { + //on adding a new column last column should get focused + if ( + prevProps.propertyValue && + this.props.propertyValue && + Object.keys(prevProps.propertyValue).length + 1 === + Object.keys(this.props.propertyValue).length + ) { + this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true); + } + } updateItems = (items: Array>) => { const menuItems = items.reduce((obj: any, each: any, index: number) => { obj[each.id] = { @@ -146,11 +88,20 @@ class MenuItemsControl extends BaseControl { + DraggableListCard({ + ...props, + isDelete: true, + placeholder: "Menu item label", + }) + } toggleVisibility={this.toggleVisibility} + updateFocus={this.updateFocus} updateItems={this.updateItems} updateOption={this.updateOption} /> @@ -243,6 +194,10 @@ class MenuItemsControl extends BaseControl { this.updateProperty(this.props.propertyName, menuItems); }; + updateFocus = (index: number, isFocused: boolean) => { + this.setState({ focusedIndex: isFocused ? index : null }); + }; + static getControlType() { return "MENU_ITEMS"; } diff --git a/app/client/src/components/propertyControls/PrimaryColumnsControl.tsx b/app/client/src/components/propertyControls/PrimaryColumnsControl.tsx index 7a8de21399..8e18af6669 100644 --- a/app/client/src/components/propertyControls/PrimaryColumnsControl.tsx +++ b/app/client/src/components/propertyControls/PrimaryColumnsControl.tsx @@ -1,19 +1,11 @@ -import React, { useCallback, useEffect, useState, Component } from "react"; +import React, { Component } from "react"; import { AppState } from "reducers"; import { connect } from "react-redux"; import { Placement } from "popper.js"; import * as Sentry from "@sentry/react"; import _ from "lodash"; import BaseControl, { ControlProps } from "./BaseControl"; -import { - StyledDragIcon, - StyledEditIcon, - StyledDeleteIcon, - StyledVisibleIcon, - StyledHiddenIcon, - StyledPropertyPaneButton, - StyledOptionControlInputGroup, -} from "./StyledControls"; +import { StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; import { Indices } from "constants/Layers"; import { DroppableComponent } from "components/ads/DraggableListComponent"; @@ -37,17 +29,7 @@ import { PropertyEvaluationErrorType, } from "utils/DynamicBindingUtils"; import { getNextEntityName } from "utils/AppsmithUtils"; -import { Colors } from "constants/Colors"; -import { noop } from "utils/AppsmithUtils"; - -const ItemWrapper = styled.div` - display: flex; - justify-content: flex-start; - align-items: center; - &.has-duplicate-label > div:nth-child(2) { - border: 1px solid ${Colors.DANGER_SOLID}; - } -`; +import { DraggableListCard } from "components/ads/DraggableListCard"; const TabsWrapper = styled.div` width: 100%; @@ -83,21 +65,6 @@ type EvaluatedValuePopupWrapperProps = ReduxStateProps & { children: JSX.Element; }; -type RenderComponentProps = { - index: number; - item: { - label: string; - isDerived?: boolean; - isVisible?: boolean; - isDuplicateLabel?: boolean; - }; - updateFocus?: (index: number, isFocused: boolean) => void; - updateOption: (index: number, value: string) => void; - onEdit?: (index: number) => void; - deleteOption: (index: number) => void; - toggleVisibility?: (index: number) => void; -}; - const getOriginalColumn = ( columns: Record, index: number, @@ -110,107 +77,6 @@ const getOriginalColumn = ( return column; }; -function ColumnControlComponent(props: RenderComponentProps) { - const [value, setValue] = useState(props.item.label); - const [isEditing, setEditing] = useState(false); - - useEffect(() => { - if (!isEditing && props.item && props.item.label) - setValue(props.item.label); - }, [props.item?.label, isEditing]); - - const { - deleteOption, - index, - item, - onEdit, - toggleVisibility, - updateFocus, - updateOption, - } = props; - - const [visibility, setVisibility] = useState(item.isVisible); - useEffect(() => { - setVisibility(item.isVisible); - }, [item.isVisible]); - const debouncedUpdate = _.debounce(updateOption, 1000); - const debouncedFocus = updateFocus ? _.debounce(updateFocus, 400) : noop; - const onChange = useCallback( - (index: number, value: string) => { - setValue(value); - debouncedUpdate(index, value); - }, - [updateOption], - ); - - const onFocus = () => { - setEditing(false); - debouncedFocus(index, true); - }; - const onBlur = () => { - setEditing(false); - debouncedFocus(index, false); - }; - - return ( - - - { - onChange(index, value); - }} - onFocus={onFocus} - placeholder="Column Title" - trimValue={false} - value={value} - width="100%" - /> - { - onEdit && onEdit(index); - }} - width={20} - /> - {!!item.isDerived ? ( - { - deleteOption && deleteOption(index); - }} - width={20} - /> - ) : visibility ? ( - { - setVisibility(!visibility); - toggleVisibility && toggleVisibility(index); - }} - width={20} - /> - ) : ( - { - setVisibility(!visibility); - toggleVisibility && toggleVisibility(index); - }} - width={20} - /> - )} - - ); -} - type State = { focusedIndex: number | null; duplicateColumnIds: string[]; @@ -241,6 +107,16 @@ class PrimaryColumnsControl extends BaseControl { }; } + componentDidUpdate(prevProps: ControlProps): void { + //on adding a new column last column should get focused + if ( + Object.keys(prevProps.propertyValue).length + 1 === + Object.keys(this.props.propertyValue).length + ) { + this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true); + } + } + render() { // Get columns from widget properties const columns: Record = @@ -291,10 +167,18 @@ class PrimaryColumnsControl extends BaseControl { + DraggableListCard({ + ...props, + isDelete: false, + placeholder: "Column Title", + }) + } toggleVisibility={this.toggleVisibility} updateFocus={this.updateFocus} updateItems={this.updateItems} @@ -445,6 +329,8 @@ class PrimaryColumnsControl extends BaseControl { this.setState({ focusedIndex: isFocused ? index : null }); }; + // updateCurrentFocusedInput = (index: number | null) => {}; + static getControlType() { return "PRIMARY_COLUMNS"; } diff --git a/app/client/src/components/propertyControls/TabControl.tsx b/app/client/src/components/propertyControls/TabControl.tsx index 6f414088a9..3bfa7037e3 100644 --- a/app/client/src/components/propertyControls/TabControl.tsx +++ b/app/client/src/components/propertyControls/TabControl.tsx @@ -1,21 +1,16 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React from "react"; import BaseControl, { ControlProps } from "./BaseControl"; -import { - StyledPropertyPaneButton, - StyledDragIcon, - StyledDeleteIcon, - StyledEditIcon, - StyledOptionControlInputGroup, -} from "./StyledControls"; +import { StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; import { generateReactKey } from "utils/generators"; import { DroppableComponent } from "components/ads/DraggableListComponent"; import { getNextEntityName, noop } from "utils/AppsmithUtils"; -import _, { debounce, orderBy } from "lodash"; +import _, { orderBy } from "lodash"; import * as Sentry from "@sentry/react"; import { Category, Size } from "components/ads/Button"; import { useDispatch } from "react-redux"; import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { DraggableListCard } from "components/ads/DraggableListCard"; const StyledPropertyPaneButtonWrapper = styled.div` display: flex; @@ -24,12 +19,6 @@ const StyledPropertyPaneButtonWrapper = styled.div` margin-top: 10px; `; -const ItemWrapper = styled.div` - display: flex; - justify-content: flex-start; - align-items: center; -`; - const TabsWrapper = styled.div` width: 100%; display: flex; @@ -37,12 +26,15 @@ const TabsWrapper = styled.div` `; type RenderComponentProps = { + focusedIndex: number | null | undefined; index: number; + isDragging: boolean; item: { label: string; isVisible?: boolean; }; deleteOption: (index: number) => void; + updateFocus?: (index: number, isFocused: boolean) => void; updateOption: (index: number, value: string) => void; toggleVisibility?: (index: number) => void; onEdit?: (props: any) => void; @@ -74,7 +66,7 @@ function AddTabButtonComponent({ widgetId }: any) { } function TabControlComponent(props: RenderComponentProps) { - const { index, item, updateOption } = props; + const { index, item } = props; const dispatch = useDispatch(); const deleteOption = () => { dispatch({ @@ -83,65 +75,42 @@ function TabControlComponent(props: RenderComponentProps) { }); }; - const [value, setValue] = useState(item.label); - const [isEditing, setEditing] = useState(false); - - useEffect(() => { - if (!isEditing && item && item.label) setValue(item.label); - }, [item?.label, isEditing]); - - const debouncedUpdate = debounce(updateOption, 1000); - const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [ - index, - ]); - - const onChange = useCallback( - (index: number, value: string) => { - setValue(value); - debouncedUpdate(index, value); - }, - [updateOption], - ); - - const onFocus = () => setEditing(true); - const onBlur = () => setEditing(false); - return ( - - - { - onChange(index, value); - }} - onFocus={onFocus} - placeholder="Tab Title" - trimValue={false} - value={value} - /> - - - + ); } -class TabControl extends BaseControl { +type State = { + focusedIndex: number | null; +}; + +class TabControl extends BaseControl { + constructor(props: ControlProps) { + super(props); + + this.state = { + focusedIndex: null, + }; + } componentDidMount() { this.migrateTabData(this.props.propertyValue); } + componentDidUpdate(prevProps: ControlProps): void { + //on adding a new column last column should get focused + if ( + Object.keys(prevProps.propertyValue).length + 1 === + Object.keys(this.props.propertyValue).length + ) { + this.updateFocus(Object.keys(this.props.propertyValue).length - 1, true); + } + } + migrateTabData( tabData: Array<{ id: string; @@ -204,11 +173,14 @@ class TabControl extends BaseControl { @@ -269,6 +241,10 @@ class TabControl extends BaseControl { this.updateProperty(this.props.propertyName, tabs); }; + updateFocus = (index: number, isFocused: boolean) => { + this.setState({ focusedIndex: isFocused ? index : null }); + }; + static getControlType() { return "TABS_INPUT"; }