diff --git a/app/client/package.json b/app/client/package.json index 59b6167c64..095aaed2be 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -13,9 +13,7 @@ "@blueprintjs/select": "^3.10.0", "@blueprintjs/table": "^3.7.1", "@sentry/browser": "^5.6.3", - "@types/axios": "^0.14.0", "@types/fontfaceobserver": "^0.0.6", - "@types/jest": "^24.0.18", "@types/lodash": "^4.14.120", "@types/moment-timezone": "^0.5.10", "@types/nanoid": "^2.0.0", @@ -71,7 +69,7 @@ "eject": "react-scripts eject", "flow": "flow" }, - "resolutions": { + "resolution": { "jest": "24.8.0" }, "eslintConfig": { diff --git a/app/client/src/components/canvas/Button.tsx b/app/client/src/components/canvas/Button.tsx index 2e7b8f96b1..d2edd1da77 100644 --- a/app/client/src/components/canvas/Button.tsx +++ b/app/client/src/components/canvas/Button.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Button, IButtonProps, MaybeElement } from "@blueprintjs/core"; +import { AnchorButton, IButtonProps, MaybeElement } from "@blueprintjs/core"; import styled, { css } from "styled-components"; import { Container } from "../../editorComponents/ContainerComponent"; import { TextComponentProps } from "./TextViewComponent"; @@ -15,7 +15,7 @@ const ButtonColorStyles = css` }}; `; -const ButtonWrapper = styled(Button)` +const ButtonWrapper = styled(AnchorButton)` && { ${ButtonColorStyles}; width: 100%; @@ -64,9 +64,10 @@ const ButtonWrapper = styled(Button)` } } `; +export type ButtonStyleName = "primary" | "secondary" | "error"; type ButtonStyleProps = { - styleName?: "primary" | "secondary" | "error"; + styleName?: ButtonStyleName; filled?: boolean; }; @@ -77,6 +78,7 @@ export const BaseButton = (props: IButtonProps & ButtonStyleProps) => { BaseButton.defaultProps = { styleName: "secondary", + disabled: false, text: "Button Text", minimal: true, }; @@ -84,13 +86,20 @@ BaseButton.defaultProps = { interface ButtonContainerProps extends TextComponentProps { icon?: MaybeElement; onClick?: (event: React.MouseEvent) => void; + disabled?: boolean; } // To be used with the canvas -const ButtonContainer = (props: ButtonContainerProps) => { +const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => { return ( - + ); }; diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index 5abf6d93b0..44e7c72838 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -8,6 +8,7 @@ export const Colors: Record = { CONCRETE: "#F3F3F3", MYSTIC: "#E1E8ED", AQUA_HAZE: "#EEF2F5", + GRAY_CHATEAU: "#A2A6A8", BLACK: "#000000", BLACK_PEARL: "#040627", @@ -27,6 +28,7 @@ export const Colors: Record = { PURPLE: "#6871EF", OXFORD_BLUE: "#2E3D49", FRENCH_PASS: "#BBE8FE", + CADET_BLUE: "#A3B3BF", }; export type Color = (typeof Colors)[keyof typeof Colors]; diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 0a6c9895ac..ca08501d5d 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -81,7 +81,10 @@ export const theme: Theme = { textAnchor: Colors.PURPLE, border: Colors.GEYSER, paneCard: Colors.SHARK, + paneInputBG: Colors.SHARK, paneBG: Colors.OUTER_SPACE, + paneText: Colors.GRAY_CHATEAU, + paneSectionLabel: Colors.CADET_BLUE, navBG: Colors.DEEP_SPACE, grid: Colors.GEYSER, containerBorder: Colors.FRENCH_PASS, @@ -101,6 +104,11 @@ export const theme: Theme = { style: "solid", color: Colors.FRENCH_PASS, }, + { + thickness: "1px", + style: "solid", + color: Colors.GEYSER_LIGHT, + }, ], sidebarWidth: "300px", headerHeight: "50px", diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 2ff70577eb..dd211d45ab 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -70,6 +70,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { FETCH_CONFIGS_ERROR: "FETCH_CONFIGS_ERROR", PROPERTY_PANE_ERROR: "PROPERTY_PANE_ERROR", FETCH_ACTIONS_ERROR: "FETCH_ACTIONS_ERROR", + UPDATE_WIDGET_PROPERTY_ERROR: "UPDATE_WIDGET_PROPERTY_ERROR", FETCH_RESOURCES_ERROR: "FETCH_RESOURCES_ERROR", CREATE_RESOURCE_ERROR: "CREATE_RESOURCE_ERROR", }; diff --git a/app/client/src/editorComponents/BaseComponent.tsx b/app/client/src/editorComponents/BaseComponent.tsx index 886a8be1fe..dd37d61985 100644 --- a/app/client/src/editorComponents/BaseComponent.tsx +++ b/app/client/src/editorComponents/BaseComponent.tsx @@ -18,6 +18,7 @@ export interface BaseStyle { heightUnit?: CSSUnit; widthUnit?: CSSUnit; backgroundColor?: Color; + border?: string; } export interface ComponentProps { diff --git a/app/client/src/editorComponents/ContainerComponent.tsx b/app/client/src/editorComponents/ContainerComponent.tsx index 9047795277..b7b91481aa 100644 --- a/app/client/src/editorComponents/ContainerComponent.tsx +++ b/app/client/src/editorComponents/ContainerComponent.tsx @@ -1,9 +1,21 @@ import { ComponentProps } from "./BaseComponent"; import { ContainerOrientation } from "../constants/WidgetConstants"; import styled from "../constants/DefaultTheme"; -import React, { createContext, Context, useRef } from "react"; +import React, { + createContext, + Context, + useRef, + useContext, + forwardRef, +} from "react"; +import { FocusContext } from "../pages/Editor/Canvas"; +import { getBorderCSSShorthand } from "../constants/DefaultTheme"; -export const Container = styled("div")` +type StyledContainerProps = ContainerProps & { + focus?: boolean; +}; + +export const StyledContainer = styled("div")` display: flex; flex-direction: ${props => { return props.orientation === "HORIZONTAL" ? "row" : "column"; @@ -19,26 +31,49 @@ export const Container = styled("div")` width: 100%; padding: ${props => props.theme.spaces[1]}px; &:after { - content: "${props => props.widgetName}"; + content: "${props => (props.focus ? props.widgetName : "")}"; position: absolute; top: -${props => props.theme.spaces[8]}px; font-size: ${props => props.theme.fontSizes[2]}px; color: ${props => props.theme.colors.containerBorder}; text-align: left; width: 100%; - } -`; + }`; + +/* eslint-disable react/display-name */ +/* eslint-disable react/prop-types */ +type Ref = HTMLDivElement; +export const Container = forwardRef((props, ref) => { + const { isFocused } = useContext(FocusContext); + const focus = isFocused === props.widgetId; + + return ; +}); export const ParentBoundsContext: Context<{ boundingParent?: React.RefObject; }> = createContext({}); +type ContainerComponentWrapperProps = ContainerStyleProps & { + isRoot?: boolean; +}; +const ContainerComponentWrapper = styled.div` + /* TODO(abhinav)(Issue: #107): this will changed based on the ContainerStyleProps */ + border: ${props => + !props.isRoot && getBorderCSSShorthand(props.theme.borders[2])}; + box-shadow: ${props => + !props.isRoot ? "0px 2px 4px rgba(67, 70, 74, 0.14)" : "none"}; + height: 100%; + width: 100%; +`; const ContainerComponent = (props: ContainerProps) => { const container = useRef(null); return ( - {props.children} + + {props.children} + ); @@ -50,4 +85,8 @@ export interface ContainerProps extends ComponentProps { isRoot?: boolean; } +type ContainerStyleProps = { + styleName?: "border" | "card" | "rounded-border"; +}; + export default ContainerComponent; diff --git a/app/client/src/editorComponents/DraggableComponent.tsx b/app/client/src/editorComponents/DraggableComponent.tsx index bc52c84ec7..639cc0b083 100644 --- a/app/client/src/editorComponents/DraggableComponent.tsx +++ b/app/client/src/editorComponents/DraggableComponent.tsx @@ -15,6 +15,7 @@ import { WidgetFunctionsContext } from "../pages/Editor/WidgetsEditor"; import { ControlIcons } from "../icons/ControlIcons"; import { theme } from "../constants/DefaultTheme"; import { ResizingContext } from "./DropTargetComponent"; +import { Tooltip } from "@blueprintjs/core"; // FontSizes array in DefaultTheme.tsx // Change this to toggle the size of delete and move handles. @@ -124,18 +125,18 @@ const DraggableComponent = (props: DraggableComponentProps) => { collect: (monitor: DragSourceMonitor) => ({ isDragging: monitor.isDragging(), }), - end: (widget, monitor) => { - if (monitor.didDrop()) { - if (isFocused === props.widgetId && showPropertyPane && currentNode) { - showPropertyPane(props.widgetId, currentNode, true); - } - } - }, begin: () => { if (isFocused === props.widgetId && showPropertyPane && currentNode) { showPropertyPane(props.widgetId, undefined); } }, + end: (widget, monitor) => { + if (monitor.didDrop()) { + if (isFocused === props.widgetId && showPropertyPane && currentNode) { + showPropertyPane(props.widgetId, currentNode); + } + } + }, canDrag: () => { return !isResizing && !!isFocused && isFocused === props.widgetId; }, @@ -175,13 +176,19 @@ const DraggableComponent = (props: DraggableComponentProps) => { {props.children} - {moveControlIcon} + + {moveControlIcon} + - {deleteControlIcon} + + {deleteControlIcon} + - {editControlIcon} + + {editControlIcon} + diff --git a/app/client/src/editorComponents/DropTargetComponent.tsx b/app/client/src/editorComponents/DropTargetComponent.tsx index 668b62e460..a8b11e6f78 100644 --- a/app/client/src/editorComponents/DropTargetComponent.tsx +++ b/app/client/src/editorComponents/DropTargetComponent.tsx @@ -26,7 +26,7 @@ type DropTargetBounds = { }; export const ResizingContext: Context<{ - isResizing?: boolean; + isResizing?: boolean | string; setIsResizing?: Function; }> = createContext({}); @@ -36,8 +36,7 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => { const [isResizing, setIsResizing] = useState(false); const { updateWidget } = useContext(WidgetFunctionsContext); const occupiedSpaces = useContext(OccupiedSpaceContext); - const { setFocus } = useContext(FocusContext); - + const { setFocus, showPropertyPane } = useContext(FocusContext); // Make this component a drop target const [{ isOver, isExactlyOver }, drop] = useDrop({ accept: Object.values(WidgetFactory.getWidgetTypes()), @@ -99,6 +98,7 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => { const handleFocus = () => { if (props.isRoot) { setFocus && setFocus(props.widgetId); + showPropertyPane && showPropertyPane(); } }; diff --git a/app/client/src/editorComponents/ResizableComponent.tsx b/app/client/src/editorComponents/ResizableComponent.tsx index c167a83bd6..53f93f18a4 100644 --- a/app/client/src/editorComponents/ResizableComponent.tsx +++ b/app/client/src/editorComponents/ResizableComponent.tsx @@ -72,7 +72,8 @@ const ResizableContainer = styled(Rnd)` width: ${props => props.theme.spaces[2]}px; height: ${props => props.theme.spaces[2]}px; border-radius: ${props => props.theme.radii[5]}%; - background: ${props => props.theme.colors.containerBorder}; + background: ${props => + props.isfocused && props.theme.colors.containerBorder}; } &:after { right: -${props => props.theme.spaces[1]}px; @@ -88,11 +89,11 @@ const ResizableContainer = styled(Rnd)` `; export const ResizableComponent = (props: ResizableComponentProps) => { - const { isDragging } = useContext(DraggableComponentContext); + const { isDragging, widgetNode } = useContext(DraggableComponentContext); const { setIsResizing } = useContext(ResizingContext); const { boundingParent } = useContext(ParentBoundsContext); const { updateWidget } = useContext(WidgetFunctionsContext); - const { isFocused, setFocus, showPropertyPane } = useContext(FocusContext); + const { showPropertyPane, isFocused, setFocus } = useContext(FocusContext); const occupiedSpaces = useContext(OccupiedSpaceContext); const [isColliding, setIsColliding] = useState(false); @@ -145,6 +146,7 @@ export const ResizableComponent = (props: ResizableComponentProps) => { ) => { setIsResizing && setIsResizing(false); setFocus && setFocus(props.widgetId); + showPropertyPane && showPropertyPane(props.widgetId, widgetNode); const leftColumn = props.leftColumn + position.x / props.parentColumnSpace; const topRow = props.topRow + position.y / props.parentRowSpace; @@ -174,6 +176,7 @@ export const ResizableComponent = (props: ResizableComponentProps) => { const canResize = !isDragging && isFocused === props.widgetId; return ( { border: isFocused === props.widgetId ? getBorderCSSShorthand(theme.borders[1]) - : getBorderCSSShorthand(theme.borders[0]), + : "none", + boxSizing: "content-box", }} onResizeStop={updateSize} onResize={checkForCollision} onResizeStart={() => { setIsResizing && setIsResizing(true); - showPropertyPane && showPropertyPane(props.widgetId, undefined); + showPropertyPane && showPropertyPane(props.widgetId); }} resizeGrid={[props.parentColumnSpace, props.parentRowSpace]} bounds={bounds} diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index f63cf16dd6..c7d3539d0d 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -8,6 +8,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = { rows: 1, columns: 2, widgetName: "Button", + isDisabled: false, + isVisible: true, }, TEXT_WIDGET: { text: "Not all labels are bad!", diff --git a/app/client/src/pages/Editor/Popper.tsx b/app/client/src/pages/Editor/Popper.tsx index 442d50c6ba..97da4a95d7 100644 --- a/app/client/src/pages/Editor/Popper.tsx +++ b/app/client/src/pages/Editor/Popper.tsx @@ -13,7 +13,7 @@ type PopperProps = { const PopperWrapper = styled(PaneWrapper)` position: absolute; z-index: 1; - height: ${props => props.theme.propertyPane.height}px; + max-height: ${props => props.theme.propertyPane.height}px; width: ${props => props.theme.propertyPane.width}px; margin: ${props => props.theme.spaces[6]}px; `; @@ -23,27 +23,27 @@ export default (props: PopperProps) => { const contentRef = useRef(null); useEffect(() => { //TODO(abhinav): optimize this, remove previous Popper instance. - new PopperJS( - props.targetRefNode, - (contentRef.current as unknown) as Element, - { - placement: "right", - modifiers: { - flip: { - behavior: ["right", "left", "bottom", "top"], - }, - keepTogether: { - enabled: false, - }, - arrow: { - enabled: false, - }, - preventOverflow: { - boundariesElement: "viewport", + const parentElement = props.targetRefNode.parentElement; + if (parentElement && parentElement.parentElement) { + new PopperJS( + props.targetRefNode, + (contentRef.current as unknown) as Element, + { + placement: "right-start", + modifiers: { + flip: { + behavior: ["right", "left", "bottom", "top"], + }, + keepTogether: { + enabled: false, + }, + arrow: { + enabled: false, + }, }, }, - }, - ); + ); + } }, [props.targetRefNode]); return createPortal( {props.children}, diff --git a/app/client/src/pages/Editor/PropertyPane.tsx b/app/client/src/pages/Editor/PropertyPane.tsx index 64212aa9cd..74b572aeba 100644 --- a/app/client/src/pages/Editor/PropertyPane.tsx +++ b/app/client/src/pages/Editor/PropertyPane.tsx @@ -1,4 +1,5 @@ import React, { Component } from "react"; +import styled from "styled-components"; import { connect } from "react-redux"; import { AppState } from "../../reducers"; import PropertyControlFactory from "../../utils/PropertyControlFactory"; @@ -11,10 +12,21 @@ import { getCurrentReferenceNode, getPropertyConfig, getIsPropertyPaneVisible, + getCurrentWidgetProperties, } from "../../selectors/propertyPaneSelectors"; import Popper from "./Popper"; +const PropertySectionLabel = styled.div` + text-transform: uppercase; + color: ${props => props.theme.colors.paneSectionLabel}; + padding: ${props => props.theme.spaces[5]}px 0; + font-size: ${props => props.theme.fontSizes[2]}px; + display: flex; + justify-content: flex-start; + align-items: center; +`; + class PropertyPane extends Component< PropertyPaneProps & PropertyPaneFunctions > { @@ -24,12 +36,7 @@ class PropertyPane extends Component< } render() { - if ( - this.props.isVisible && - this.props.widgetId && - this.props.targetNode && - this.props.propertySections - ) { + if (this.props.isVisible && this.props.widgetId && this.props.targetNode) { const content = this.renderPropertyPane(this.props.propertySections); return ( @@ -48,7 +55,7 @@ class PropertyPane extends Component< ? _.map(propertySections, (propertySection: PropertySection) => { return this.renderPropertySection( propertySection, - propertySection.id, + this.props.widgetId + propertySection.id, ); }) : undefined} @@ -60,7 +67,9 @@ class PropertyPane extends Component< return (
{!_.isNil(propertySection) ? ( -
{propertySection.sectionName}
+ + {propertySection.sectionName} + ) : ( undefined )} @@ -81,6 +90,9 @@ class PropertyPane extends Component< ); } else { try { + propertyControlOrSection.propertyValue = this.props.widgetProperties[ + propertyControlOrSection.propertyName + ]; return PropertyControlFactory.createControl( propertyControlOrSection, { onPropertyChange: this.onPropertyChange }, @@ -97,11 +109,11 @@ class PropertyPane extends Component< } onPropertyChange(propertyName: string, propertyValue: any) { - // this.props.updateWidgetProperty( - // this.props.widgetId, - // propertyName, - // propertyValue, - // ); + this.props.updateWidgetProperty( + this.props.widgetId, + propertyName, + propertyValue, + ); } } @@ -109,6 +121,7 @@ const mapStateToProps = (state: AppState): PropertyPaneProps => { return { propertySections: getPropertyConfig(state), widgetId: getCurrentWidgetId(state), + widgetProperties: getCurrentWidgetProperties(state), isVisible: getIsPropertyPaneVisible(state), targetNode: getCurrentReferenceNode(state), }; @@ -127,6 +140,7 @@ const mapDispatchToProps = (dispatch: any): PropertyPaneFunctions => { export interface PropertyPaneProps { propertySections?: PropertySection[]; widgetId?: string; + widgetProperties?: any; //TODO(abhinav): Secure type definition isVisible: boolean; targetNode?: HTMLDivElement; } diff --git a/app/client/src/pages/common/PaneWrapper.tsx b/app/client/src/pages/common/PaneWrapper.tsx index 419be2697c..34e4c9724d 100644 --- a/app/client/src/pages/common/PaneWrapper.tsx +++ b/app/client/src/pages/common/PaneWrapper.tsx @@ -4,7 +4,8 @@ export default styled.div` background-color: ${props => props.theme.colors.paneBG}; border-radius: ${props => props.theme.radii[2]}px; box-shadow: 0px 0px 3px ${props => props.theme.colors.paneBG}; - padding: 5px 10px; + padding: ${props => props.theme.spaces[5]}px + ${props => props.theme.spaces[7]}px; color: ${props => props.theme.colors.textOnDarkBG}; text-transform: capitalize; `; diff --git a/app/client/src/propertyControls/BaseControl.tsx b/app/client/src/propertyControls/BaseControl.tsx index fd2e933c7f..c056b4cf62 100644 --- a/app/client/src/propertyControls/BaseControl.tsx +++ b/app/client/src/propertyControls/BaseControl.tsx @@ -28,6 +28,7 @@ export interface ControlData { label: string; propertyName: string; controlType: ControlType; + propertyValue?: any; } export interface ControlFunctions { diff --git a/app/client/src/propertyControls/DropDownControl.tsx b/app/client/src/propertyControls/DropDownControl.tsx index 2840191547..891f035e44 100644 --- a/app/client/src/propertyControls/DropDownControl.tsx +++ b/app/client/src/propertyControls/DropDownControl.tsx @@ -2,7 +2,8 @@ import React, { SyntheticEvent } from "react"; import BaseControl, { ControlProps } from "./BaseControl"; import { ControlType } from "../constants/PropertyControlConstants"; import { Button, MenuItem } from "@blueprintjs/core"; -import { Select, IItemRendererProps } from "@blueprintjs/select"; +import { IItemRendererProps } from "@blueprintjs/select"; +import { ControlWrapper, StyledDropDown } from "./StyledControls"; class DropDownControl extends BaseControl { constructor(props: DropDownControlProps) { @@ -11,20 +12,25 @@ class DropDownControl extends BaseControl { } render() { - const DropDown = Select.ofType(); + const selected: DropdownOption | undefined = this.props.options.find( + option => option.value === this.props.propertyValue, + ); return ( - } - > -