setFocusedIndex(selectedIndex)}
diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx
index e09cbeee82..64e20f336e 100644
--- a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx
+++ b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx
@@ -1,5 +1,7 @@
import React, { memo, useCallback } from "react";
-import _, { isEqual } from "lodash";
+import _, { get, isEqual } from "lodash";
+import * as log from "loglevel";
+
import {
ControlPropertyLabelContainer,
ControlWrapper,
@@ -22,8 +24,10 @@ import { IPanelProps } from "@blueprintjs/core";
import PanelPropertiesEditor from "./PanelPropertiesEditor";
import {
getEvalValuePath,
+ isDynamicValue,
isPathADynamicProperty,
isPathADynamicTrigger,
+ THEME_BINDING_REGEX,
} from "utils/DynamicBindingUtils";
import {
getWidgetPropsForPropertyName,
@@ -37,13 +41,18 @@ import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { getExpectedValue } from "utils/validation/common";
import { ControlData } from "components/propertyControls/BaseControl";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
-import * as log from "loglevel";
+import { getSelectedAppTheme } from "selectors/appThemingSelectors";
+import TooltipComponent from "components/ads/Tooltip";
+import { ReactComponent as ResetIcon } from "assets/icons/control/undo_2.svg";
+import { AppTheme } from "entities/AppTheming";
type Props = PropertyPaneControlConfig & {
panel: IPanelProps;
theme: EditorTheme;
};
+const SHOULD_NOT_REJECT_DYNAMIC_BINDING_LIST_FOR = ["COLOR_PICKER"];
+
const PropertyControl = memo((props: Props) => {
const dispatch = useDispatch();
@@ -67,6 +76,53 @@ const PropertyControl = memo((props: Props) => {
isEqual,
);
+ const selectedTheme = useSelector(getSelectedAppTheme);
+
+ /**
+ * A property's stylesheet value can be fetched in 2 ways
+ * 1. If a method is defined on the property config (getStylesheetValue), then
+ * it's the methods responsibility to resolve the stylesheet value.
+ * 2. If no such method is defined, the value is assumed to be present in the
+ * theme config and thus it is fetched from there.
+ */
+ const propertyStylesheetValue = (() => {
+ const widgetStylesheet: AppTheme["stylesheet"][string] = get(
+ selectedTheme,
+ `stylesheet.${widgetProperties.type}`,
+ );
+
+ if (props.getStylesheetValue) {
+ return props.getStylesheetValue(
+ widgetProperties,
+ props.propertyName,
+ widgetStylesheet,
+ );
+ }
+
+ return get(widgetStylesheet, props.propertyName);
+ })();
+
+ const propertyValue = _.get(widgetProperties, props.propertyName);
+
+ /**
+ * checks if property value is deviated or not.
+ * by deviation, we mean if value of property is same as
+ * the one defined in the theme stylesheet. if values are different,
+ * that means the property value is deviated from the theme stylesheet.
+ */
+ const isPropertyDeviatedFromTheme =
+ typeof propertyStylesheetValue === "string" &&
+ THEME_BINDING_REGEX.test(propertyStylesheetValue) &&
+ propertyStylesheetValue !== propertyValue;
+
+ /**
+ * resets the value of property to theme stylesheet value
+ * which is a binding to theme object defined in the stylesheet
+ */
+ const resetPropertyValueToTheme = () => {
+ onPropertyChange(props.propertyName, propertyStylesheetValue);
+ };
+
const {
autoCompleteEnhancementFn: childWidgetAutoCompleteEnhancementFn,
customJSControlEnhancementFn: childWidgetCustomJSControlEnhancementFn,
@@ -83,11 +139,26 @@ const PropertyControl = memo((props: Props) => {
propertyName: propertyName,
propertyState: !isDynamic ? "JS" : "NORMAL",
});
+
+ let shouldRejectDynamicBindingPathList = true;
+
+ // we don't want to remove the path from dynamic binding list
+ // on toggling of js in case of few widgets
+ if (
+ SHOULD_NOT_REJECT_DYNAMIC_BINDING_LIST_FOR.includes(
+ props.controlType,
+ ) &&
+ isDynamicValue(propertyValue)
+ ) {
+ shouldRejectDynamicBindingPathList = false;
+ }
+
dispatch(
setWidgetDynamicProperty(
widgetProperties?.widgetId,
propertyName,
!isDynamic,
+ shouldRejectDynamicBindingPathList,
),
);
},
@@ -311,13 +382,15 @@ const PropertyControl = memo((props: Props) => {
);
// Do not render the control if it needs to be hidden
- if (props.hidden && props.hidden(widgetProperties, props.propertyName)) {
+ if (
+ (props.hidden && props.hidden(widgetProperties, props.propertyName)) ||
+ props.invisible
+ ) {
return null;
}
const { label, propertyName } = props;
if (widgetProperties) {
- const propertyValue = _.get(widgetProperties, propertyName);
// get the dataTreePath and apply enhancement if exists
let dataTreePath: string =
props.dataTreePath || `${widgetProperties.widgetName}.${propertyName}`;
@@ -406,7 +479,7 @@ const PropertyControl = memo((props: Props) => {
try {
return (
{
: "VERTICAL"
}
>
-
+
{
)}
+ {isPropertyDeviatedFromTheme && (
+ <>
+
+
+
+
+ >
+ )}
{PropertyControlFactory.createControl(
config,
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/DeleteThemeModal.tsx b/app/client/src/pages/Editor/ThemePropertyPane/DeleteThemeModal.tsx
new file mode 100644
index 0000000000..56bfaadc0c
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/DeleteThemeModal.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+
+import { Variant } from "components/ads/common";
+import {
+ createMessage,
+ DELETE_APP_THEME_WARNING,
+ DELETE_CONFIRMATION_MODAL_TITLE,
+} from "@appsmith/constants/messages";
+import { Colors } from "constants/Colors";
+import Dialog from "components/ads/DialogComponent";
+import Button, { Category, Size } from "components/ads/Button";
+
+interface DeleteThemeModalProps {
+ isOpen: boolean;
+ onClose(): void;
+ onDelete(): void;
+}
+
+const deleteIconConfig = {
+ name: "delete",
+ fillColor: Colors.DANGER_SOLID,
+ hoverColor: Colors.DANGER_SOLID_HOVER,
+};
+
+function DeleteThemeModal(props: DeleteThemeModalProps) {
+ const { isOpen, onClose, onDelete } = props;
+
+ return (
+
+ );
+}
+
+export default DeleteThemeModal;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/SaveThemeModal.tsx b/app/client/src/pages/Editor/ThemePropertyPane/SaveThemeModal.tsx
new file mode 100644
index 0000000000..de79475ed7
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/SaveThemeModal.tsx
@@ -0,0 +1,160 @@
+import React, { useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+
+import AnalyticsUtil from "utils/AnalyticsUtil";
+import TextInput from "components/ads/TextInput";
+import Dialog from "components/ads/DialogComponent";
+import Button, { Category, Size } from "components/ads/Button";
+import { saveSelectedThemeAction } from "actions/appThemingActions";
+import { getCurrentApplicationId } from "selectors/editorSelectors";
+import { getAppThemes } from "selectors/appThemingSelectors";
+import {
+ createMessage,
+ ERROR_MESSAGE_NAME_EMPTY,
+ SPECIAL_CHARACTER_ERROR,
+ UNIQUE_NAME_ERROR,
+} from "ce/constants/messages";
+
+interface SaveThemeModalProps {
+ isOpen: boolean;
+ onClose(): void;
+}
+
+function SaveThemeModal(props: SaveThemeModalProps) {
+ const { isOpen } = props;
+ const dispatch = useDispatch();
+ const [name, setName] = useState("");
+ const [inputValidator, setInputValidator] = useState({
+ isValid: false,
+ message: "",
+ isDirty: false,
+ });
+ const applicationId = useSelector(getCurrentApplicationId);
+ const themes = useSelector(getAppThemes);
+
+ /**
+ * dispatches action to save selected theme
+ *
+ */
+ const onSubmit = (event: any) => {
+ event.preventDefault();
+
+ // if input validations fails, don't do anything
+ if (!inputValidator.isValid || inputValidator.isDirty === false) return;
+
+ AnalyticsUtil.logEvent("APP_THEMING_SAVE_THEME_SUCCESS", {
+ themeName: name,
+ });
+
+ dispatch(saveSelectedThemeAction({ applicationId, name }));
+
+ // close the modal after submit
+ onClose();
+ };
+
+ /**
+ * theme creation validator
+ *
+ * @param value
+ * @returns
+ */
+ const createThemeValidator = (value: string) => {
+ let isValid = !!value;
+
+ let errorMessage = !isValid ? createMessage(ERROR_MESSAGE_NAME_EMPTY) : "";
+
+ if (
+ isValid &&
+ themes.find((theme) => value.toLowerCase() === theme.name.toLowerCase())
+ ) {
+ isValid = false;
+ errorMessage = createMessage(UNIQUE_NAME_ERROR);
+ }
+
+ if (/[^a-zA-Z0-9\-\/]/.test(value)) {
+ isValid = false;
+ errorMessage = createMessage(SPECIAL_CHARACTER_ERROR);
+ }
+
+ return {
+ isValid: isValid,
+ message: errorMessage,
+ isDirty: true,
+ };
+ };
+
+ /**
+ * on input change
+ *
+ * @param value
+ */
+ const onChangeName = (value: string) => {
+ const validator = createThemeValidator(value);
+
+ setInputValidator(validator);
+ setName(value);
+ };
+
+ /**
+ * on close modal
+ */
+ const onClose = () => {
+ // reset validations
+ setInputValidator({
+ isValid: false,
+ message: "",
+ isDirty: false,
+ });
+
+ props.onClose();
+ };
+
+ return (
+
+ );
+}
+
+export default SaveThemeModal;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/SettingSection.tsx b/app/client/src/pages/Editor/ThemePropertyPane/SettingSection.tsx
new file mode 100644
index 0000000000..49df7f21eb
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/SettingSection.tsx
@@ -0,0 +1,44 @@
+import * as Sentry from "@sentry/react";
+import React, { useState } from "react";
+import { Collapse } from "@blueprintjs/core";
+import ArrowRight from "remixicon-react/ArrowRightSLineIcon";
+
+interface SettingSectionProps {
+ isDefaultOpen?: boolean;
+ className?: string;
+ title: string;
+ children?: React.ReactNode;
+ collapsible?: boolean;
+}
+
+export function SettingSection(props: SettingSectionProps) {
+ const { className = "", collapsible = true } = props;
+ const [isOpen, setOpen] = useState(props.isDefaultOpen);
+
+ return (
+
+
setOpen((isOpen) => !isOpen)}
+ >
+
{props.title}
+ {collapsible && (
+
+ )}
+
+
+ {props.children}
+
+
+ );
+}
+
+SettingSection.displayName = "SettingSection";
+
+export default Sentry.withProfiler(SettingSection);
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/ThemeBetaCard.tsx b/app/client/src/pages/Editor/ThemePropertyPane/ThemeBetaCard.tsx
new file mode 100644
index 0000000000..3c3ef0abfa
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/ThemeBetaCard.tsx
@@ -0,0 +1,60 @@
+import React from "react";
+import styled from "styled-components";
+import { useDispatch } from "react-redux";
+
+import { closeAppThemingBetaCard } from "actions/appThemingActions";
+import {
+ createMessage,
+ APP_THEME_BETA_CARD_HEADING,
+ APP_THEME_BETA_CARD_CONTENT,
+} from "@appsmith/constants/messages";
+
+import { Button, Size, Category, Variant } from "components/ads";
+import { Colors } from "constants/Colors";
+
+const StyledButton = styled(Button)`
+ background-color: ${Colors.BLACK};
+ color: ${Colors.WHITE};
+ border: 2px solid ${Colors.BLACK};
+
+ &:hover {
+ background-color: transparent;
+ border: 2px solid ${Colors.BLACK};
+ color: ${Colors.BLACK};
+
+ svg {
+ path {
+ fill: ${Colors.BLACK};
+ }
+ }
+ }
+`;
+
+export function ThemeBetaCard() {
+ const dispatch = useDispatch();
+
+ const closeThemeBetaCard = () => {
+ dispatch(closeAppThemingBetaCard());
+ };
+
+ return (
+
+
{createMessage(APP_THEME_BETA_CARD_HEADING)}
+
{createMessage(APP_THEME_BETA_CARD_CONTENT)}
+
+
+
+
+
+ );
+}
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/ThemeCard.tsx b/app/client/src/pages/Editor/ThemePropertyPane/ThemeCard.tsx
new file mode 100644
index 0000000000..642a555d11
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/ThemeCard.tsx
@@ -0,0 +1,228 @@
+import { last } from "lodash";
+import classNames from "classnames";
+import styled from "styled-components";
+import * as Sentry from "@sentry/react";
+import React, { useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import DeleteIcon from "remixicon-react/DeleteBinLineIcon";
+
+import {
+ changeSelectedAppThemeAction,
+ deleteAppThemeAction,
+} from "actions/appThemingActions";
+import {
+ AppThemingMode,
+ getAppThemingStack,
+} from "selectors/appThemingSelectors";
+import { AppTheme } from "entities/AppTheming";
+import AnalyticsUtil from "utils/AnalyticsUtil";
+import DeleteThemeModal from "./DeleteThemeModal";
+import { getComplementaryGrayscaleColor } from "widgets/WidgetUtils";
+import { getCurrentApplicationId } from "selectors/editorSelectors";
+
+/**
+ * ----------------------------------------------------------------------------
+ * TYPES
+ *-----------------------------------------------------------------------------
+ */
+interface ThemeCard {
+ theme: AppTheme;
+ isSelected?: boolean;
+ className?: string;
+ selectable?: boolean;
+ deletable?: boolean;
+}
+
+const MainContainer = styled.main<{ backgroundColor: string }>`
+ background-color: ${({ backgroundColor }) => backgroundColor};
+`;
+
+const HeaderContainer = styled.main<{ primaryColor: string }>`
+ background-color: ${({ primaryColor }) => primaryColor};
+ color: ${({ primaryColor }) => getComplementaryGrayscaleColor(primaryColor)};
+`;
+
+const MainText = styled.main<{ backgroundColor: string }>`
+ color: ${({ backgroundColor }) =>
+ getComplementaryGrayscaleColor(backgroundColor)};
+`;
+
+const ThemeColorCircle = styled.main<{ backgroundColor: string }>`
+ background-color: ${({ backgroundColor }) => backgroundColor};
+`;
+
+const ThemeColorButton = styled.main<{
+ backgroundColor: string;
+ borderRadius: string;
+ boxShadow: string;
+ secondary?: boolean;
+ borderColor: string;
+}>`
+ background-color: ${({ backgroundColor }) => backgroundColor};
+ box-shadow: ${({ boxShadow }) => boxShadow};
+ border: ${({ borderColor }) => `1px solid ${borderColor}`};
+ border-radius: ${({ borderRadius }) => borderRadius};
+ color: ${({ backgroundColor }) =>
+ getComplementaryGrayscaleColor(backgroundColor)};
+`;
+
+/**
+ * ----------------------------------------------------------------------------
+ * COMPONENT
+ *-----------------------------------------------------------------------------
+ */
+export function ThemeCard(props: ThemeCard) {
+ const { deletable, selectable, theme } = props;
+ const dispatch = useDispatch();
+ const themingStack = useSelector(getAppThemingStack);
+ const themingMode = last(themingStack);
+ const applicationId = useSelector(getCurrentApplicationId);
+ const isThemeSelectionMode =
+ themingMode === AppThemingMode.APP_THEME_SELECTION;
+ const [isDeleteModalOpen, toggleDeleteModal] = useState(false);
+
+ // colors
+ const userDefinedColors = theme.properties.colors;
+ const primaryColor = userDefinedColors.primaryColor;
+ const backgroundColor = userDefinedColors.backgroundColor;
+
+ // border radius
+ const borderRadius = theme.properties.borderRadius;
+ const primaryBorderRadius = borderRadius[Object.keys(borderRadius)[0]];
+
+ // box shadow
+ const boxShadow = theme.properties.boxShadow;
+ const primaryBoxShadow = boxShadow[Object.keys(boxShadow)[0]];
+
+ /**
+ * fires action for changing theme
+ *
+ * NOTE: since we are same card in theme edit and theme selection,
+ * we don't need to fire the action in theme edit mode on click on the card
+ */
+ const changeSelectedTheme = () => {
+ AnalyticsUtil.logEvent("APP_THEMING_APPLY_THEME", {
+ themeId: theme.id,
+ themeName: theme.name,
+ });
+
+ if (isThemeSelectionMode && selectable) {
+ dispatch(
+ changeSelectedAppThemeAction({
+ applicationId,
+ theme,
+ }),
+ );
+ }
+ };
+
+ const openDeleteModalFn = () => toggleDeleteModal(true);
+ const closeDeleteModalFn = () => toggleDeleteModal(false);
+
+ /**
+ * dispatch delete app theme action
+ */
+ const onDeleteTheme = () => {
+ AnalyticsUtil.logEvent("APP_THEMING_DELETE_THEME", {
+ themeId: theme.id,
+ themeName: theme.name,
+ });
+
+ dispatch(deleteAppThemeAction({ themeId: theme.id, name: theme.name }));
+
+ closeDeleteModalFn();
+ };
+
+ return (
+ <>
+
+ {selectable && (
+
+
+ {props.theme.displayName}
+
+ {deletable && (
+
+ )}
+
+ )}
+
+
+
+
+
+ AaBbCc
+
+
+ {Object.keys(userDefinedColors).map((colorKey, index) => (
+
+ ))}
+
+
+
+
+
+ Button
+
+
+ Button
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+ThemeCard.displayName = "ThemeCard";
+
+export default Sentry.withProfiler(ThemeCard);
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/ThemeEditor.tsx b/app/client/src/pages/Editor/ThemePropertyPane/ThemeEditor.tsx
new file mode 100644
index 0000000000..cbd0701aa1
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/ThemeEditor.tsx
@@ -0,0 +1,259 @@
+import { createGlobalStyle } from "styled-components";
+import { get, startCase } from "lodash";
+import MoreIcon from "remixicon-react/MoreFillIcon";
+import { useDispatch, useSelector } from "react-redux";
+import React, { useCallback, useState } from "react";
+import Save2LineIcon from "remixicon-react/Save2LineIcon";
+
+import ThemeCard from "./ThemeCard";
+import {
+ Dropdown,
+ DropdownList,
+ DropdownItem,
+ DropdownTrigger,
+} from "components/ads/DropdownV2";
+import {
+ AppThemingMode,
+ getAppThemingStack,
+ getSelectedAppTheme,
+} from "selectors/appThemingSelectors";
+import {
+ setAppThemingModeStackAction,
+ updateSelectedAppThemeAction,
+} from "actions/appThemingActions";
+import SettingSection from "./SettingSection";
+import SaveThemeModal from "./SaveThemeModal";
+import { AppTheme } from "entities/AppTheming";
+import AnalyticsUtil from "utils/AnalyticsUtil";
+import ThemeFontControl from "./controls/ThemeFontControl";
+import ThemeColorControl from "./controls/ThemeColorControl";
+import Button, { Category, Size } from "components/ads/Button";
+import ThemeBoxShadowControl from "./controls/ThemeShadowControl";
+import { getCurrentApplicationId } from "selectors/editorSelectors";
+import ThemeBorderRadiusControl from "./controls/ThemeBorderRadiusControl";
+import BetaCard from "components/editorComponents/BetaCard";
+import { Classes as CsClasses } from "components/ads/common";
+
+const THEMING_BETA_CARD_POPOVER_CLASSNAME = `theming-beta-card-popover`;
+
+const PopoverStyles = createGlobalStyle`
+.${THEMING_BETA_CARD_POPOVER_CLASSNAME} .bp3-popover-content {
+ padding: 10px 12px;
+ border-radius: 0px;
+ background-color: #FFF !important;
+ color: #090707 !important;
+ box-shadow: none !important;
+}
+
+.${THEMING_BETA_CARD_POPOVER_CLASSNAME} .${CsClasses.BP3_POPOVER_ARROW_BORDER},
+.${THEMING_BETA_CARD_POPOVER_CLASSNAME} .${CsClasses.BP3_POPOVER_ARROW_FILL} {
+ fill: #FFF !important;
+ stroke: #FFF !important;
+ box-shadow: 0px 0px 2px rgb(0 0 0 / 20%), 0px 2px 10px rgb(0 0 0 / 10%);
+}
+`;
+
+function ThemeEditor() {
+ const dispatch = useDispatch();
+ const applicationId = useSelector(getCurrentApplicationId);
+ const selectedTheme = useSelector(getSelectedAppTheme);
+ const themingStack = useSelector(getAppThemingStack);
+ const [isSaveModalOpen, setSaveModalOpen] = useState(false);
+
+ /**
+ * customizes the current theme
+ */
+ const updateSelectedTheme = useCallback(
+ (theme: AppTheme) => {
+ AnalyticsUtil.logEvent("APP_THEMING_CUSTOMIZE_THEME", {
+ themeId: theme.id,
+ themeName: theme.name,
+ });
+
+ dispatch(updateSelectedAppThemeAction({ applicationId, theme }));
+ },
+ [updateSelectedAppThemeAction],
+ );
+
+ /**
+ * sets the mode to THEME_EDIT
+ */
+ const onClickChangeThemeButton = useCallback(() => {
+ AnalyticsUtil.logEvent("APP_THEMING_CHOOSE_THEME");
+
+ dispatch(
+ setAppThemingModeStackAction([
+ ...themingStack,
+ AppThemingMode.APP_THEME_SELECTION,
+ ]),
+ );
+ }, [setAppThemingModeStackAction]);
+
+ /**
+ * open the save modal
+ */
+ const onOpenSaveModal = useCallback(() => {
+ AnalyticsUtil.logEvent("APP_THEMING_SAVE_THEME_START");
+
+ setSaveModalOpen(true);
+ }, [setSaveModalOpen]);
+
+ /**
+ * on close save modal
+ */
+ const onCloseSaveModal = useCallback(() => {
+ setSaveModalOpen(false);
+ }, [setSaveModalOpen]);
+
+ return (
+ <>
+
+
+
+
+
+
+ {/* FONT */}
+
+ {Object.keys(selectedTheme.config.fontFamily).map(
+ (fontFamilySectionName: string, index: number) => {
+ return (
+
+ {startCase(fontFamilySectionName)}
+
+
+ );
+ },
+ )}
+
+ {/* COLORS */}
+
+
+
+
+ {/* BORDER RADIUS */}
+
+ {Object.keys(selectedTheme.config.borderRadius).map(
+ (borderRadiusSectionName: string, index: number) => {
+ return (
+
+ {startCase(borderRadiusSectionName)}
+
+
+ );
+ },
+ )}
+
+
+ {/* BOX SHADOW */}
+
+ {Object.keys(selectedTheme.config.boxShadow).map(
+ (boxShadowSectionName: string, index: number) => {
+ return (
+
+ {startCase(boxShadowSectionName)}
+
+
+ );
+ },
+ )}
+
+
+
+
+
+ >
+ );
+}
+
+export default ThemeEditor;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/ThemeSelector.tsx b/app/client/src/pages/Editor/ThemePropertyPane/ThemeSelector.tsx
new file mode 100644
index 0000000000..1450e63d72
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/ThemeSelector.tsx
@@ -0,0 +1,86 @@
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+
+import {
+ getAppThemes,
+ getAppThemingStack,
+ getSelectedAppTheme,
+} from "selectors/appThemingSelectors";
+import { ThemeCard } from "./ThemeCard";
+import { SettingSection } from "./SettingSection";
+import ArrowLeft from "remixicon-react/ArrowLeftSLineIcon";
+import { setAppThemingModeStackAction } from "actions/appThemingActions";
+
+function ThemeSelector() {
+ const dispatch = useDispatch();
+ const themes = useSelector(getAppThemes);
+ const themingStack = useSelector(getAppThemingStack);
+ const selectedTheme = useSelector(getSelectedAppTheme);
+
+ /**
+ * goes to previous screen in the pane
+ */
+ const onClickBack = () => {
+ dispatch(setAppThemingModeStackAction(themingStack.slice(0, -1)));
+ };
+
+ /**
+ * stores user saved themes
+ */
+ const userSavedThemes = themes.filter(
+ (theme) => theme.isSystemTheme === false,
+ );
+
+ /**
+ * stores default system themes
+ */
+ const systemThemes = themes.filter((theme) => theme.isSystemTheme === true);
+
+ return (
+
+
+ {userSavedThemes.length > 0 && (
+
+ Your Themes
+ {userSavedThemes.map((theme) => (
+
+ ))}
+
+ )}
+
+ Featured Themes
+ {systemThemes.map((theme) => (
+
+ ))}
+
+
+ );
+}
+
+export default ThemeSelector;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx
new file mode 100644
index 0000000000..b10f8aa713
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx
@@ -0,0 +1,68 @@
+import classNames from "classnames";
+import React, { useCallback } from "react";
+
+import { AppTheme } from "entities/AppTheming";
+import TooltipComponent from "components/ads/Tooltip";
+
+interface ThemeBorderRadiusControlProps {
+ options: {
+ [key: string]: string;
+ };
+ selectedOption?: string;
+ theme: AppTheme;
+ sectionName: string;
+ updateTheme: (theme: AppTheme) => void;
+}
+
+function ThemeBorderRadiusControl(props: ThemeBorderRadiusControlProps) {
+ const { options, sectionName, selectedOption, theme, updateTheme } = props;
+
+ /**
+ * changes the border in theme
+ */
+ const onChangeBorder = useCallback(
+ (optionKey: string) => {
+ updateTheme({
+ ...theme,
+ properties: {
+ ...theme.properties,
+ borderRadius: {
+ [sectionName]: options[optionKey],
+ },
+ },
+ });
+ },
+ [updateTheme, theme],
+ );
+
+ return (
+
+ {Object.keys(options).map((optionKey) => (
+
+
+
+ ))}
+
+ );
+}
+
+export default ThemeBorderRadiusControl;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeColorControl.tsx b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeColorControl.tsx
new file mode 100644
index 0000000000..2b395859c7
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeColorControl.tsx
@@ -0,0 +1,75 @@
+import { startCase } from "lodash";
+import classNames from "classnames";
+import React, { useState } from "react";
+import styled from "styled-components";
+
+import { AppTheme } from "entities/AppTheming";
+import TooltipComponent from "components/ads/Tooltip";
+import ColorPickerComponent from "components/ads/ColorPickerComponentV2";
+
+interface ThemeColorControlProps {
+ theme: AppTheme;
+ updateTheme: (theme: AppTheme) => void;
+}
+
+const ColorBox = styled.div<{
+ background: string;
+}>`
+ background: ${({ background }) => background};
+`;
+
+function ThemeColorControl(props: ThemeColorControlProps) {
+ const { theme, updateTheme } = props;
+ const [selectedColor, setSelectedColor] = useState(null);
+ const userDefinedColors = theme.properties.colors;
+
+ return (
+
+
+ {Object.keys(theme.properties.colors).map(
+ (colorName: string, index: number) => {
+ return (
+
+ {
+ setSelectedColor(
+ colorName !== selectedColor ? colorName : null,
+ );
+ }}
+ />
+
+ );
+ },
+ )}
+
+ {selectedColor && (
+
+ {
+ updateTheme({
+ ...theme,
+ properties: {
+ ...theme.properties,
+ colors: {
+ ...theme.properties.colors,
+ [selectedColor]: color,
+ },
+ },
+ });
+ }}
+ color={userDefinedColors[selectedColor]}
+ key={selectedColor}
+ />
+
+ )}
+
+ );
+}
+
+export default ThemeColorControl;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeFontControl.tsx b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeFontControl.tsx
new file mode 100644
index 0000000000..27d8d64d42
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeFontControl.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+
+import Dropdown, {
+ DropdownOption,
+ RenderOption,
+} from "components/ads/Dropdown";
+import { AppTheme } from "entities/AppTheming";
+
+interface ThemeFontControlProps {
+ theme: AppTheme;
+ sectionName: string;
+ options: string[];
+ selectedOption: string;
+ updateTheme: (theme: AppTheme) => void;
+}
+
+function ThemeFontControl(props: ThemeFontControlProps) {
+ const { options, sectionName, selectedOption, theme, updateTheme } = props;
+
+ /**
+ * renders dropdown option
+ *
+ * @param param0
+ * @returns
+ */
+ const renderOption: RenderOption = ({ isSelectedNode, option }) => (
+ {
+ if (!isSelectedNode) {
+ updateTheme({
+ ...theme,
+ properties: {
+ ...theme.properties,
+ fontFamily: {
+ ...theme.properties.fontFamily,
+ [sectionName]:
+ (option as DropdownOption).value || selectedOption,
+ },
+ },
+ });
+ }
+ }}
+ >
+
+ Aa
+
+
{(option as DropdownOption).label}
+
+ );
+
+ return (
+
+ ({
+ value: option,
+ label: option,
+ }))}
+ renderOption={renderOption}
+ selected={{
+ label: selectedOption,
+ value: selectedOption,
+ }}
+ showLabelOnly
+ width="100%"
+ />
+
+ );
+}
+
+export default ThemeFontControl;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx
new file mode 100644
index 0000000000..9889e4b4a1
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx
@@ -0,0 +1,68 @@
+import classNames from "classnames";
+import React, { useCallback } from "react";
+
+import { AppTheme } from "entities/AppTheming";
+import TooltipComponent from "components/ads/Tooltip";
+import CloseLineIcon from "remixicon-react/CloseLineIcon";
+
+interface ThemeBoxShadowControlProps {
+ options: {
+ [key: string]: string;
+ };
+ selectedOption?: string;
+ theme: AppTheme;
+ sectionName: string;
+ updateTheme: (theme: AppTheme) => void;
+}
+
+function ThemeBoxShadowControl(props: ThemeBoxShadowControlProps) {
+ const { options, sectionName, selectedOption, theme, updateTheme } = props;
+
+ /**
+ * changes the shadow in the theme
+ */
+ const onChangeShadow = useCallback(
+ (optionKey: string) => {
+ updateTheme({
+ ...theme,
+ properties: {
+ ...theme.properties,
+ boxShadow: {
+ ...theme.properties.boxShadow,
+ [sectionName]: options[optionKey],
+ },
+ },
+ });
+ },
+ [updateTheme, theme],
+ );
+
+ return (
+
+ {Object.keys(options).map((optionKey) => (
+
+
+
+ ))}
+
+ );
+}
+
+export default ThemeBoxShadowControl;
diff --git a/app/client/src/pages/Editor/ThemePropertyPane/index.tsx b/app/client/src/pages/Editor/ThemePropertyPane/index.tsx
new file mode 100644
index 0000000000..791759613d
--- /dev/null
+++ b/app/client/src/pages/Editor/ThemePropertyPane/index.tsx
@@ -0,0 +1,39 @@
+import React, { useMemo } from "react";
+import * as Sentry from "@sentry/react";
+import { last } from "lodash";
+
+import ThemeEditor from "./ThemeEditor";
+import ThemeSelector from "./ThemeSelector";
+import {
+ AppThemingMode,
+ getAppThemingStack,
+} from "selectors/appThemingSelectors";
+import { useSelector } from "react-redux";
+
+export function ThemePropertyPane() {
+ const themingStack = useSelector(getAppThemingStack);
+ const themingMode = last(themingStack);
+
+ /**
+ * renders the theming property pane:
+ *
+ * 1. if THEME_EDIT -> ThemeEditor
+ * 2. if THEME_SELECTION -> ThemeSelector
+ */
+ const propertyPane = useMemo(() => {
+ switch (true) {
+ case themingMode === AppThemingMode.APP_THEME_EDIT:
+ return ;
+ case themingMode === AppThemingMode.APP_THEME_SELECTION:
+ return ;
+ default:
+ return ;
+ }
+ }, [themingMode]);
+
+ return {propertyPane}
;
+}
+
+ThemePropertyPane.displayName = "ThemePropertyPane";
+
+export default Sentry.withProfiler(ThemePropertyPane);
diff --git a/app/client/src/pages/Editor/ToggleModeButton.tsx b/app/client/src/pages/Editor/ToggleModeButton.tsx
index 8d3178e914..9d3863f5d0 100644
--- a/app/client/src/pages/Editor/ToggleModeButton.tsx
+++ b/app/client/src/pages/Editor/ToggleModeButton.tsx
@@ -5,8 +5,7 @@ import TooltipComponent from "components/ads/Tooltip";
import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper";
import Pen from "remixicon-react/PencilFillIcon";
import Eye from "remixicon-react/EyeLineIcon";
-import { ReactComponent as CommentModeUnread } from "assets/icons/comments/comment-mode-unread-indicator.svg";
-import { ReactComponent as CommentMode } from "assets/icons/comments/chat.svg";
+import CommentIcon from "remixicon-react/MessageLineIcon";
import { Indices } from "constants/Layers";
import {
@@ -260,7 +259,6 @@ function CommentModeBtn({
showUnreadIndicator: boolean;
showSelectedMode: boolean;
}) {
- const CommentModeIcon = showUnreadIndicator ? CommentModeUnread : CommentMode;
const commentModeClassName = showUnreadIndicator
? `t--toggle-comment-mode-on--unread`
: `t--toggle-comment-mode-on`;
@@ -271,7 +269,7 @@ function CommentModeBtn({
className={`t--switch-comment-mode-on ${commentModeClassName}`}
onClick={handleSetCommentModeButton}
showSelectedMode={showSelectedMode}
- type="stroke"
+ type="fill"
>
-
+
+
+ {showUnreadIndicator && (
+
+ )}
+
);
@@ -353,8 +356,8 @@ function ToggleCommentModeButton({
const proceedToNextTourStep = useProceedToNextTourStep(activeStepConfig);
const isTourStepActive = useIsTourStepActive(activeStepConfig);
-
const mode = useSelector((state: AppState) => state.entities.app.mode);
+ const isViewMode = mode === APP_MODE.PUBLISHED;
const handleSetCommentModeButton = useCallback(() => {
AnalyticsUtil.logEvent("COMMENTS_TOGGLE_MODE", {
@@ -380,7 +383,7 @@ function ToggleCommentModeButton({
- {!isExploring && (
+ {!isExploring && !isViewMode && (
`
width: 100%;
position: relative;
overflow-x: auto;
overflow-y: auto;
+ background: ${({ background }) => background};
&:before {
position: absolute;
top: 0;
@@ -35,15 +44,17 @@ const Container = styled.section`
`;
function CanvasContainer() {
+ const dispatch = useDispatch();
const currentPageId = useSelector(getCurrentPageId);
const isFetchingPage = useSelector(getIsFetchingPage);
const widgets = useSelector(getCanvasWidgetDsl);
const pages = useSelector(getViewModePageList);
const theme = useSelector(getCurrentThemeDetails);
const isPreviewMode = useSelector(previewModeSelector);
+ const selectedTheme = useSelector(getSelectedAppTheme);
const params = useParams<{ applicationId: string; pageId: string }>();
const shouldHaveTopMargin = !isPreviewMode || pages.length > 1;
- const dispatch = useDispatch();
+ const isAppThemeChanging = useSelector(getAppThemeIsChanging);
useEffect(() => {
return () => {
@@ -51,6 +62,8 @@ function CanvasContainer() {
};
}, []);
+ const fontFamily = useGoogleFont(selectedTheme.properties.fontFamily.appFont);
+
const pageLoading = (
@@ -70,16 +83,27 @@ function CanvasContainer() {
const heightWithTopMargin = `calc(100vh - 2.25rem - ${theme.smallHeaderHeight} - ${theme.bottomBarHeight})`;
return (
+ {isAppThemeChanging && (
+
+
+
+ )}
{node}
);
diff --git a/app/client/src/pages/Editor/WidgetsEditor/PageTabs.tsx b/app/client/src/pages/Editor/WidgetsEditor/PageTabs.tsx
index 4562aad46d..2542f2d982 100644
--- a/app/client/src/pages/Editor/WidgetsEditor/PageTabs.tsx
+++ b/app/client/src/pages/Editor/WidgetsEditor/PageTabs.tsx
@@ -3,7 +3,7 @@ import classNames from "classnames";
import { useSelector } from "react-redux";
import { getCurrentApplication } from "selectors/applicationSelectors";
-import PageTabsContainer from "pages/AppViewer/viewer/PageTabsContainer";
+import PageTabsContainer from "pages/AppViewer/PageTabsContainer";
import {
getViewModePageList,
previewModeSelector,
diff --git a/app/client/src/pages/Editor/WidgetsEditor/Toolbar.tsx b/app/client/src/pages/Editor/WidgetsEditor/Toolbar.tsx
index 10a23c32d1..663a52ede3 100644
--- a/app/client/src/pages/Editor/WidgetsEditor/Toolbar.tsx
+++ b/app/client/src/pages/Editor/WidgetsEditor/Toolbar.tsx
@@ -1,32 +1,8 @@
-import React, { useCallback } from "react";
-import { useDispatch, useSelector } from "react-redux";
-
-import MenuIcon from "remixicon-react/MenuLineIcon";
-import { setExplorerActiveAction } from "actions/explorerActions";
-import { getExplorerPinned } from "selectors/explorerSelector";
+import React from "react";
function Toolbar() {
- const dispatch = useDispatch();
- const explorerPinned = useSelector(getExplorerPinned);
-
- /**
- * on hovering the menu, make the explorer active
- */
- const onMenuHover = useCallback(() => {
- dispatch(setExplorerActiveAction(true));
- }, [setExplorerActiveAction]);
-
return (
-
-
- {explorerPinned === false && (
-
- )}
-
-
+
);
}
diff --git a/app/client/src/pages/Editor/WidgetsEditor/index.tsx b/app/client/src/pages/Editor/WidgetsEditor/index.tsx
index 6a7bfc1172..b3b6490332 100644
--- a/app/client/src/pages/Editor/WidgetsEditor/index.tsx
+++ b/app/client/src/pages/Editor/WidgetsEditor/index.tsx
@@ -121,9 +121,9 @@ function WidgetsEditor() {
) : (
<>
{guidedTourEnabled && }
-
+
diff --git a/app/client/src/pages/common/ProfileDropdown.tsx b/app/client/src/pages/common/ProfileDropdown.tsx
index 96ddba16c3..4f54d20164 100644
--- a/app/client/src/pages/common/ProfileDropdown.tsx
+++ b/app/client/src/pages/common/ProfileDropdown.tsx
@@ -102,6 +102,7 @@ export default function ProfileDropdown(props: TagProps) {
>
diff --git a/app/client/src/pages/common/ProfileImage.tsx b/app/client/src/pages/common/ProfileImage.tsx
index 01fc8c368b..3f0f390262 100644
--- a/app/client/src/pages/common/ProfileImage.tsx
+++ b/app/client/src/pages/common/ProfileImage.tsx
@@ -4,9 +4,9 @@ import Text, { TextType } from "components/ads/Text";
import styled, { ThemeContext } from "styled-components";
import { Colors } from "constants/Colors";
-export const Profile = styled.div<{ backgroundColor?: string; side?: number }>`
- width: ${(props) => props.side || 34}px;
- height: ${(props) => props.side || 34}px;
+export const Profile = styled.div<{ backgroundColor?: string; size?: number }>`
+ width: ${(props) => props.size || 34}px;
+ height: ${(props) => props.size || 34}px;
display: flex;
align-items: center;
border-radius: 50%;
@@ -31,7 +31,7 @@ export default function ProfileImage(props: {
userName?: string;
className?: string;
commonName?: string;
- side?: number;
+ size?: number;
source?: string;
}) {
const theme = useContext(ThemeContext);
@@ -52,7 +52,7 @@ export default function ProfileImage(props: {
{!shouldRenderImage ? (
diff --git a/app/client/src/pages/organization/Members.tsx b/app/client/src/pages/organization/Members.tsx
index 762211745c..5541a3b11d 100644
--- a/app/client/src/pages/organization/Members.tsx
+++ b/app/client/src/pages/organization/Members.tsx
@@ -383,7 +383,7 @@ export default function MemberSettings(props: PageProps) {
diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx
index 2da3f0feb5..023b55fd0e 100644
--- a/app/client/src/reducers/index.tsx
+++ b/app/client/src/reducers/index.tsx
@@ -56,6 +56,7 @@ import { AppCollabReducerState } from "./uiReducers/appCollabReducer";
import { CrudInfoModalReduxState } from "./uiReducers/crudInfoModalReducer";
import { FormEvaluationState } from "./evaluationReducers/formEvaluationReducer";
import { widgetReflow } from "./uiReducers/reflowReducer";
+import { AppThemingState } from "./uiReducers/appThemingReducer";
import { MainCanvasReduxState } from "./uiReducers/mainCanvasReducer";
import SettingsReducer, {
SettingsReduxState,
@@ -113,6 +114,7 @@ export interface AppState {
appCollab: AppCollabReducerState;
crudInfoModal: CrudInfoModalReduxState;
widgetReflow: widgetReflow;
+ appTheming: AppThemingState;
mainCanvas: MainCanvasReduxState;
};
entities: {
diff --git a/app/client/src/reducers/uiReducers/appThemingReducer.ts b/app/client/src/reducers/uiReducers/appThemingReducer.ts
new file mode 100644
index 0000000000..b8607baaab
--- /dev/null
+++ b/app/client/src/reducers/uiReducers/appThemingReducer.ts
@@ -0,0 +1,134 @@
+import { AppTheme } from "entities/AppTheming";
+import { AppThemingMode } from "selectors/appThemingSelectors";
+import { createImmerReducer } from "utils/AppsmithUtils";
+import {
+ ReduxAction,
+ ReduxActionTypes,
+} from "@appsmith/constants/ReduxActionConstants";
+
+export type AppThemingState = {
+ isSaving: boolean;
+ isChanging: boolean;
+ stack: AppThemingMode[];
+ selectedTheme: AppTheme;
+ themes: AppTheme[];
+ themesLoading: boolean;
+ selectedThemeLoading: boolean;
+ isBetaCardShown: boolean;
+};
+
+const initialState: AppThemingState = {
+ stack: [],
+ themes: [],
+ isSaving: false,
+ isChanging: false,
+ themesLoading: false,
+ isBetaCardShown: true,
+ selectedThemeLoading: false,
+ selectedTheme: {
+ id: "",
+ name: "",
+ displayName: "",
+ created_by: "",
+ created_at: "",
+ config: {
+ colors: {
+ backgroundColor: "#f6f6f6",
+ primaryColor: "",
+ secondaryColor: "",
+ },
+ borderRadius: {},
+ boxShadow: {},
+ fontFamily: {},
+ },
+ properties: {
+ colors: {
+ backgroundColor: "#f6f6f6",
+ primaryColor: "",
+ secondaryColor: "",
+ },
+ borderRadius: {},
+ boxShadow: {},
+ fontFamily: {},
+ },
+ stylesheet: {},
+ },
+};
+
+const themeReducer = createImmerReducer(initialState, {
+ [ReduxActionTypes.SET_APP_THEMING_STACK]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.stack = action.payload;
+ },
+ [ReduxActionTypes.FETCH_APP_THEMES_INIT]: (state: AppThemingState) => {
+ state.themesLoading = true;
+ },
+ [ReduxActionTypes.FETCH_APP_THEMES_SUCCESS]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.themesLoading = false;
+ state.themes = action.payload;
+ },
+ [ReduxActionTypes.FETCH_SELECTED_APP_THEME_SUCCESS]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.themesLoading = false;
+ state.selectedTheme = action.payload;
+ },
+ [ReduxActionTypes.UPDATE_SELECTED_APP_THEME_INIT]: (
+ state: AppThemingState,
+ ) => {
+ state.isSaving = true;
+ },
+ [ReduxActionTypes.UPDATE_SELECTED_APP_THEME_SUCCESS]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.isSaving = false;
+ state.selectedTheme = action.payload;
+ },
+ [ReduxActionTypes.CHANGE_SELECTED_APP_THEME_INIT]: (
+ state: AppThemingState,
+ ) => {
+ state.isChanging = true;
+ },
+ [ReduxActionTypes.CHANGE_SELECTED_APP_THEME_SUCCESS]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.isChanging = false;
+ state.selectedTheme = action.payload;
+ },
+ [ReduxActionTypes.DELETE_APP_THEME_SUCCESS]: (
+ state: AppThemingState,
+ action: ReduxAction<{ themeId: string }>,
+ ) => {
+ state.themes = state.themes.filter(
+ (theme) => theme.id !== action.payload.themeId,
+ );
+ },
+ [ReduxActionTypes.SAVE_APP_THEME_SUCCESS]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.themes.push(action.payload);
+ },
+ [ReduxActionTypes.UPDATE_BETA_CARD_SHOWN]: (
+ state: AppThemingState,
+ action: ReduxAction,
+ ) => {
+ state.isBetaCardShown = action.payload;
+ },
+ [ReduxActionTypes.CLOSE_BETA_CARD_SHOWN]: (state: AppThemingState) => {
+ state.isBetaCardShown = true;
+ },
+ [ReduxActionTypes.FOCUS_WIDGET]: (state: AppThemingState) => {
+ state.stack = [];
+ },
+});
+
+export default themeReducer;
diff --git a/app/client/src/reducers/uiReducers/appViewReducer.tsx b/app/client/src/reducers/uiReducers/appViewReducer.tsx
index 736da000d4..0104c8b2a8 100644
--- a/app/client/src/reducers/uiReducers/appViewReducer.tsx
+++ b/app/client/src/reducers/uiReducers/appViewReducer.tsx
@@ -1,5 +1,6 @@
import { createReducer } from "utils/AppsmithUtils";
import {
+ ReduxAction,
ReduxActionTypes,
ReduxActionErrorTypes,
} from "@appsmith/constants/ReduxActionConstants";
@@ -7,6 +8,7 @@ import {
const initialState: AppViewReduxState = {
isFetchingPage: false,
initialized: false,
+ headerHeight: 0,
};
const appViewReducer = createReducer(initialState, {
@@ -34,11 +36,21 @@ const appViewReducer = createReducer(initialState, {
isFetchingPage: false,
};
},
+ [ReduxActionTypes.SET_APP_VIEWER_HEADER_HEIGHT]: (
+ state: AppViewReduxState,
+ action: ReduxAction,
+ ) => {
+ return {
+ ...state,
+ headerHeight: action.payload,
+ };
+ },
});
export interface AppViewReduxState {
initialized: boolean;
isFetchingPage: boolean;
+ headerHeight: number;
}
export default appViewReducer;
diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx
index ab9474d410..2def5faacb 100644
--- a/app/client/src/reducers/uiReducers/index.tsx
+++ b/app/client/src/reducers/uiReducers/index.tsx
@@ -39,6 +39,7 @@ import gitSyncReducer from "./gitSyncReducer";
import crudInfoModalReducer from "./crudInfoModalReducer";
import { widgetReflowReducer } from "./reflowReducer";
import jsObjectNameReducer from "./jsObjectNameReducer";
+import appThemingReducer from "./appThemingReducer";
import mainCanvasReducer from "./mainCanvasReducer";
const uiReducer = combineReducers({
@@ -82,6 +83,7 @@ const uiReducer = combineReducers({
appCollab: appCollabReducer,
crudInfoModal: crudInfoModalReducer,
widgetReflow: widgetReflowReducer,
+ appTheming: appThemingReducer,
mainCanvas: mainCanvasReducer,
});
diff --git a/app/client/src/sagas/AppThemingSaga.tsx b/app/client/src/sagas/AppThemingSaga.tsx
new file mode 100644
index 0000000000..e21e8c1879
--- /dev/null
+++ b/app/client/src/sagas/AppThemingSaga.tsx
@@ -0,0 +1,276 @@
+import React from "react";
+import {
+ ChangeSelectedAppThemeAction,
+ DeleteAppThemeAction,
+ FetchAppThemesAction,
+ FetchSelectedAppThemeAction,
+ SaveAppThemeAction,
+ updateisBetaCardShownAction,
+ UpdateSelectedAppThemeAction,
+} from "actions/appThemingActions";
+import {
+ ReduxAction,
+ ReduxActionErrorTypes,
+ ReduxActionTypes,
+} from "@appsmith/constants/ReduxActionConstants";
+import ThemingApi from "api/AppThemingApi";
+import { all, takeLatest, put, select } from "redux-saga/effects";
+import { Variant } from "components/ads/common";
+import { Toaster } from "components/ads/Toast";
+import {
+ CHANGE_APP_THEME,
+ createMessage,
+ DELETE_APP_THEME,
+ SAVE_APP_THEME,
+} from "@appsmith/constants/messages";
+import { ENTITY_TYPE } from "entities/AppsmithConsole";
+import { undoAction, updateReplayEntity } from "actions/pageActions";
+import { getCanvasWidgets } from "selectors/entitiesSelector";
+import store from "store";
+import { getAppMode } from "selectors/applicationSelectors";
+import { APP_MODE } from "entities/App";
+import { getCurrentUser } from "selectors/usersSelectors";
+import { User } from "constants/userConstants";
+import { getBetaFlag, setBetaFlag, STORAGE_KEYS } from "utils/storage";
+
+/**
+ * init app theming
+ */
+export function* initAppTheming() {
+ try {
+ const user: User = yield select(getCurrentUser);
+ const { email } = user;
+ if (email) {
+ const appThemingBetaFlag: boolean = yield getBetaFlag(
+ email,
+ STORAGE_KEYS.APP_THEMING_BETA_SHOWN,
+ );
+
+ yield put(updateisBetaCardShownAction(appThemingBetaFlag));
+ }
+ } catch (error) {}
+}
+
+/**
+ * fetches all themes of the application
+ *
+ * @param action
+ */
+// eslint-disable-next-line
+export function* fetchAppThemes(action: ReduxAction) {
+ try {
+ const { applicationId } = action.payload;
+ const response = yield ThemingApi.fetchThemes(applicationId);
+
+ yield put({
+ type: ReduxActionTypes.FETCH_APP_THEMES_SUCCESS,
+ payload: response.data,
+ });
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.FETCH_APP_THEMES_ERROR,
+ payload: { error },
+ });
+ }
+}
+
+/**
+ * fetches the selected theme of the application
+ *
+ * @param action
+ */
+
+export function* fetchAppSelectedTheme(
+ // eslint-disable-next-line
+ action: ReduxAction,
+) {
+ const { applicationId } = action.payload;
+ const mode: APP_MODE = yield select(getAppMode);
+
+ try {
+ // eslint-disable-next-line
+ const response = yield ThemingApi.fetchSelected(applicationId, mode);
+
+ yield put({
+ type: ReduxActionTypes.FETCH_SELECTED_APP_THEME_SUCCESS,
+ payload: response.data,
+ });
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.FETCH_SELECTED_APP_THEME_ERROR,
+ payload: { error },
+ });
+ }
+}
+
+/**
+ * updates the selected theme of the application
+ *
+ * @param action
+ */
+export function* updateSelectedTheme(
+ action: ReduxAction,
+) {
+ // eslint-disable-next-line
+ const { shouldReplay = true, theme, applicationId } = action.payload;
+ const canvasWidgets = yield select(getCanvasWidgets);
+
+ try {
+ yield ThemingApi.updateTheme(applicationId, theme);
+
+ yield put({
+ type: ReduxActionTypes.UPDATE_SELECTED_APP_THEME_SUCCESS,
+ payload: theme,
+ });
+
+ if (shouldReplay) {
+ yield put(
+ updateReplayEntity(
+ "canvas",
+ { widgets: canvasWidgets, theme },
+ ENTITY_TYPE.WIDGET,
+ ),
+ );
+ }
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.UPDATE_SELECTED_APP_THEME_ERROR,
+ payload: { error },
+ });
+ }
+}
+
+/**
+ * changes eelcted theme
+ *
+ * @param action
+ */
+export function* changeSelectedTheme(
+ action: ReduxAction,
+) {
+ const { applicationId, shouldReplay = true, theme } = action.payload;
+ const canvasWidgets = yield select(getCanvasWidgets);
+
+ try {
+ yield ThemingApi.changeTheme(applicationId, theme);
+
+ yield put({
+ type: ReduxActionTypes.CHANGE_SELECTED_APP_THEME_SUCCESS,
+ payload: theme,
+ });
+
+ // shows toast
+ Toaster.show({
+ text: createMessage(CHANGE_APP_THEME, theme.name),
+ variant: Variant.success,
+ actionElement: (
+ store.dispatch(undoAction())}>Undo
+ ),
+ });
+
+ if (shouldReplay) {
+ yield put(
+ updateReplayEntity(
+ "canvas",
+ { widgets: canvasWidgets, theme },
+ ENTITY_TYPE.WIDGET,
+ ),
+ );
+ }
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.UPDATE_SELECTED_APP_THEME_ERROR,
+ payload: { error },
+ });
+ }
+}
+
+/**
+ * save and create new theme from selected theme
+ *
+ * @param action
+ */
+export function* saveSelectedTheme(action: ReduxAction) {
+ const { applicationId, name } = action.payload;
+
+ try {
+ const response = yield ThemingApi.saveTheme(applicationId, { name });
+
+ yield put({
+ type: ReduxActionTypes.SAVE_APP_THEME_SUCCESS,
+ payload: response.data,
+ });
+
+ // shows toast
+ Toaster.show({
+ text: createMessage(SAVE_APP_THEME, name),
+ variant: Variant.success,
+ });
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.SAVE_APP_THEME_ERROR,
+ payload: { error },
+ });
+ }
+}
+
+/**
+ * deletes custom saved theme
+ *
+ * @param action
+ */
+export function* deleteTheme(action: ReduxAction) {
+ const { name, themeId } = action.payload;
+
+ try {
+ yield ThemingApi.deleteTheme(themeId);
+
+ yield put({
+ type: ReduxActionTypes.DELETE_APP_THEME_SUCCESS,
+ payload: { themeId },
+ });
+
+ // shows toast
+ Toaster.show({
+ text: createMessage(DELETE_APP_THEME, name),
+ variant: Variant.success,
+ });
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.DELETE_APP_THEME_ERROR,
+ payload: { error },
+ });
+ }
+}
+
+function* closeisBetaCardShown() {
+ try {
+ const user: User = yield select(getCurrentUser);
+ const { email } = user;
+ if (email) {
+ yield setBetaFlag(email, STORAGE_KEYS.APP_THEMING_BETA_SHOWN, true);
+ }
+ } catch (error) {}
+}
+
+export default function* appThemingSaga() {
+ yield all([takeLatest(ReduxActionTypes.INITIALIZE_EDITOR, initAppTheming)]);
+ yield all([
+ takeLatest(ReduxActionTypes.FETCH_APP_THEMES_INIT, fetchAppThemes),
+ takeLatest(
+ ReduxActionTypes.FETCH_SELECTED_APP_THEME_INIT,
+ fetchAppSelectedTheme,
+ ),
+ takeLatest(
+ ReduxActionTypes.UPDATE_SELECTED_APP_THEME_INIT,
+ updateSelectedTheme,
+ ),
+ takeLatest(
+ ReduxActionTypes.CHANGE_SELECTED_APP_THEME_INIT,
+ changeSelectedTheme,
+ ),
+ takeLatest(ReduxActionTypes.SAVE_APP_THEME_INIT, saveSelectedTheme),
+ takeLatest(ReduxActionTypes.DELETE_APP_THEME_INIT, deleteTheme),
+ takeLatest(ReduxActionTypes.CLOSE_BETA_CARD_SHOWN, closeisBetaCardShown),
+ ]);
+}
diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts
index 65076ca0ac..7d754ff50a 100644
--- a/app/client/src/sagas/EvaluationsSaga.ts
+++ b/app/client/src/sagas/EvaluationsSaga.ts
@@ -85,6 +85,7 @@ import { Channel } from "redux-saga";
import { ActionDescription } from "entities/DataTree/actionTriggers";
import { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer";
import { FormEvalActionPayload } from "./FormEvaluationSaga";
+import { getSelectedAppTheme } from "selectors/appThemingSelectors";
import { updateMetaState } from "actions/metaActions";
import { getAllActionValidationConfig } from "selectors/entitiesSelector";
@@ -99,6 +100,7 @@ function* evaluateTreeSaga(
const allActionValidationConfig = yield select(getAllActionValidationConfig);
const unevalTree = yield select(getUnevaluatedDataTree);
const widgets = yield select(getWidgets);
+ const theme = yield select(getSelectedAppTheme);
log.debug({ unevalTree });
PerformanceTracker.startAsyncTracking(
@@ -111,6 +113,7 @@ function* evaluateTreeSaga(
unevalTree,
widgetTypeConfigMap,
widgets,
+ theme,
shouldReplay,
allActionValidationConfig,
},
@@ -438,6 +441,7 @@ function* evaluationChangeListenerSaga() {
const action: EvaluationReduxAction = yield take(
evtActionChannel,
);
+
if (shouldProcessBatchedAction(action)) {
const postEvalActions = getPostEvalActions(action);
yield call(
diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts
index 0a245f538c..b8c4f1fec9 100644
--- a/app/client/src/sagas/InitSagas.ts
+++ b/app/client/src/sagas/InitSagas.ts
@@ -86,6 +86,10 @@ import { isURLDeprecated, getUpdatedRoute } from "utils/helpers";
import { fillPathname, viewerURL, builderURL } from "RouteBuilder";
import { enableGuidedTour } from "actions/onboardingActions";
import { setPreviewModeAction } from "actions/editorActions";
+import {
+ fetchSelectedAppThemeAction,
+ fetchAppThemesAction,
+} from "actions/appThemingActions";
export function* failFastApiCalls(
triggerActions: Array | ReduxActionWithoutPayload>,
@@ -121,6 +125,13 @@ export function* failFastApiCalls(
return true;
}
+/**
+ * this saga is called once then application is loaded.
+ * It will hold the editor in uninitialized till all the apis/actions are completed
+ *
+ * @param initializeEditorAction
+ * @returns
+ */
function* bootstrapEditor(payload: InitializeEditorPayload) {
const { branch } = payload;
yield put(resetEditorSuccess());
@@ -233,11 +244,15 @@ function* initiateEditorActions(applicationId: string) {
const initActionsCalls = [
fetchActions({ applicationId }, []),
fetchJSCollections({ applicationId }),
+ fetchSelectedAppThemeAction(applicationId),
+ fetchAppThemesAction(applicationId),
];
const successActionEffects = [
ReduxActionTypes.FETCH_JS_ACTIONS_SUCCESS,
ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
+ ReduxActionTypes.FETCH_APP_THEMES_SUCCESS,
+ ReduxActionTypes.FETCH_SELECTED_APP_THEME_SUCCESS,
];
const failureActionEffects = [
ReduxActionErrorTypes.FETCH_JS_ACTIONS_ERROR,
@@ -412,10 +427,16 @@ export function* initializeAppViewerSaga(
[
fetchActionsForView({ applicationId }),
fetchJSCollectionsForView({ applicationId }),
+ fetchPublishedPage(toLoadPageId, true),
+ fetchSelectedAppThemeAction(applicationId),
+ fetchAppThemesAction(applicationId),
],
[
ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
ReduxActionTypes.FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS,
+ ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
+ ReduxActionTypes.FETCH_APP_THEMES_SUCCESS,
+ ReduxActionTypes.FETCH_SELECTED_APP_THEME_SUCCESS,
],
[
ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR,
diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx
index a6613dd060..50b48ba8a4 100644
--- a/app/client/src/sagas/PageSagas.tsx
+++ b/app/client/src/sagas/PageSagas.tsx
@@ -348,7 +348,7 @@ export function* fetchAllPublishedPagesSaga() {
const pageIds = yield select(getAllPageIds);
yield all(
pageIds.map((pageId: string) => {
- return call(PageApi.fetchPublishedPage, { pageId });
+ return call(PageApi.fetchPublishedPage, { pageId, bustCache: true });
}),
);
} catch (error) {
diff --git a/app/client/src/sagas/ReplaySaga.ts b/app/client/src/sagas/ReplaySaga.ts
index c0f807c450..174511da09 100644
--- a/app/client/src/sagas/ReplaySaga.ts
+++ b/app/client/src/sagas/ReplaySaga.ts
@@ -36,7 +36,10 @@ import {
import { updateAndSaveLayout } from "actions/pageActions";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { commentModeSelector } from "selectors/commentsSelectors";
-import { snipingModeSelector } from "selectors/editorSelectors";
+import {
+ getCurrentApplicationId,
+ snipingModeSelector,
+} from "selectors/editorSelectors";
import { findFieldInfo, REPLAY_FOCUS_DELAY } from "entities/Replay/replayUtils";
import { setActionProperty, updateAction } from "actions/pluginActionActions";
import { getEntityInCurrentPath } from "./RecentEntitiesSagas";
@@ -70,6 +73,12 @@ import {
DATASOURCE_REST_API_FORM,
QUERY_EDITOR_FORM_NAME,
} from "constants/forms";
+import { Canvas } from "entities/Replay/ReplayEntity/ReplayCanvas";
+import {
+ setAppThemingModeStackAction,
+ updateSelectedAppThemeAction,
+} from "actions/appThemingActions";
+import { AppThemingMode } from "selectors/appThemingSelectors";
export type UndoRedoPayload = {
operation: ReplayReduxActionTypes;
@@ -195,12 +204,18 @@ export function* undoRedoSaga(action: ReduxAction) {
} = workerResponse;
logs && logs.forEach((evalLog: any) => log.debug(evalLog));
+
+ if (replay.theme) {
+ yield call(replayThemeSaga, replayEntity, replay);
+
+ return;
+ }
switch (replayEntityType) {
case ENTITY_TYPE.WIDGET: {
const isPropertyUpdate = replay.widgets && replay.propertyUpdates;
AnalyticsUtil.logEvent(event, { paths, timeTaken });
if (isPropertyUpdate) yield call(openPropertyPaneSaga, replay);
- yield put(updateAndSaveLayout(replayEntity, false, false));
+ yield put(updateAndSaveLayout(replayEntity.widgets, false, false));
if (!isPropertyUpdate) yield call(postUndoRedoSaga, replay);
break;
}
@@ -223,6 +238,39 @@ export function* undoRedoSaga(action: ReduxAction) {
}
}
+/**
+ * replay theme actions
+ *
+ * @param replayEntity
+ * @param replay
+ */
+function* replayThemeSaga(replayEntity: Canvas, replay: any) {
+ const applicationId: string = yield select(getCurrentApplicationId);
+
+ // if theme is changed, open the theme selector
+ if (replay.themeChanged) {
+ yield put(
+ setAppThemingModeStackAction([AppThemingMode.APP_THEME_SELECTION]),
+ );
+ } else {
+ yield put(setAppThemingModeStackAction([]));
+ }
+
+ yield put(selectWidgetAction());
+
+ // todo(pawan): check with arun/rahul on how we can get rid of this check
+ // better way to do is set shouldreplay = false when evaluating tree
+ if (replayEntity.theme.id) {
+ yield put(
+ updateSelectedAppThemeAction({
+ theme: replayEntity.theme,
+ shouldReplay: false,
+ applicationId,
+ }),
+ );
+ }
+}
+
function* replayActionSaga(
replayEntity: Action,
replay: { updates: ReplayEditorUpdate[] },
@@ -328,7 +376,7 @@ function* getDatasourceFieldConfig(
}
/*
- Figure out the tab in which the last modified field is present and the
+ Figure out the tab in which the last modified field is present and the
field config of the last modified field.
*/
function* getEditorFieldConfig(replayEntity: Action, modifiedProperty: string) {
diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts
index 83822ee8e6..8ef3516e65 100644
--- a/app/client/src/sagas/WidgetAdditionSagas.ts
+++ b/app/client/src/sagas/WidgetAdditionSagas.ts
@@ -36,8 +36,18 @@ import WidgetFactory from "utils/WidgetFactory";
import omit from "lodash/omit";
import produce from "immer";
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+import { getSelectedAppThemeStylesheet } from "selectors/appThemingSelectors";
+import { getPropertiesToUpdate } from "./WidgetOperationSagas";
+import { klona as clone } from "klona/full";
+
const WidgetTypes = WidgetFactory.widgetTypes;
+const themePropertiesDefaults = {
+ boxShadow: "none",
+ borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
+};
+
type GeneratedWidgetPayload = {
widgetId: string;
widgets: { [widgetId: string]: FlattenedWidgetProps };
@@ -53,6 +63,20 @@ function* getEntityNames() {
return Object.keys(evalTree);
}
+/**
+ * return stylesheet of widget
+ * NOTE: a stylesheet is an object that contains
+ * which property of widget will use which property of the theme
+ *
+ * @param type
+ * @returns
+ */
+function* getThemeDefaultConfig(type: string) {
+ const stylesheet = yield select(getSelectedAppThemeStylesheet);
+
+ return stylesheet[type] || themePropertiesDefaults;
+}
+
function* getChildWidgetProps(
parent: FlattenedWidgetProps,
params: WidgetAddChild,
@@ -71,6 +95,7 @@ function* getChildWidgetProps(
const restDefaultConfig = omit(WidgetFactory.widgetConfigMap.get(type), [
"blueprint",
]);
+ const themeDefaultConfig = yield call(getThemeDefaultConfig, type);
if (!widgetName) {
const widgetNames = Object.keys(widgets).map((w) => widgets[w].widgetName);
const entityNames: string[] = yield call(getEntityNames);
@@ -106,6 +131,7 @@ function* getChildWidgetProps(
minHeight,
widgetId: newWidgetId,
renderMode: RenderModes.CANVAS,
+ ...themeDefaultConfig,
};
const widget = generateWidgetProps(
parent,
@@ -120,8 +146,15 @@ function* getChildWidgetProps(
);
widget.widgetId = newWidgetId;
+ const { dynamicBindingPathList } = yield call(
+ getPropertiesToUpdate,
+ widget,
+ themeDefaultConfig,
+ );
+ widget.dynamicBindingPathList = clone(dynamicBindingPathList);
return widget;
}
+
function* generateChildWidgets(
parent: FlattenedWidgetProps,
params: WidgetAddChild,
diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx
index 019b021082..8aef191adb 100644
--- a/app/client/src/sagas/WidgetOperationSagas.tsx
+++ b/app/client/src/sagas/WidgetOperationSagas.tsx
@@ -349,7 +349,12 @@ function* updateWidgetPropertySaga(
export function* setWidgetDynamicPropertySaga(
action: ReduxAction,
) {
- const { isDynamic, propertyPath, widgetId } = action.payload;
+ const {
+ isDynamic,
+ propertyPath,
+ shouldRejectDynamicBindingPathList = true,
+ widgetId,
+ } = action.payload;
const stateWidget: WidgetProps = yield select(getWidget, widgetId);
let widget = cloneDeep({ ...stateWidget });
const propertyValue = _.get(widget, propertyPath);
@@ -370,9 +375,12 @@ export function* setWidgetDynamicPropertySaga(
dynamicPropertyPathList = _.reject(dynamicPropertyPathList, {
key: propertyPath,
});
- dynamicBindingPathList = _.reject(dynamicBindingPathList, {
- key: propertyPath,
- });
+
+ if (shouldRejectDynamicBindingPathList) {
+ dynamicBindingPathList = _.reject(dynamicBindingPathList, {
+ key: propertyPath,
+ });
+ }
const { parsed } = yield call(
validateProperty,
propertyPath,
@@ -390,7 +398,7 @@ export function* setWidgetDynamicPropertySaga(
yield put(updateAndSaveLayout(widgets));
}
-function getPropertiesToUpdate(
+export function getPropertiesToUpdate(
widget: WidgetProps,
updates: Record,
triggerPaths?: string[],
diff --git a/app/client/src/sagas/index.tsx b/app/client/src/sagas/index.tsx
index a9cd8949d9..4bef0e0edc 100644
--- a/app/client/src/sagas/index.tsx
+++ b/app/client/src/sagas/index.tsx
@@ -39,7 +39,7 @@ import replaySaga from "./ReplaySaga";
import selectionCanvasSagas from "./CanvasSagas/SelectionCanvasSagas";
import draggingCanvasSagas from "./CanvasSagas/DraggingCanvasSagas";
import gitSyncSagas from "./GitSyncSagas";
-
+import appThemingSaga from "./AppThemingSaga";
import log from "loglevel";
import * as sentry from "@sentry/react";
import formEvaluationChangeListener from "./FormEvaluationSaga";
@@ -89,6 +89,7 @@ const sagas = [
draggingCanvasSagas,
gitSyncSagas,
SuperUserSagas,
+ appThemingSaga,
];
export function* rootSaga(sagasToRun = sagas): any {
diff --git a/app/client/src/selectors/appThemingSelectors.tsx b/app/client/src/selectors/appThemingSelectors.tsx
new file mode 100644
index 0000000000..e10b29833e
--- /dev/null
+++ b/app/client/src/selectors/appThemingSelectors.tsx
@@ -0,0 +1,75 @@
+import { AppState } from "reducers";
+
+export enum AppThemingMode {
+ APP_THEME_EDIT = "APP_THEME_EDIT",
+ APP_THEME_SELECTION = "APP_THEME_SELECTION",
+}
+
+/**
+ * returns the theming mode ( edit, selection, variant editor )
+ *
+ * @param state
+ * @returns
+ */
+export const getAppThemingStack = (state: AppState) => {
+ return state.ui.appTheming.stack;
+};
+
+/**
+ * gets the themes
+ *
+ * @param state
+ * @returns
+ */
+export const getAppThemes = (state: AppState) => {
+ return state.ui.appTheming.themes;
+};
+
+/**
+ * get the selected theme
+ *
+ * @param state
+ * @returns
+ */
+export const getSelectedAppTheme = (state: AppState) => {
+ return state.ui.appTheming.selectedTheme;
+};
+
+/**
+ * get the selected theme stylsheet
+ *
+ * @param state
+ * @returns
+ */
+export const getSelectedAppThemeStylesheet = (state: AppState) => {
+ return state.ui.appTheming.selectedTheme.stylesheet;
+};
+
+/**
+ * get the preview theme or selected theme
+ *
+ * @param state
+ * @returns
+ */
+export const getSelectedAppThemeProperties = (state: AppState) => {
+ return state.ui.appTheming.selectedTheme.properties;
+};
+
+/**
+ * gets the value of `state.ui.appTheming.isSaving`
+ *
+ * @param state
+ * @returns
+ */
+export const getAppThemeIsChanging = (state: AppState) => {
+ return state.ui.appTheming.isChanging;
+};
+
+/**
+ * gets the value of `state.ui.appTheming.isSaving`
+ *
+ * @param state
+ * @returns
+ */
+export const getIsBetaCardShown = (state: AppState): boolean =>
+ state.ui.appTheming.isBetaCardShown;
diff --git a/app/client/src/selectors/appViewSelectors.tsx b/app/client/src/selectors/appViewSelectors.tsx
index 20543ffcb7..324af8efa3 100644
--- a/app/client/src/selectors/appViewSelectors.tsx
+++ b/app/client/src/selectors/appViewSelectors.tsx
@@ -2,6 +2,7 @@ import { createSelector } from "reselect";
import { AppState } from "reducers";
import { AppViewReduxState } from "reducers/uiReducers/appViewReducer";
import { PageListReduxState } from "reducers/entityReducers/pageListReducer";
+import { builderURL } from "RouteBuilder";
const getAppViewState = (state: AppState) => state.ui.appView;
const getPageListState = (state: AppState): PageListReduxState =>
@@ -35,3 +36,24 @@ export const getCurrentDSLPageId = createSelector(
getPageListState,
(pageList: PageListReduxState) => pageList.currentPageId,
);
+
+export const getEditorURL = createSelector(
+ getPageListState,
+ (pageList: PageListReduxState) =>
+ pageList.applicationId && pageList.currentPageId
+ ? builderURL({
+ applicationId: pageList.applicationId,
+ pageId: pageList.currentPageId,
+ })
+ : "",
+);
+
+/**
+ * returns the height of header in app view mode
+ *
+ * @param state
+ * @returns
+ */
+export const getAppViewHeaderHeight = (state: AppState) => {
+ return state.ui.appView.headerHeight;
+};
diff --git a/app/client/src/selectors/dataTreeSelectors.ts b/app/client/src/selectors/dataTreeSelectors.ts
index a5e349e644..0f7ee986f9 100644
--- a/app/client/src/selectors/dataTreeSelectors.ts
+++ b/app/client/src/selectors/dataTreeSelectors.ts
@@ -12,6 +12,7 @@ import { getWidgets, getWidgetsMeta } from "sagas/selectors";
import "url-search-params-polyfill";
import { getPageList } from "./appViewSelectors";
import { AppState } from "reducers";
+import { getSelectedAppThemeProperties } from "./appThemingSelectors";
export const getUnevaluatedDataTree = createSelector(
getActionsForCurrentPage,
@@ -22,6 +23,7 @@ export const getUnevaluatedDataTree = createSelector(
getAppData,
getPluginEditorConfigs,
getPluginDependencyConfig,
+ getSelectedAppThemeProperties,
(
actions,
jsActions,
@@ -31,6 +33,7 @@ export const getUnevaluatedDataTree = createSelector(
appData,
editorConfigs,
pluginDependencyConfig,
+ selectedAppThemeProperty,
) => {
const pageList = pageListPayload || [];
return DataTreeFactory.create({
@@ -42,6 +45,7 @@ export const getUnevaluatedDataTree = createSelector(
appData,
editorConfigs,
pluginDependencyConfig,
+ theme: selectedAppThemeProperty,
});
},
);
diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx
index 25d38d412d..3b3b747fca 100644
--- a/app/client/src/selectors/editorSelectors.tsx
+++ b/app/client/src/selectors/editorSelectors.tsx
@@ -65,6 +65,7 @@ export const getIsPageSaving = (state: AppState) => {
const savingApis = state.ui.apiPane.isSaving;
const savingJSObjects = state.ui.jsPane.isSaving;
+ const isSavingAppTheme = state.ui.appTheming.isSaving;
Object.keys(savingApis).forEach((apiId) => {
areApisSaving = savingApis[apiId] || areApisSaving;
@@ -78,6 +79,7 @@ export const getIsPageSaving = (state: AppState) => {
state.ui.editor.loadingStates.saving ||
areApisSaving ||
areJsObjectsSaving ||
+ isSavingAppTheme ||
state.ui.editor.loadingStates.savingEntity
);
};
diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx
index 127cfad5f8..19dee3ace4 100644
--- a/app/client/src/utils/AnalyticsUtil.tsx
+++ b/app/client/src/utils/AnalyticsUtil.tsx
@@ -218,6 +218,12 @@ export type EventName =
| "DEFAULT_CONFIGURATION_CHECKBOX_TOGGLED"
| "CONNECT_BUTTON_ON_GIT_SYNC_MODAL_CLICK"
| "DATASOURCE_AUTH_COMPLETE"
+ | "APP_THEMING_CHOOSE_THEME"
+ | "APP_THEMING_APPLY_THEME"
+ | "APP_THEMING_CUSTOMIZE_THEME"
+ | "APP_THEMING_SAVE_THEME_START"
+ | "APP_THEMING_SAVE_THEME_SUCCESS"
+ | "APP_THEMING_DELETE_THEME"
| "RECONNECTING_DATASOURCE_ITEM_CLICK"
| "ADD_MISSING_DATASOURCE_LINK_CLICK"
| "RECONNECTING_SKIP_TO_APPLICATION_BUTTON_CLICK"
diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts
index 1948e81109..2e020eff41 100644
--- a/app/client/src/utils/DSLMigrations.ts
+++ b/app/client/src/utils/DSLMigrations.ts
@@ -51,6 +51,8 @@ import { migrateMapWidgetIsClickedMarkerCentered } from "./migrations/MapWidget"
import { DSLWidget } from "widgets/constants";
import { migrateRecaptchaType } from "./migrations/ButtonWidgetMigrations";
import { PrivateWidgets } from "entities/DataTree/dataTreeFactory";
+import { migrateStylingPropertiesForTheming } from "./migrations/ThemingMigrations";
+
import {
migratePhoneInputWidgetAllowFormatting,
migratePhoneInputWidgetDefaultDialCode,
@@ -1085,6 +1087,11 @@ export const transformDSL = (
if (currentDSL.version === 56) {
currentDSL = migrateRadioGroupAlignmentProperty(currentDSL);
+ currentDSL.version = 57;
+ }
+
+ if (currentDSL.version === 57) {
+ currentDSL = migrateStylingPropertiesForTheming(currentDSL);
currentDSL.version = LATEST_PAGE_VERSION;
}
diff --git a/app/client/src/utils/DSLMigrationsUtils.test.ts b/app/client/src/utils/DSLMigrationsUtils.test.ts
index 6913522496..21e6bf556b 100644
--- a/app/client/src/utils/DSLMigrationsUtils.test.ts
+++ b/app/client/src/utils/DSLMigrationsUtils.test.ts
@@ -6,8 +6,1225 @@ import { OverflowTypes } from "widgets/TextWidget/constants";
import { migrateRadioGroupAlignmentProperty } from "./migrations/RadioGroupWidget";
describe("correctly migrate dsl", () => {
- it("AddsPrivateWidgetsToAllListWidgets", () => {
- const currentVersion = 49;
+ it("transformDSL for private widget", () => {
+ const currentVersion = 49; // before adding privateWidgets to all List widgets
+ const nextVersion = LATEST_PAGE_VERSION; // It runs Two Migrations, Always Update as migration increases
+ const currentDSL: ContainerWidgetProps = {
+ backgroundColor: "none",
+ bottomRow: 740,
+ canExtend: true,
+ children: [
+ {
+ widgetName: "Input1",
+ displayName: "Input",
+ iconSVG: "/static/media/icon.9f505595.svg",
+ topRow: 18,
+ bottomRow: 22,
+ parentRowSpace: 10,
+ autoFocus: false,
+ type: "INPUT_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ parentColumnSpace: 15.0625,
+ dynamicTriggerPathList: [],
+ resetOnSubmit: true,
+ leftColumn: 23,
+ dynamicBindingPathList: [],
+ labelStyle: "",
+ inputType: "TEXT",
+ isDisabled: false,
+ key: "ftefjorusw",
+ isRequired: false,
+ rightColumn: 43,
+ widgetId: "lz9hvhcltl",
+ isVisible: true,
+ label: "",
+ allowCurrencyChange: false,
+ version: 1,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ iconAlign: "left",
+ defaultText: "",
+ },
+ {
+ widgetName: "Button1",
+ onClick:
+ '{{Api1.run(()=>{\ndownload((\nfunction(){\nreturn "data:image/jpeg;base64,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"\n}\n)(), "test.png", "image/png")\n})}}',
+ buttonColor: "#03B365",
+ dynamicPropertyPathList: [
+ {
+ key: "onClick",
+ },
+ ],
+ displayName: "Button",
+ iconSVG: "/static/media/icon.cca02633.svg",
+ topRow: 29,
+ bottomRow: 33,
+ tooltip: "",
+ parentRowSpace: 10,
+ type: "BUTTON_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ parentColumnSpace: 14.0625,
+ dynamicTriggerPathList: [
+ {
+ key: "onClick",
+ },
+ ],
+ leftColumn: 20,
+ dynamicBindingPathList: [],
+ text: "Submit",
+ isDisabled: false,
+ key: "pg01cxraj1",
+ rightColumn: 36,
+ isDefaultClickDisabled: true,
+ widgetId: "d229q1ydul",
+ isVisible: true,
+ recaptchaType: "V3",
+ version: 1,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ buttonVariant: "PRIMARY",
+ placement: "CENTER",
+ },
+ {
+ widgetName: "Input2",
+ displayName: "Input",
+ iconSVG: "/static/media/icon.9f505595.svg",
+ topRow: 44,
+ bottomRow: 48,
+ parentRowSpace: 10,
+ autoFocus: false,
+ type: "INPUT_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ parentColumnSpace: 14.0625,
+ resetOnSubmit: true,
+ leftColumn: 9,
+ labelStyle: "",
+ inputType: "TEXT",
+ isDisabled: false,
+ key: "519sr07k1u",
+ isRequired: false,
+ rightColumn: 29,
+ widgetId: "eenq4c022d",
+ isVisible: true,
+ label: "",
+ allowCurrencyChange: false,
+ version: 1,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ iconAlign: "left",
+ defaultText: "",
+ },
+ {
+ version: 1,
+ template: {
+ Image1: {
+ isVisible: true,
+ defaultImage: "https://assets.appsmith.com/widgets/default.png",
+ imageShape: "RECTANGLE",
+ maxZoomLevel: 1,
+ enableRotation: false,
+ enableDownload: false,
+ objectFit: "contain",
+ image: "{{List1.listData.map((currentItem) => currentItem.img)}}",
+ widgetName: "Image1",
+ version: 1,
+ animateLoading: true,
+ type: "IMAGE_WIDGET",
+ hideCard: false,
+ displayName: "Image",
+ key: "9cn4ooadxj",
+ iconSVG: "/static/media/icon.52d8fb96.svg",
+ dynamicBindingPathList: [
+ {
+ key: "image",
+ },
+ ],
+ dynamicTriggerPathList: [],
+ widgetId: "yqofym38tn",
+ renderMode: "CANVAS",
+ isLoading: false,
+ leftColumn: 0,
+ rightColumn: 16,
+ topRow: 0,
+ bottomRow: 8.4,
+ parentId: "vqn2okwc6a",
+ },
+ Text1: {
+ isVisible: true,
+ text: "{{List1.listData.map((currentItem) => currentItem.name)}}",
+ fontSize: "PARAGRAPH",
+ fontStyle: "BOLD",
+ textAlign: "LEFT",
+ textColor: "#231F20",
+ truncateButtonColor: "#FFC13D",
+ widgetName: "Text1",
+ shouldScroll: false,
+ shouldTruncate: false,
+ version: 1,
+ animateLoading: true,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ displayName: "Text",
+ key: "yd217bk315",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ textStyle: "HEADING",
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ dynamicTriggerPathList: [],
+ widgetId: "zeqf6yfm3s",
+ renderMode: "CANVAS",
+ isLoading: false,
+ leftColumn: 16,
+ rightColumn: 28,
+ topRow: 0,
+ bottomRow: 4,
+ parentId: "vqn2okwc6a",
+ },
+ Text2: {
+ isVisible: true,
+ text: "{{List1.listData.map((currentItem) => currentItem.id)}}",
+ fontSize: "PARAGRAPH",
+ fontStyle: "BOLD",
+ textAlign: "LEFT",
+ textColor: "#231F20",
+ truncateButtonColor: "#FFC13D",
+ widgetName: "Text2",
+ shouldScroll: false,
+ shouldTruncate: false,
+ version: 1,
+ animateLoading: true,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ displayName: "Text",
+ key: "yd217bk315",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ textStyle: "BODY",
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ dynamicTriggerPathList: [],
+ widgetId: "8wyekp2o6e",
+ renderMode: "CANVAS",
+ isLoading: false,
+ leftColumn: 16,
+ rightColumn: 24,
+ topRow: 4,
+ bottomRow: 8,
+ parentId: "vqn2okwc6a",
+ },
+ },
+ widgetName: "List1",
+ listData: [
+ {
+ id: "001",
+ name: "Blue",
+ img: "https://assets.appsmith.com/widgets/default.png",
+ },
+ {
+ id: "002",
+ name: "Green",
+ img: "https://assets.appsmith.com/widgets/default.png",
+ },
+ {
+ id: "003",
+ name: "Red",
+ img: "https://assets.appsmith.com/widgets/default.png",
+ },
+ ],
+ isCanvas: true,
+ displayName: "List",
+ iconSVG: "/static/media/icon.9925ee17.svg",
+ topRow: 34,
+ bottomRow: 74,
+ parentRowSpace: 10,
+ type: "LIST_WIDGET",
+ hideCard: false,
+ gridGap: 0,
+ animateLoading: true,
+ parentColumnSpace: 14.0625,
+ leftColumn: 39,
+ dynamicBindingPathList: [
+ {
+ key: "template.Image1.image",
+ },
+ {
+ key: "template.Text1.text",
+ },
+ {
+ key: "template.Text2.text",
+ },
+ ],
+ gridType: "vertical",
+ enhancements: true,
+ children: [
+ {
+ widgetName: "Canvas1",
+ displayName: "Canvas",
+ topRow: 0,
+ bottomRow: 400,
+ parentRowSpace: 1,
+ type: "CANVAS_WIDGET",
+ canExtend: false,
+ hideCard: true,
+ dropDisabled: true,
+ openParentPropertyPane: true,
+ minHeight: 400,
+ noPad: true,
+ parentColumnSpace: 1,
+ leftColumn: 0,
+ children: [
+ {
+ boxShadow: "NONE",
+ widgetName: "Container1",
+ borderColor: "transparent",
+ disallowCopy: true,
+ isCanvas: true,
+ displayName: "Container",
+ iconSVG: "/static/media/icon.1977dca3.svg",
+ topRow: 0,
+ bottomRow: 12,
+ dragDisabled: true,
+ type: "CONTAINER_WIDGET",
+ hideCard: false,
+ openParentPropertyPane: true,
+ isDeletable: false,
+ animateLoading: true,
+ leftColumn: 0,
+ children: [
+ {
+ widgetName: "Canvas2",
+ detachFromLayout: true,
+ displayName: "Canvas",
+ widgetId: "vqn2okwc6a",
+ containerStyle: "none",
+ topRow: 0,
+ parentRowSpace: 1,
+ isVisible: true,
+ type: "CANVAS_WIDGET",
+ canExtend: false,
+ version: 1,
+ hideCard: true,
+ parentId: "9e77epyavg",
+ renderMode: "CANVAS",
+ isLoading: false,
+ parentColumnSpace: 1,
+ leftColumn: 0,
+ children: [
+ {
+ widgetName: "Image1",
+ displayName: "Image",
+ iconSVG: "/static/media/icon.52d8fb96.svg",
+ topRow: 0,
+ bottomRow: 8.4,
+ type: "IMAGE_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ dynamicTriggerPathList: [],
+ imageShape: "RECTANGLE",
+ dynamicBindingPathList: [
+ {
+ key: "image",
+ },
+ ],
+ leftColumn: 0,
+ defaultImage:
+ "https://assets.appsmith.com/widgets/default.png",
+ key: "9cn4ooadxj",
+ image: "{{currentItem.img}}",
+ rightColumn: 16,
+ objectFit: "contain",
+ widgetId: "yqofym38tn",
+ logBlackList: {
+ isVisible: true,
+ defaultImage: true,
+ imageShape: true,
+ maxZoomLevel: true,
+ enableRotation: true,
+ enableDownload: true,
+ objectFit: true,
+ image: true,
+ widgetName: true,
+ version: true,
+ animateLoading: true,
+ type: true,
+ hideCard: true,
+ displayName: true,
+ key: true,
+ iconSVG: true,
+ isCanvas: true,
+ dynamicBindingPathList: true,
+ dynamicTriggerPathList: true,
+ minHeight: true,
+ widgetId: true,
+ renderMode: true,
+ isLoading: true,
+ parentColumnSpace: true,
+ parentRowSpace: true,
+ leftColumn: true,
+ rightColumn: true,
+ topRow: true,
+ bottomRow: true,
+ parentId: true,
+ },
+ isVisible: true,
+ version: 1,
+ parentId: "vqn2okwc6a",
+ renderMode: "CANVAS",
+ isLoading: false,
+ maxZoomLevel: 1,
+ enableDownload: false,
+ enableRotation: false,
+ },
+ {
+ widgetName: "Text1",
+ displayName: "Text",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ topRow: 0,
+ bottomRow: 4,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ animateLoading: true,
+ dynamicTriggerPathList: [],
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ leftColumn: 16,
+ shouldTruncate: false,
+ truncateButtonColor: "#FFC13D",
+ text: "{{currentItem.name}}",
+ key: "yd217bk315",
+ rightColumn: 28,
+ textAlign: "LEFT",
+ widgetId: "zeqf6yfm3s",
+ logBlackList: {
+ isVisible: true,
+ text: true,
+ fontSize: true,
+ fontStyle: true,
+ textAlign: true,
+ textColor: true,
+ truncateButtonColor: true,
+ widgetName: true,
+ shouldScroll: true,
+ shouldTruncate: true,
+ version: true,
+ animateLoading: true,
+ type: true,
+ hideCard: true,
+ displayName: true,
+ key: true,
+ iconSVG: true,
+ isCanvas: true,
+ textStyle: true,
+ dynamicBindingPathList: true,
+ dynamicTriggerPathList: true,
+ minHeight: true,
+ widgetId: true,
+ renderMode: true,
+ isLoading: true,
+ parentColumnSpace: true,
+ parentRowSpace: true,
+ leftColumn: true,
+ rightColumn: true,
+ topRow: true,
+ bottomRow: true,
+ parentId: true,
+ },
+ isVisible: true,
+ fontStyle: "BOLD",
+ textColor: "#231F20",
+ shouldScroll: false,
+ version: 1,
+ parentId: "vqn2okwc6a",
+ renderMode: "CANVAS",
+ isLoading: false,
+ fontSize: "PARAGRAPH",
+ textStyle: "HEADING",
+ },
+ {
+ widgetName: "Text2",
+ displayName: "Text",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ topRow: 4,
+ bottomRow: 8,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ animateLoading: true,
+ dynamicTriggerPathList: [],
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ leftColumn: 16,
+ shouldTruncate: false,
+ truncateButtonColor: "#FFC13D",
+ text: "{{currentItem.id}}",
+ key: "yd217bk315",
+ rightColumn: 24,
+ textAlign: "LEFT",
+ widgetId: "8wyekp2o6e",
+ logBlackList: {
+ isVisible: true,
+ text: true,
+ fontSize: true,
+ fontStyle: true,
+ textAlign: true,
+ textColor: true,
+ truncateButtonColor: true,
+ widgetName: true,
+ shouldScroll: true,
+ shouldTruncate: true,
+ version: true,
+ animateLoading: true,
+ type: true,
+ hideCard: true,
+ displayName: true,
+ key: true,
+ iconSVG: true,
+ isCanvas: true,
+ textStyle: true,
+ dynamicBindingPathList: true,
+ dynamicTriggerPathList: true,
+ minHeight: true,
+ widgetId: true,
+ renderMode: true,
+ isLoading: true,
+ parentColumnSpace: true,
+ parentRowSpace: true,
+ leftColumn: true,
+ rightColumn: true,
+ topRow: true,
+ bottomRow: true,
+ parentId: true,
+ },
+ isVisible: true,
+ fontStyle: "BOLD",
+ textColor: "#231F20",
+ shouldScroll: false,
+ version: 1,
+ parentId: "vqn2okwc6a",
+ renderMode: "CANVAS",
+ isLoading: false,
+ fontSize: "PARAGRAPH",
+ textStyle: "BODY",
+ },
+ ],
+ key: "omhgz5cakp",
+ },
+ ],
+ borderWidth: "0",
+ key: "ca3a42k2a4",
+ disablePropertyPane: true,
+ backgroundColor: "white",
+ rightColumn: 64,
+ widgetId: "9e77epyavg",
+ containerStyle: "card",
+ isVisible: true,
+ version: 1,
+ parentId: "q3ype57cdo",
+ renderMode: "CANVAS",
+ isLoading: false,
+ borderRadius: "0",
+ },
+ ],
+ key: "omhgz5cakp",
+ rightColumn: 337.5,
+ detachFromLayout: true,
+ widgetId: "q3ype57cdo",
+ containerStyle: "none",
+ isVisible: true,
+ version: 1,
+ parentId: "iupz1d99ka",
+ renderMode: "CANVAS",
+ isLoading: false,
+ },
+ ],
+ key: "axex98spx3",
+ backgroundColor: "transparent",
+ rightColumn: 63,
+ itemBackgroundColor: "#FFFFFF",
+ widgetId: "iupz1d99ka",
+ isVisible: true,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ },
+ ],
+ containerStyle: "none",
+ detachFromLayout: true,
+ dynamicBindingPathList: [],
+ dynamicTriggerPathList: [],
+ leftColumn: 0,
+ minHeight: 640,
+ parentColumnSpace: 1,
+ parentRowSpace: 1,
+ rightColumn: 912,
+ snapColumns: 64,
+ snapRows: 125,
+ topRow: 0,
+ type: "CANVAS_WIDGET",
+ version: currentVersion,
+ widgetId: "0",
+ widgetName: "MainContainer",
+ renderMode: "CANVAS",
+ isLoading: false,
+ };
+
+ const expectedNextDSL: ContainerWidgetProps = {
+ backgroundColor: "none",
+ bottomRow: 740,
+ canExtend: true,
+ version: nextVersion,
+ children: [
+ {
+ widgetName: "Input1",
+ displayName: "Input",
+ iconSVG: "/static/media/icon.9f505595.svg",
+ topRow: 18,
+ bottomRow: 22,
+ parentRowSpace: 10,
+ autoFocus: false,
+ type: "INPUT_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ parentColumnSpace: 15.0625,
+ dynamicTriggerPathList: [],
+ resetOnSubmit: true,
+ leftColumn: 23,
+ labelTextSize: "0.875rem",
+ dynamicBindingPathList: [
+ {
+ key: "accentColor",
+ },
+ ],
+ labelStyle: "",
+ inputType: "TEXT",
+ isDisabled: false,
+ key: "ftefjorusw",
+ isRequired: false,
+ rightColumn: 43,
+ widgetId: "lz9hvhcltl",
+ isVisible: true,
+ label: "",
+ allowCurrencyChange: false,
+ version: 1,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ iconAlign: "left",
+ defaultText: "",
+ borderRadius: "0px",
+ boxShadow: "none",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
+ },
+ {
+ widgetName: "Button1",
+ onClick:
+ '{{Api1.run(()=>{\ndownload((\nfunction(){\nreturn "data:image/jpeg;base64,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"\n}\n)(), "test.png", "image/png")\n})}}',
+ buttonColor: "#03B365",
+ dynamicPropertyPathList: [
+ {
+ key: "onClick",
+ },
+ ],
+ displayName: "Button",
+ iconSVG: "/static/media/icon.cca02633.svg",
+ topRow: 29,
+ bottomRow: 33,
+ tooltip: "",
+ parentRowSpace: 10,
+ type: "BUTTON_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ parentColumnSpace: 14.0625,
+ dynamicTriggerPathList: [
+ {
+ key: "onClick",
+ },
+ ],
+ leftColumn: 20,
+ dynamicBindingPathList: [],
+ text: "Submit",
+ isDisabled: false,
+ key: "pg01cxraj1",
+ labelTextSize: "0.875rem",
+ rightColumn: 36,
+ isDefaultClickDisabled: true,
+ widgetId: "d229q1ydul",
+ isVisible: true,
+ recaptchaType: "V3",
+ version: 1,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ buttonVariant: "PRIMARY",
+ placement: "CENTER",
+ borderRadius: "0px",
+ boxShadow: "none",
+ },
+ {
+ widgetName: "Input2",
+ displayName: "Input",
+ iconSVG: "/static/media/icon.9f505595.svg",
+ topRow: 44,
+ bottomRow: 48,
+ parentRowSpace: 10,
+ autoFocus: false,
+ type: "INPUT_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ parentColumnSpace: 14.0625,
+ resetOnSubmit: true,
+ leftColumn: 9,
+ labelTextSize: "0.875rem",
+ labelStyle: "",
+ inputType: "TEXT",
+ isDisabled: false,
+ key: "519sr07k1u",
+ isRequired: false,
+ rightColumn: 29,
+ widgetId: "eenq4c022d",
+ isVisible: true,
+ label: "",
+ allowCurrencyChange: false,
+ version: 1,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ iconAlign: "left",
+ defaultText: "",
+ borderRadius: "0px",
+ boxShadow: "none",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
+ dynamicBindingPathList: [
+ {
+ key: "accentColor",
+ },
+ ],
+ },
+ {
+ widgetName: "List1",
+ template: {
+ Image1: {
+ isVisible: true,
+ defaultImage: "https://assets.appsmith.com/widgets/default.png",
+ imageShape: "RECTANGLE",
+ maxZoomLevel: 1,
+ enableRotation: false,
+ enableDownload: false,
+ objectFit: "contain",
+ image: "{{List1.listData.map((currentItem) => currentItem.img)}}",
+ widgetName: "Image1",
+ version: 1,
+ animateLoading: true,
+ type: "IMAGE_WIDGET",
+ hideCard: false,
+ displayName: "Image",
+ key: "9cn4ooadxj",
+ iconSVG: "/static/media/icon.52d8fb96.svg",
+ dynamicBindingPathList: [
+ {
+ key: "image",
+ },
+ ],
+ dynamicTriggerPathList: [],
+ widgetId: "yqofym38tn",
+ renderMode: "CANVAS",
+ isLoading: false,
+ leftColumn: 0,
+ rightColumn: 16,
+ topRow: 0,
+ bottomRow: 8.4,
+ parentId: "vqn2okwc6a",
+ },
+ Text1: {
+ isVisible: true,
+ text: "{{List1.listData.map((currentItem) => currentItem.name)}}",
+ fontSize: "PARAGRAPH",
+ fontStyle: "BOLD",
+ textAlign: "LEFT",
+ textColor: "#231F20",
+ truncateButtonColor: "#FFC13D",
+ widgetName: "Text1",
+ shouldScroll: false,
+ shouldTruncate: false,
+ version: 1,
+ animateLoading: true,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ displayName: "Text",
+ key: "yd217bk315",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ textStyle: "HEADING",
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ dynamicTriggerPathList: [],
+ widgetId: "zeqf6yfm3s",
+ renderMode: "CANVAS",
+ isLoading: false,
+ leftColumn: 16,
+ rightColumn: 28,
+ topRow: 0,
+ bottomRow: 4,
+ parentId: "vqn2okwc6a",
+ },
+ Text2: {
+ isVisible: true,
+ text: "{{List1.listData.map((currentItem) => currentItem.id)}}",
+ fontSize: "PARAGRAPH",
+ fontStyle: "BOLD",
+ textAlign: "LEFT",
+ textColor: "#231F20",
+ truncateButtonColor: "#FFC13D",
+ widgetName: "Text2",
+ shouldScroll: false,
+ shouldTruncate: false,
+ version: 1,
+ animateLoading: true,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ displayName: "Text",
+ key: "yd217bk315",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ textStyle: "BODY",
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ dynamicTriggerPathList: [],
+ widgetId: "8wyekp2o6e",
+ renderMode: "CANVAS",
+ isLoading: false,
+ leftColumn: 16,
+ rightColumn: 24,
+ topRow: 4,
+ bottomRow: 8,
+ parentId: "vqn2okwc6a",
+ },
+ },
+ listData: [
+ {
+ id: "001",
+ name: "Blue",
+ img: "https://assets.appsmith.com/widgets/default.png",
+ },
+ {
+ id: "002",
+ name: "Green",
+ img: "https://assets.appsmith.com/widgets/default.png",
+ },
+ {
+ id: "003",
+ name: "Red",
+ img: "https://assets.appsmith.com/widgets/default.png",
+ },
+ ],
+ isCanvas: true,
+ displayName: "List",
+ iconSVG: "/static/media/icon.9925ee17.svg",
+ topRow: 34,
+ bottomRow: 74,
+ parentRowSpace: 10,
+ type: "LIST_WIDGET",
+ hideCard: false,
+ gridGap: 0,
+ animateLoading: true,
+ parentColumnSpace: 14.0625,
+ leftColumn: 39,
+ dynamicBindingPathList: [
+ {
+ key: "template.Image1.image",
+ },
+ {
+ key: "template.Text1.text",
+ },
+ {
+ key: "template.Text2.text",
+ },
+ {
+ key: "accentColor",
+ },
+ ],
+ gridType: "vertical",
+ enhancements: true,
+ children: [
+ {
+ widgetName: "Canvas1",
+ displayName: "Canvas",
+ topRow: 0,
+ bottomRow: 400,
+ parentRowSpace: 1,
+ type: "CANVAS_WIDGET",
+ canExtend: false,
+ hideCard: true,
+ dropDisabled: true,
+ openParentPropertyPane: true,
+ minHeight: 400,
+ noPad: true,
+ parentColumnSpace: 1,
+ leftColumn: 0,
+ children: [
+ {
+ boxShadow: "none",
+ widgetName: "Container1",
+ borderColor: "transparent",
+ disallowCopy: true,
+ isCanvas: true,
+ displayName: "Container",
+ iconSVG: "/static/media/icon.1977dca3.svg",
+ dynamicPropertyPathList: [
+ {
+ key: "borderRadius",
+ },
+ ],
+ topRow: 0,
+ bottomRow: 12,
+ dragDisabled: true,
+ type: "CONTAINER_WIDGET",
+ hideCard: false,
+ openParentPropertyPane: true,
+ isDeletable: false,
+ animateLoading: true,
+ leftColumn: 0,
+ children: [
+ {
+ widgetName: "Canvas2",
+ detachFromLayout: true,
+ displayName: "Canvas",
+ widgetId: "vqn2okwc6a",
+ containerStyle: "none",
+ topRow: 0,
+ parentRowSpace: 1,
+ isVisible: true,
+ type: "CANVAS_WIDGET",
+ canExtend: false,
+ version: 1,
+ hideCard: true,
+ parentId: "9e77epyavg",
+ renderMode: "CANVAS",
+ isLoading: false,
+ parentColumnSpace: 1,
+ leftColumn: 0,
+ children: [
+ {
+ widgetName: "Image1",
+ displayName: "Image",
+ iconSVG: "/static/media/icon.52d8fb96.svg",
+ topRow: 0,
+ bottomRow: 8.4,
+ type: "IMAGE_WIDGET",
+ hideCard: false,
+ animateLoading: true,
+ dynamicTriggerPathList: [],
+ imageShape: "RECTANGLE",
+ dynamicBindingPathList: [
+ {
+ key: "image",
+ },
+ ],
+ leftColumn: 0,
+ defaultImage:
+ "https://assets.appsmith.com/widgets/default.png",
+ key: "9cn4ooadxj",
+ labelTextSize: "0.875rem",
+ image: "{{currentItem.img}}",
+ rightColumn: 16,
+ objectFit: "contain",
+ widgetId: "yqofym38tn",
+ logBlackList: {
+ isVisible: true,
+ defaultImage: true,
+ imageShape: true,
+ maxZoomLevel: true,
+ enableRotation: true,
+ enableDownload: true,
+ objectFit: true,
+ image: true,
+ widgetName: true,
+ version: true,
+ animateLoading: true,
+ type: true,
+ hideCard: true,
+ displayName: true,
+ key: true,
+ iconSVG: true,
+ isCanvas: true,
+ dynamicBindingPathList: true,
+ dynamicTriggerPathList: true,
+ minHeight: true,
+ widgetId: true,
+ renderMode: true,
+ isLoading: true,
+ parentColumnSpace: true,
+ parentRowSpace: true,
+ leftColumn: true,
+ rightColumn: true,
+ topRow: true,
+ bottomRow: true,
+ parentId: true,
+ },
+ isVisible: true,
+ version: 1,
+ parentId: "vqn2okwc6a",
+ renderMode: "CANVAS",
+ isLoading: false,
+ maxZoomLevel: 1,
+ enableDownload: false,
+ enableRotation: false,
+ borderRadius: "0px",
+ boxShadow: "none",
+ },
+ {
+ widgetName: "Text1",
+ displayName: "Text",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ topRow: 0,
+ bottomRow: 4,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ animateLoading: true,
+ dynamicTriggerPathList: [],
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ leftColumn: 16,
+ truncateButtonColor: "#FFC13D",
+ text: "{{currentItem.name}}",
+ key: "yd217bk315",
+ labelTextSize: "0.875rem",
+ rightColumn: 28,
+ textAlign: "LEFT",
+ widgetId: "zeqf6yfm3s",
+ logBlackList: {
+ isVisible: true,
+ text: true,
+ fontSize: true,
+ fontStyle: true,
+ textAlign: true,
+ textColor: true,
+ truncateButtonColor: true,
+ widgetName: true,
+ shouldScroll: true,
+ shouldTruncate: true,
+ version: true,
+ animateLoading: true,
+ type: true,
+ hideCard: true,
+ displayName: true,
+ key: true,
+ iconSVG: true,
+ isCanvas: true,
+ textStyle: true,
+ dynamicBindingPathList: true,
+ dynamicTriggerPathList: true,
+ minHeight: true,
+ widgetId: true,
+ renderMode: true,
+ isLoading: true,
+ parentColumnSpace: true,
+ parentRowSpace: true,
+ leftColumn: true,
+ rightColumn: true,
+ topRow: true,
+ bottomRow: true,
+ parentId: true,
+ },
+ isVisible: true,
+ fontStyle: "BOLD",
+ textColor: "#231F20",
+ overflow: OverflowTypes.NONE,
+ version: 1,
+ parentId: "vqn2okwc6a",
+ renderMode: "CANVAS",
+ isLoading: false,
+ fontSize: "0.875rem",
+ textStyle: "HEADING",
+ borderRadius: "0px",
+ boxShadow: "none",
+ },
+ {
+ widgetName: "Text2",
+ displayName: "Text",
+ iconSVG: "/static/media/icon.97c59b52.svg",
+ topRow: 4,
+ bottomRow: 8,
+ type: "TEXT_WIDGET",
+ fontFamily: "System Default",
+ hideCard: false,
+ animateLoading: true,
+ dynamicTriggerPathList: [],
+ dynamicBindingPathList: [
+ {
+ key: "text",
+ },
+ ],
+ leftColumn: 16,
+ truncateButtonColor: "#FFC13D",
+ text: "{{currentItem.id}}",
+ key: "yd217bk315",
+ labelTextSize: "0.875rem",
+ rightColumn: 24,
+ textAlign: "LEFT",
+ widgetId: "8wyekp2o6e",
+ logBlackList: {
+ isVisible: true,
+ text: true,
+ fontSize: true,
+ fontStyle: true,
+ textAlign: true,
+ textColor: true,
+ truncateButtonColor: true,
+ widgetName: true,
+ shouldScroll: true,
+ shouldTruncate: true,
+ version: true,
+ animateLoading: true,
+ type: true,
+ hideCard: true,
+ displayName: true,
+ key: true,
+ iconSVG: true,
+ isCanvas: true,
+ textStyle: true,
+ dynamicBindingPathList: true,
+ dynamicTriggerPathList: true,
+ minHeight: true,
+ widgetId: true,
+ renderMode: true,
+ isLoading: true,
+ parentColumnSpace: true,
+ parentRowSpace: true,
+ leftColumn: true,
+ rightColumn: true,
+ topRow: true,
+ bottomRow: true,
+ parentId: true,
+ },
+ isVisible: true,
+ fontStyle: "BOLD",
+ textColor: "#231F20",
+ overflow: OverflowTypes.NONE,
+ version: 1,
+ parentId: "vqn2okwc6a",
+ renderMode: "CANVAS",
+ isLoading: false,
+ fontSize: "0.875rem",
+ textStyle: "BODY",
+ borderRadius: "0px",
+ boxShadow: "none",
+ },
+ ],
+ key: "omhgz5cakp",
+ labelTextSize: "0.875rem",
+ borderRadius: "0px",
+ boxShadow: "none",
+ },
+ ],
+ borderWidth: "0",
+ key: "ca3a42k2a4",
+ labelTextSize: "0.875rem",
+ disablePropertyPane: true,
+ backgroundColor: "white",
+ rightColumn: 64,
+ widgetId: "9e77epyavg",
+ containerStyle: "card",
+ isVisible: true,
+ version: 1,
+ parentId: "q3ype57cdo",
+ renderMode: "CANVAS",
+ isLoading: false,
+ borderRadius: "0px",
+ },
+ ],
+ key: "omhgz5cakp",
+ labelTextSize: "0.875rem",
+ rightColumn: 337.5,
+ detachFromLayout: true,
+ widgetId: "q3ype57cdo",
+ containerStyle: "none",
+ isVisible: true,
+ version: 1,
+ parentId: "iupz1d99ka",
+ renderMode: "CANVAS",
+ isLoading: false,
+ borderRadius: "0px",
+ boxShadow: "none",
+ },
+ ],
+ privateWidgets: {
+ Image1: true,
+ Text1: true,
+ Text2: true,
+ },
+ key: "axex98spx3",
+ labelTextSize: "0.875rem",
+ backgroundColor: "transparent",
+ rightColumn: 63,
+ itemBackgroundColor: "#FFFFFF",
+ widgetId: "iupz1d99ka",
+ isVisible: true,
+ parentId: "0",
+ renderMode: "CANVAS",
+ isLoading: false,
+ version: 1,
+ borderRadius: "0px",
+ boxShadow: "none",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
+ },
+ ],
+ containerStyle: "none",
+ detachFromLayout: true,
+ dynamicBindingPathList: [],
+ dynamicTriggerPathList: [],
+ leftColumn: 0,
+ minHeight: 640,
+ parentColumnSpace: 1,
+ parentRowSpace: 1,
+ rightColumn: 912,
+ snapColumns: 64,
+ snapRows: 125,
+ topRow: 0,
+ type: "CANVAS_WIDGET",
+ widgetId: "0",
+ widgetName: "MainContainer",
+ renderMode: RenderModes.CANVAS,
+ isLoading: false,
+ };
+
+ const actualNextDsl = transformDSL(currentDSL);
+
+ expect(actualNextDsl).toEqual(expectedNextDSL);
+ });
+
+ it("transformDSL for theming v1", () => {
+ const currentVersion = 53;
const nextVersion = LATEST_PAGE_VERSION;
const currentDSL: ContainerWidgetProps = {
backgroundColor: "none",
@@ -169,6 +1386,7 @@ describe("correctly migrate dsl", () => {
version: 1,
animateLoading: true,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
displayName: "Text",
key: "yd217bk315",
@@ -203,6 +1421,7 @@ describe("correctly migrate dsl", () => {
version: 1,
animateLoading: true,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
displayName: "Text",
key: "yd217bk315",
@@ -393,6 +1612,7 @@ describe("correctly migrate dsl", () => {
topRow: 0,
bottomRow: 4,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
animateLoading: true,
dynamicTriggerPathList: [],
@@ -461,6 +1681,7 @@ describe("correctly migrate dsl", () => {
topRow: 4,
bottomRow: 8,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
animateLoading: true,
dynamicTriggerPathList: [],
@@ -605,11 +1826,16 @@ describe("correctly migrate dsl", () => {
dynamicTriggerPathList: [],
resetOnSubmit: true,
leftColumn: 23,
- dynamicBindingPathList: [],
+ dynamicBindingPathList: [
+ {
+ key: "accentColor",
+ },
+ ],
labelStyle: "",
inputType: "TEXT",
isDisabled: false,
key: "ftefjorusw",
+ labelTextSize: "0.875rem",
isRequired: false,
rightColumn: 43,
widgetId: "lz9hvhcltl",
@@ -622,6 +1848,9 @@ describe("correctly migrate dsl", () => {
isLoading: false,
iconAlign: "left",
defaultText: "",
+ borderRadius: "0px",
+ boxShadow: "none",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
},
{
widgetName: "Button1",
@@ -653,6 +1882,7 @@ describe("correctly migrate dsl", () => {
text: "Submit",
isDisabled: false,
key: "pg01cxraj1",
+ labelTextSize: "0.875rem",
rightColumn: 36,
isDefaultClickDisabled: true,
widgetId: "d229q1ydul",
@@ -664,6 +1894,8 @@ describe("correctly migrate dsl", () => {
isLoading: false,
buttonVariant: "PRIMARY",
placement: "CENTER",
+ borderRadius: "0px",
+ boxShadow: "none",
},
{
widgetName: "Input2",
@@ -683,6 +1915,7 @@ describe("correctly migrate dsl", () => {
inputType: "TEXT",
isDisabled: false,
key: "519sr07k1u",
+ labelTextSize: "0.875rem",
isRequired: false,
rightColumn: 29,
widgetId: "eenq4c022d",
@@ -695,6 +1928,14 @@ describe("correctly migrate dsl", () => {
isLoading: false,
iconAlign: "left",
defaultText: "",
+ borderRadius: "0px",
+ boxShadow: "none",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
+ dynamicBindingPathList: [
+ {
+ key: "accentColor",
+ },
+ ],
},
{
widgetName: "List1",
@@ -745,6 +1986,7 @@ describe("correctly migrate dsl", () => {
version: 1,
animateLoading: true,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
displayName: "Text",
key: "yd217bk315",
@@ -779,6 +2021,7 @@ describe("correctly migrate dsl", () => {
version: 1,
animateLoading: true,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
displayName: "Text",
key: "yd217bk315",
@@ -839,6 +2082,9 @@ describe("correctly migrate dsl", () => {
{
key: "template.Text2.text",
},
+ {
+ key: "accentColor",
+ },
],
gridType: "vertical",
enhancements: true,
@@ -860,13 +2106,18 @@ describe("correctly migrate dsl", () => {
leftColumn: 0,
children: [
{
- boxShadow: "NONE",
+ boxShadow: "none",
widgetName: "Container1",
borderColor: "transparent",
disallowCopy: true,
isCanvas: true,
displayName: "Container",
iconSVG: "/static/media/icon.1977dca3.svg",
+ dynamicPropertyPathList: [
+ {
+ key: "borderRadius",
+ },
+ ],
topRow: 0,
bottomRow: 12,
dragDisabled: true,
@@ -916,6 +2167,7 @@ describe("correctly migrate dsl", () => {
defaultImage:
"https://assets.appsmith.com/widgets/default.png",
key: "9cn4ooadxj",
+ labelTextSize: "0.875rem",
image: "{{currentItem.img}}",
rightColumn: 16,
objectFit: "contain",
@@ -960,6 +2212,8 @@ describe("correctly migrate dsl", () => {
maxZoomLevel: 1,
enableDownload: false,
enableRotation: false,
+ borderRadius: "0px",
+ boxShadow: "none",
},
{
widgetName: "Text1",
@@ -968,6 +2222,7 @@ describe("correctly migrate dsl", () => {
topRow: 0,
bottomRow: 4,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
animateLoading: true,
dynamicTriggerPathList: [],
@@ -980,6 +2235,7 @@ describe("correctly migrate dsl", () => {
truncateButtonColor: "#FFC13D",
text: "{{currentItem.name}}",
key: "yd217bk315",
+ labelTextSize: "0.875rem",
rightColumn: 28,
textAlign: "LEFT",
widgetId: "zeqf6yfm3s",
@@ -1020,13 +2276,15 @@ describe("correctly migrate dsl", () => {
isVisible: true,
fontStyle: "BOLD",
textColor: "#231F20",
- overflow: OverflowTypes.NONE,
version: 1,
parentId: "vqn2okwc6a",
+ overflow: "NONE",
renderMode: "CANVAS",
isLoading: false,
- fontSize: "PARAGRAPH",
+ fontSize: "0.875rem",
textStyle: "HEADING",
+ borderRadius: "0px",
+ boxShadow: "none",
},
{
widgetName: "Text2",
@@ -1035,6 +2293,7 @@ describe("correctly migrate dsl", () => {
topRow: 4,
bottomRow: 8,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
animateLoading: true,
dynamicTriggerPathList: [],
@@ -1047,6 +2306,7 @@ describe("correctly migrate dsl", () => {
truncateButtonColor: "#FFC13D",
text: "{{currentItem.id}}",
key: "yd217bk315",
+ labelTextSize: "0.875rem",
rightColumn: 24,
textAlign: "LEFT",
widgetId: "8wyekp2o6e",
@@ -1087,20 +2347,26 @@ describe("correctly migrate dsl", () => {
isVisible: true,
fontStyle: "BOLD",
textColor: "#231F20",
- overflow: OverflowTypes.NONE,
version: 1,
parentId: "vqn2okwc6a",
+ overflow: "NONE",
renderMode: "CANVAS",
isLoading: false,
- fontSize: "PARAGRAPH",
+ fontSize: "0.875rem",
textStyle: "BODY",
+ borderRadius: "0px",
+ boxShadow: "none",
},
],
key: "omhgz5cakp",
+ labelTextSize: "0.875rem",
+ borderRadius: "0px",
+ boxShadow: "none",
},
],
borderWidth: "0",
key: "ca3a42k2a4",
+ labelTextSize: "0.875rem",
disablePropertyPane: true,
backgroundColor: "white",
rightColumn: 64,
@@ -1111,10 +2377,11 @@ describe("correctly migrate dsl", () => {
parentId: "q3ype57cdo",
renderMode: "CANVAS",
isLoading: false,
- borderRadius: "0",
+ borderRadius: "0px",
},
],
key: "omhgz5cakp",
+ labelTextSize: "0.875rem",
rightColumn: 337.5,
detachFromLayout: true,
widgetId: "q3ype57cdo",
@@ -1124,15 +2391,13 @@ describe("correctly migrate dsl", () => {
parentId: "iupz1d99ka",
renderMode: "CANVAS",
isLoading: false,
+ borderRadius: "0px",
+ boxShadow: "none",
},
],
- privateWidgets: {
- Image1: true,
- Text1: true,
- Text2: true,
- },
key: "axex98spx3",
backgroundColor: "transparent",
+ labelTextSize: "0.875rem",
rightColumn: 63,
itemBackgroundColor: "#FFFFFF",
widgetId: "iupz1d99ka",
@@ -1141,6 +2406,9 @@ describe("correctly migrate dsl", () => {
renderMode: "CANVAS",
isLoading: false,
version: 1,
+ borderRadius: "0px",
+ boxShadow: "none",
+ accentColor: "{{appsmith.theme.colors.primaryColor}}",
},
],
containerStyle: "none",
@@ -1162,7 +2430,7 @@ describe("correctly migrate dsl", () => {
isLoading: false,
};
- const actualNextDsl = transformDSL(currentDSL, false);
+ const actualNextDsl = transformDSL(currentDSL);
expect(actualNextDsl).toEqual(expectedNextDSL);
});
@@ -1367,6 +2635,7 @@ describe("correctly migrate dsl", () => {
topRow: 1,
bottomRow: 5,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
animateLoading: true,
leftColumn: 1.5,
@@ -1682,6 +2951,7 @@ describe("correctly migrate dsl", () => {
topRow: 1,
bottomRow: 5,
type: "TEXT_WIDGET",
+ fontFamily: "System Default",
hideCard: false,
animateLoading: true,
leftColumn: 1.5,
diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts
index 8991b7382b..3a99f35c0e 100644
--- a/app/client/src/utils/DynamicBindingUtils.ts
+++ b/app/client/src/utils/DynamicBindingUtils.ts
@@ -303,6 +303,15 @@ export const isPathADynamicProperty = (
return false;
};
+export const THEME_BINDING_REGEX = /{{.*appsmith\.theme\..*}}/;
+
+export const isThemeBoundProperty = (
+ widget: WidgetProps,
+ path: string,
+): boolean => {
+ return widget && widget[path] && THEME_BINDING_REGEX.test(widget[path]);
+};
+
export const unsafeFunctionForEval = [
"setTimeout",
"fetch",
diff --git a/app/client/src/utils/PropertyControlFactory.tsx b/app/client/src/utils/PropertyControlFactory.tsx
index 7c182b65ae..f775bcf703 100644
--- a/app/client/src/utils/PropertyControlFactory.tsx
+++ b/app/client/src/utils/PropertyControlFactory.tsx
@@ -24,10 +24,14 @@ class PropertyControlFactory {
additionalAutoComplete?: Record>,
hideEvaluatedValue?: boolean,
): JSX.Element {
- let controlBuilder = this.controlMap.get(controlData.controlType);
+ let controlBuilder;
+
if (preferEditor) {
- if (customEditor) controlBuilder = this.controlMap.get(customEditor);
- else controlBuilder = this.controlMap.get("CODE_EDITOR");
+ controlBuilder = customEditor
+ ? this.controlMap.get(customEditor)
+ : this.controlMap.get("CODE_EDITOR");
+ } else {
+ controlBuilder = this.controlMap.get(controlData.controlType);
}
if (controlBuilder) {
diff --git a/app/client/src/utils/helpers.test.ts b/app/client/src/utils/helpers.test.ts
index f2f0d08db9..90dbf85703 100644
--- a/app/client/src/utils/helpers.test.ts
+++ b/app/client/src/utils/helpers.test.ts
@@ -8,9 +8,11 @@ import {
getSubstringBetweenTwoWords,
captureInvalidDynamicBindingPath,
mergeWidgetConfig,
+ extractColorsFromString,
} from "./helpers";
import WidgetFactory from "./WidgetFactory";
import * as Sentry from "@sentry/react";
+import { Colors } from "constants/Colors";
describe("flattenObject test", () => {
it("Check if non nested object is returned correctly", () => {
@@ -546,3 +548,22 @@ describe("#captureInvalidDynamicBindingPath", () => {
);
});
});
+
+describe("#extractColorsFromString", () => {
+ it("Check if the extractColorsFromString returns rgb, rgb, hex color strings", () => {
+ const borderWithHex = `2px solid ${Colors.GREEN}`;
+ const borderWithRgb = "2px solid rgb(0,0,0)";
+ const borderWithRgba = `2px solid ${Colors.BOX_SHADOW_DEFAULT_VARIANT1}`;
+
+ //Check Hex value
+ expect(extractColorsFromString(borderWithHex)[0]).toEqual("#03b365");
+
+ //Check rgba value
+ expect(extractColorsFromString(borderWithRgba)[0]).toEqual(
+ "rgba(0, 0, 0, 0.25)",
+ );
+
+ //Check rgb
+ expect(extractColorsFromString(borderWithRgb)[0]).toEqual("rgb(0,0,0)");
+ });
+});
diff --git a/app/client/src/utils/helpers.tsx b/app/client/src/utils/helpers.tsx
index 7a829a66cb..1d95c7d3f0 100644
--- a/app/client/src/utils/helpers.tsx
+++ b/app/client/src/utils/helpers.tsx
@@ -606,6 +606,28 @@ export function getLogToSentryFromResponse(response?: ApiResponse) {
return response && response?.responseMeta?.status >= 500;
}
+const BLACKLIST_COLORS = ["#ffffff"];
+const HEX_REGEX = /#[0-9a-fA-F]{6}/gi;
+const RGB_REGEX = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/gi;
+
+/**
+ * extract colors from string
+ *
+ * @param text
+ * @returns
+ */
+export function extractColorsFromString(text: string) {
+ const colors = new Set();
+
+ [...(text.match(RGB_REGEX) || []), ...(text.match(HEX_REGEX) || [])]
+ .filter((d) => BLACKLIST_COLORS.indexOf(d.toLowerCase()) === -1)
+ .forEach((color) => {
+ colors.add(color.toLowerCase());
+ });
+
+ return Array.from(colors) as Array;
+}
+
/*
* Function to merge property pane config of a widget
*
diff --git a/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts b/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts
index 11fe68fc5d..091258d4c3 100644
--- a/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts
+++ b/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts
@@ -34,6 +34,7 @@ export const useAllowEditorDragToSelect = () => {
const isCommentMode = useSelector(commentModeSelector);
const isSnipingMode = useSelector(snipingModeSelector);
const isPreviewMode = useSelector(previewModeSelector);
+
return (
!isResizingOrDragging &&
!isDraggingDisabled &&
diff --git a/app/client/src/utils/hooks/useDynamicAppLayout.tsx b/app/client/src/utils/hooks/useDynamicAppLayout.tsx
index 7b5dc02047..c6619beac0 100644
--- a/app/client/src/utils/hooks/useDynamicAppLayout.tsx
+++ b/app/client/src/utils/hooks/useDynamicAppLayout.tsx
@@ -97,8 +97,8 @@ export const useDynamicAppLayout = () => {
calculatedWidth -= propertyPaneWidth;
}
- // if explorer is unpinned or its preview mode, we don't need to subtract the EE width
- if (isExplorerPinned === true && isPreviewMode === false) {
+ // if explorer is closed or its preview mode, we don't need to subtract the EE width
+ if (isExplorerPinned === true && !isPreviewMode) {
const explorerWidth = domEntityExplorer?.clientWidth || 0;
calculatedWidth -= explorerWidth;
@@ -108,10 +108,12 @@ export const useDynamicAppLayout = () => {
case maxWidth < 0:
case appLayout?.type === "FLUID":
case calculatedWidth < maxWidth && calculatedWidth > minWidth:
+ const totalWidthToSubtract = BORDERS_WIDTH + GUTTER_WIDTH;
+ // NOTE: gutter + border width will be only substracted when theme mode and preview mode are off
return (
calculatedWidth -
(appMode === APP_MODE.EDIT && !isPreviewMode
- ? BORDERS_WIDTH + GUTTER_WIDTH
+ ? totalWidthToSubtract
: 0)
);
case calculatedWidth < minWidth:
@@ -168,6 +170,7 @@ export const useDynamicAppLayout = () => {
* - preview mode
* - explorer width
* - explorer is pinned
+ * - theme mode is turned on
*/
useEffect(() => {
resizeToLayout();
diff --git a/app/client/src/utils/hooks/useGoogleFont.tsx b/app/client/src/utils/hooks/useGoogleFont.tsx
new file mode 100644
index 0000000000..0d791f0d97
--- /dev/null
+++ b/app/client/src/utils/hooks/useGoogleFont.tsx
@@ -0,0 +1,31 @@
+import { useEffect, useMemo } from "react";
+import webfontloader from "webfontloader";
+
+export const DEFAULT_FONT_NAME = "System Default";
+
+function useGoogleFont(fontFamily = DEFAULT_FONT_NAME) {
+ useEffect(() => {
+ if (fontFamily !== DEFAULT_FONT_NAME) {
+ webfontloader.load({
+ google: {
+ families: [`${fontFamily}:300,400,500,700`],
+ },
+ });
+ }
+ }, [fontFamily]);
+
+ /**
+ * returns the font to be used for the canvas
+ */
+ const fontFamilyName = useMemo(() => {
+ if (fontFamily === DEFAULT_FONT_NAME) {
+ return "inherit";
+ }
+
+ return fontFamily;
+ }, [fontFamily]);
+
+ return fontFamilyName;
+}
+
+export default useGoogleFont;
diff --git a/app/client/src/utils/hooks/useOnClickOutside.tsx b/app/client/src/utils/hooks/useOnClickOutside.tsx
new file mode 100644
index 0000000000..f5478c5e5d
--- /dev/null
+++ b/app/client/src/utils/hooks/useOnClickOutside.tsx
@@ -0,0 +1,29 @@
+import { useEffect, RefObject } from "react";
+
+type Event = MouseEvent | TouchEvent;
+
+export const useOnClickOutside = (
+ refs: RefObject[],
+ handler: (event: Event) => void,
+) => {
+ useEffect(() => {
+ const listener = (event: Event) => {
+ for (const ref of refs) {
+ const el = ref?.current;
+ if (!el || el.contains((event?.target as Node) || null)) {
+ return;
+ }
+ }
+
+ handler(event); // Call the handler only if the click is outside of the element passed.
+ };
+
+ document.body.addEventListener("mousedown", listener);
+ document.body.addEventListener("touchstart", listener);
+
+ return () => {
+ document.body.removeEventListener("mousedown", listener);
+ document.body.removeEventListener("touchstart", listener);
+ };
+ }, [refs.length, handler]); // Reload only if ref or handler changes
+};
diff --git a/app/client/src/utils/migrations/ThemingMigrations.ts b/app/client/src/utils/migrations/ThemingMigrations.ts
new file mode 100644
index 0000000000..e7b95670f7
--- /dev/null
+++ b/app/client/src/utils/migrations/ThemingMigrations.ts
@@ -0,0 +1,614 @@
+import { ButtonBorderRadiusTypes } from "components/constants";
+import { BoxShadowTypes } from "components/designSystems/appsmith/WidgetStyleContainer";
+import { Colors } from "constants/Colors";
+import {
+ DEFAULT_BOXSHADOW,
+ THEMEING_TEXT_SIZES,
+ THEMING_BORDER_RADIUS,
+} from "constants/ThemeConstants";
+import { TextSizes } from "constants/WidgetConstants";
+import { clone, get, has, set } from "lodash";
+import { isDynamicValue } from "utils/DynamicBindingUtils";
+import { WidgetProps } from "widgets/BaseWidget";
+import {
+ BUTTON_GROUP_CHILD_STYLESHEET,
+ JSON_FORM_WIDGET_CHILD_STYLESHEET,
+ rgbaMigrationConstantV56,
+ TABLE_WIDGET_CHILD_STYLESHEET,
+} from "widgets/constants";
+import { ContainerWidgetProps } from "widgets/ContainerWidget/widget";
+import { ROOT_SCHEMA_KEY } from "widgets/JSONFormWidget/constants";
+import { parseSchemaItem } from "widgets/WidgetUtils";
+
+export const migrateStylingPropertiesForTheming = (
+ currentDSL: ContainerWidgetProps,
+) => {
+ const widgetsWithPrimaryColorProp = [
+ "DATE_PICKER_WIDGET2",
+ "INPUT_WIDGET",
+ "INPUT_WIDGET_V2",
+ "LIST_WIDGET",
+ "MULTI_SELECT_TREE_WIDGET",
+ "DROP_DOWN_WIDGET",
+ "TABS_WIDGET",
+ "SINGLE_SELECT_TREE_WIDGET",
+ "TABLE_WIDGET",
+ "BUTTON_GROUP_WIDGET",
+ "PHONE_INPUT_WIDGET",
+ "CURRENCY_INPUT_WIDGET",
+ "SELECT_WIDGET",
+ "MULTI_SELECT_WIDGET_V2",
+ "MULTI_SELECT_WIDGET",
+ ];
+
+ currentDSL.children = currentDSL.children?.map((child) => {
+ switch (child.borderRadius) {
+ case ButtonBorderRadiusTypes.SHARP:
+ child.borderRadius = THEMING_BORDER_RADIUS.none;
+ break;
+ case ButtonBorderRadiusTypes.ROUNDED:
+ child.borderRadius = THEMING_BORDER_RADIUS.rounded;
+ break;
+ case ButtonBorderRadiusTypes.CIRCLE:
+ child.borderRadius = THEMING_BORDER_RADIUS.circle;
+ addPropertyToDynamicPropertyPathList("borderRadius", child);
+ break;
+ default:
+ if (
+ (child.type === "CONTAINER_WIDGET" ||
+ child.type === "FORM_WIDGET" ||
+ child.type === "JSON_FORM_WIDGET") &&
+ child.borderRadius
+ ) {
+ child.borderRadius = `${child.borderRadius}px`;
+ addPropertyToDynamicPropertyPathList("borderRadius", child);
+ } else {
+ child.borderRadius = THEMING_BORDER_RADIUS.none;
+ }
+ }
+
+ switch (child.boxShadow) {
+ case BoxShadowTypes.VARIANT1:
+ child.boxShadow = `0px 0px 4px 3px ${child.boxShadowColor ||
+ "rgba(0, 0, 0, 0.25)"}`;
+ addPropertyToDynamicPropertyPathList("boxShadow", child);
+ break;
+ case BoxShadowTypes.VARIANT2:
+ child.boxShadow = `3px 3px 4px ${child.boxShadowColor ||
+ "rgba(0, 0, 0, 0.25)"}`;
+ addPropertyToDynamicPropertyPathList("boxShadow", child);
+ break;
+ case BoxShadowTypes.VARIANT3:
+ child.boxShadow = `0px 1px 3px ${child.boxShadowColor ||
+ "rgba(0, 0, 0, 0.25)"}`;
+ addPropertyToDynamicPropertyPathList("boxShadow", child);
+ break;
+ case BoxShadowTypes.VARIANT4:
+ child.boxShadow = `2px 2px 0px ${child.boxShadowColor ||
+ "rgba(0, 0, 0, 0.25)"}`;
+ addPropertyToDynamicPropertyPathList("boxShadow", child);
+ break;
+ case BoxShadowTypes.VARIANT5:
+ child.boxShadow = `-2px -2px 0px ${child.boxShadowColor ||
+ "rgba(0, 0, 0, 0.25)"}`;
+ addPropertyToDynamicPropertyPathList("boxShadow", child);
+ break;
+ default:
+ child.boxShadow = DEFAULT_BOXSHADOW;
+ }
+
+ /**
+ * Migrates the textSize property present at the table level.
+ */
+ if (child.type === "TABLE_WIDGET") {
+ switch (child.textSize) {
+ case TextSizes.PARAGRAPH2:
+ child.textSize = THEMEING_TEXT_SIZES.xs;
+ addPropertyToDynamicPropertyPathList("textSize", child);
+ break;
+ case TextSizes.PARAGRAPH:
+ child.textSize = THEMEING_TEXT_SIZES.sm;
+ break;
+ case TextSizes.HEADING3:
+ child.textSize = THEMEING_TEXT_SIZES.base;
+ break;
+ case TextSizes.HEADING2:
+ child.textSize = THEMEING_TEXT_SIZES.md;
+ addPropertyToDynamicPropertyPathList("textSize", child);
+ break;
+ case TextSizes.HEADING1:
+ child.textSize = THEMEING_TEXT_SIZES.lg;
+ addPropertyToDynamicPropertyPathList("textSize", child);
+ break;
+ default:
+ child.textSize = THEMEING_TEXT_SIZES.sm;
+ }
+ if (child.hasOwnProperty("primaryColumns")) {
+ Object.keys(child.primaryColumns).forEach((key: string) => {
+ /**
+ * Migrates the textSize property present at the primaryColumn and derivedColumn level.
+ */
+ const column = child.primaryColumns[key];
+ const isDerivedColumn =
+ child.hasOwnProperty("derivedColumns") &&
+ key in child.derivedColumns;
+ const derivedColumn = child.derivedColumns[key];
+ switch (column.textSize) {
+ case TextSizes.PARAGRAPH2:
+ column.textSize = THEMEING_TEXT_SIZES.xs;
+ if (isDerivedColumn) {
+ derivedColumn.textSize = THEMEING_TEXT_SIZES.xs;
+ }
+ addPropertyToDynamicPropertyPathList(
+ `primaryColumns.${key}.textSize`,
+ child,
+ );
+ break;
+ case TextSizes.PARAGRAPH:
+ column.textSize = THEMEING_TEXT_SIZES.sm;
+ if (isDerivedColumn) {
+ derivedColumn.textSize = THEMEING_TEXT_SIZES.sm;
+ }
+ break;
+ case TextSizes.HEADING3:
+ column.textSize = THEMEING_TEXT_SIZES.base;
+ if (isDerivedColumn) {
+ derivedColumn.textSize = THEMEING_TEXT_SIZES.base;
+ }
+ break;
+ case TextSizes.HEADING2:
+ column.textSize = THEMEING_TEXT_SIZES.md;
+ if (isDerivedColumn) {
+ derivedColumn.textSize = THEMEING_TEXT_SIZES.md;
+ }
+ addPropertyToDynamicPropertyPathList(
+ `primaryColumns.${key}.textSize`,
+ child,
+ );
+ break;
+ case TextSizes.HEADING1:
+ column.textSize = THEMEING_TEXT_SIZES.lg;
+ if (isDerivedColumn) {
+ derivedColumn.textSize = THEMEING_TEXT_SIZES.lg;
+ }
+ addPropertyToDynamicPropertyPathList(
+ `primaryColumns.${key}.textSize`,
+ child,
+ );
+ break;
+ }
+
+ /**
+ * Migrate the borderRadius if exists for the primary columns and derived columns
+ */
+ if (!column.borderRadius) {
+ column.borderRadius = THEMING_BORDER_RADIUS.none;
+ if (isDerivedColumn) {
+ derivedColumn.borderRadius = THEMING_BORDER_RADIUS.none;
+ }
+ }
+ switch (column.borderRadius) {
+ case ButtonBorderRadiusTypes.SHARP:
+ column.borderRadius = THEMING_BORDER_RADIUS.none;
+ if (isDerivedColumn) {
+ derivedColumn.borderRadius = THEMING_BORDER_RADIUS.none;
+ }
+ break;
+ case ButtonBorderRadiusTypes.ROUNDED:
+ column.borderRadius = THEMING_BORDER_RADIUS.rounded;
+ if (isDerivedColumn) {
+ derivedColumn.borderRadius = THEMING_BORDER_RADIUS.rounded;
+ }
+ break;
+ case ButtonBorderRadiusTypes.CIRCLE:
+ column.borderRadius = THEMING_BORDER_RADIUS.circle;
+ if (isDerivedColumn) {
+ derivedColumn.borderRadius = THEMING_BORDER_RADIUS.circle;
+ }
+ break;
+ }
+
+ /**
+ * Migrate the boxShadow if exists for the primary columns and derived columns:
+ */
+ const isBoxShadowColorDynamic = isDynamicValue(column.boxShadowColor);
+ const newBoxShadowColor =
+ column.boxShadowColor || rgbaMigrationConstantV56;
+
+ if (column.boxShadow) {
+ addPropertyToDynamicPropertyPathList(
+ `primaryColumns.${key}.boxShadow`,
+ child,
+ );
+ } else {
+ column.boxShadow = "none";
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = "none";
+ }
+ }
+
+ switch (column.boxShadow) {
+ case BoxShadowTypes.VARIANT1:
+ if (!isBoxShadowColorDynamic) {
+ // Checks is boxShadowColor is not dynamic
+ column.boxShadow = `0px 0px 4px 3px ${newBoxShadowColor}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `0px 0px 4px 3px ${newBoxShadowColor}`;
+ }
+ delete column.boxShadowColor;
+ } else {
+ // Dynamic
+ column.boxShadow = `0px 0px 4px 3px ${rgbaMigrationConstantV56}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `0px 0px 4px 3px ${rgbaMigrationConstantV56}`;
+ }
+ }
+ break;
+ case BoxShadowTypes.VARIANT2:
+ if (!isBoxShadowColorDynamic) {
+ // Checks is boxShadowColor is not dynamic
+ column.boxShadow = `3px 3px 4px ${newBoxShadowColor}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `3px 3px 4px ${newBoxShadowColor}`;
+ }
+ delete column.boxShadowColor;
+ } else {
+ // Dynamic
+ column.boxShadow = `3px 3px 4px ${rgbaMigrationConstantV56}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `3px 3px 4px ${rgbaMigrationConstantV56}`;
+ }
+ }
+ break;
+ case BoxShadowTypes.VARIANT3:
+ if (!isBoxShadowColorDynamic) {
+ // Checks is boxShadowColor is not dynamic
+ column.boxShadow = `0px 1px 3px ${newBoxShadowColor}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `0px 1px 3px ${newBoxShadowColor}`;
+ }
+ delete column.boxShadowColor;
+ } else {
+ // Dynamic
+ column.boxShadow = `0px 1px 3px ${rgbaMigrationConstantV56}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `0px 1px 3px ${rgbaMigrationConstantV56}`;
+ }
+ }
+ break;
+ case BoxShadowTypes.VARIANT4:
+ if (!isBoxShadowColorDynamic) {
+ // Checks is boxShadowColor is not dynamic
+ column.boxShadow = `2px 2px 0px ${newBoxShadowColor}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `2px 2px 0px ${newBoxShadowColor}`;
+ }
+ delete column.boxShadowColor;
+ } else {
+ column.boxShadow = `2px 2px 0px ${rgbaMigrationConstantV56}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `2px 2px 0px ${rgbaMigrationConstantV56}`;
+ }
+ }
+ break;
+ case BoxShadowTypes.VARIANT5:
+ if (!isBoxShadowColorDynamic) {
+ // Checks is boxShadowColor is not dynamic
+ column.boxShadow = `-2px -2px 0px ${newBoxShadowColor}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `-2px -2px 0px ${newBoxShadowColor}`;
+ }
+ delete column.boxShadowColor;
+ } else {
+ // Dynamic
+ column.boxShadow = `-2px -2px 0px ${rgbaMigrationConstantV56}`;
+ if (isDerivedColumn) {
+ derivedColumn.boxShadow = `-2px -2px 0px ${rgbaMigrationConstantV56}`;
+ }
+ }
+ break;
+ }
+ });
+ }
+ }
+
+ /**
+ * Migrate the parent level properties for JSON Form
+ */
+ if (child.type === "JSON_FORM_WIDGET") {
+ const parentLevelProperties = ["submitButtonStyles", "resetButtonStyles"];
+ parentLevelProperties.forEach((propertyName: string) => {
+ const propertyPathBorderRadius = `${propertyName}.borderRadius`;
+ const propertyPathBoxShadow = `${propertyName}.boxShadow`;
+ const propertyPathBoxShadowColor = `${propertyName}.boxShadowColor`;
+
+ if (has(child, propertyPathBorderRadius)) {
+ const jsonFormBorderRadius = get(child, propertyPathBorderRadius);
+ switch (jsonFormBorderRadius) {
+ case ButtonBorderRadiusTypes.SHARP:
+ set(child, propertyPathBorderRadius, THEMING_BORDER_RADIUS.none);
+ break;
+ case ButtonBorderRadiusTypes.ROUNDED:
+ set(
+ child,
+ propertyPathBorderRadius,
+ THEMING_BORDER_RADIUS.rounded,
+ );
+ break;
+ case ButtonBorderRadiusTypes.CIRCLE:
+ set(
+ child,
+ propertyPathBorderRadius,
+ THEMING_BORDER_RADIUS.circle,
+ );
+ addPropertyToDynamicPropertyPathList(
+ propertyPathBorderRadius,
+ child,
+ );
+ break;
+ default:
+ set(child, propertyPathBorderRadius, THEMING_BORDER_RADIUS.none);
+ }
+ } else {
+ set(child, propertyPathBorderRadius, THEMING_BORDER_RADIUS.none);
+ }
+
+ if (has(child, propertyPathBoxShadow)) {
+ const jsonFormBoxShadow = get(child, propertyPathBoxShadow);
+ const boxShadowColor =
+ (has(child, propertyPathBoxShadowColor) &&
+ get(child, propertyPathBoxShadowColor)) ||
+ "rgba(0, 0, 0, 0.25)";
+ switch (jsonFormBoxShadow) {
+ case BoxShadowTypes.VARIANT1:
+ set(
+ child,
+ propertyPathBoxShadow,
+ `0px 0px 4px 3px ${boxShadowColor}`,
+ );
+ addPropertyToDynamicPropertyPathList(
+ propertyPathBoxShadow,
+ child,
+ );
+ break;
+ case BoxShadowTypes.VARIANT2:
+ set(
+ child,
+ propertyPathBoxShadow,
+ `3px 3px 4px ${boxShadowColor}`,
+ );
+ addPropertyToDynamicPropertyPathList(
+ propertyPathBoxShadow,
+ child,
+ );
+ break;
+ case BoxShadowTypes.VARIANT3:
+ set(
+ child,
+ propertyPathBoxShadow,
+ `0px 1px 3px ${boxShadowColor}`,
+ );
+ addPropertyToDynamicPropertyPathList(
+ propertyPathBoxShadow,
+ child,
+ );
+ break;
+ case BoxShadowTypes.VARIANT4:
+ set(
+ child,
+ propertyPathBoxShadow,
+ `2px 2px 0px ${boxShadowColor}`,
+ );
+ addPropertyToDynamicPropertyPathList(
+ propertyPathBoxShadow,
+ child,
+ );
+ break;
+ case BoxShadowTypes.VARIANT5:
+ set(
+ child,
+ propertyPathBoxShadow,
+ `-2px -2px 0px ${boxShadowColor}`,
+ );
+ addPropertyToDynamicPropertyPathList(
+ propertyPathBoxShadow,
+ child,
+ );
+ break;
+ default:
+ set(child, propertyPathBoxShadow, DEFAULT_BOXSHADOW);
+ }
+ } else {
+ set(child, propertyPathBoxShadow, DEFAULT_BOXSHADOW);
+ }
+ });
+
+ /**
+ * Migrate the children level properties for JSON form
+ */
+ if (has(child, "schema")) {
+ const clonedSchema = clone(child.schema);
+ parseSchemaItem(
+ clonedSchema[ROOT_SCHEMA_KEY],
+ `schema.${ROOT_SCHEMA_KEY}`,
+ (schemaItem, propertyPath) => {
+ if (schemaItem) {
+ switch (schemaItem.labelTextSize) {
+ case TextSizes.PARAGRAPH2:
+ schemaItem.labelTextSize = THEMEING_TEXT_SIZES.xs;
+ addPropertyToDynamicPropertyPathList(
+ `${propertyPath}.labelTextSize`,
+ child,
+ );
+ break;
+ case TextSizes.PARAGRAPH:
+ schemaItem.labelTextSize = THEMEING_TEXT_SIZES.sm;
+ break;
+ case TextSizes.HEADING3:
+ schemaItem.labelTextSize = THEMEING_TEXT_SIZES.base;
+ break;
+ case TextSizes.HEADING2:
+ schemaItem.labelTextSize = THEMEING_TEXT_SIZES.md;
+ addPropertyToDynamicPropertyPathList(
+ `${propertyPath}.labelTextSize`,
+ child,
+ );
+ break;
+ case TextSizes.HEADING1:
+ schemaItem.labelTextSize = THEMEING_TEXT_SIZES.lg;
+ addPropertyToDynamicPropertyPathList(
+ `${propertyPath}.labelTextSize`,
+ child,
+ );
+ break;
+ default:
+ schemaItem.labelTextSize = THEMEING_TEXT_SIZES.sm;
+ }
+
+ // Set the default borderRadius
+ !has(schemaItem, "borderRadius") &&
+ set(schemaItem, "borderRadius", THEMING_BORDER_RADIUS.none);
+ // Set the default borderRadius for the Item styles in an array type:
+ !has(schemaItem, "cellBorderRadius") &&
+ set(schemaItem, "cellBorderRadius", THEMING_BORDER_RADIUS.none);
+
+ // Sets the default value for the boxShadow
+ !has(schemaItem, "boxShadow") &&
+ set(schemaItem, "boxShadow", DEFAULT_BOXSHADOW);
+
+ // Sets the default value for the boxShadow property of Item styles inside an array:
+ !has(schemaItem, "cellBoxShadow") &&
+ set(schemaItem, "cellBoxShadow", DEFAULT_BOXSHADOW);
+
+ // Sets default value as green for the accentColor(Most of the widgets require the below property):
+ !has(schemaItem, "accentColor") &&
+ set(schemaItem, "accentColor", Colors.GREEN);
+ }
+ },
+ );
+
+ child.schema = clonedSchema;
+ }
+ }
+
+ switch (child.fontSize) {
+ case TextSizes.PARAGRAPH2:
+ child.fontSize = THEMEING_TEXT_SIZES.xs;
+ addPropertyToDynamicPropertyPathList("fontSize", child);
+ break;
+ case TextSizes.PARAGRAPH:
+ child.fontSize = THEMEING_TEXT_SIZES.sm;
+ break;
+ case TextSizes.HEADING3:
+ child.fontSize = THEMEING_TEXT_SIZES.base;
+ break;
+ case TextSizes.HEADING2:
+ child.fontSize = THEMEING_TEXT_SIZES.md;
+ addPropertyToDynamicPropertyPathList("fontSize", child);
+ break;
+ case TextSizes.HEADING1:
+ child.fontSize = THEMEING_TEXT_SIZES.lg;
+ addPropertyToDynamicPropertyPathList("fontSize", child);
+ break;
+ }
+
+ switch (child.labelTextSize) {
+ case TextSizes.PARAGRAPH2:
+ child.labelTextSize = THEMEING_TEXT_SIZES.xs;
+ addPropertyToDynamicPropertyPathList("labelTextSize", child);
+ break;
+ case TextSizes.PARAGRAPH:
+ child.labelTextSize = THEMEING_TEXT_SIZES.sm;
+ break;
+ case TextSizes.HEADING3:
+ child.labelTextSize = THEMEING_TEXT_SIZES.base;
+ break;
+ case TextSizes.HEADING2:
+ child.labelTextSize = THEMEING_TEXT_SIZES.md;
+ addPropertyToDynamicPropertyPathList("labelTextSize", child);
+ break;
+ case TextSizes.HEADING1:
+ child.labelTextSize = THEMEING_TEXT_SIZES.lg;
+ addPropertyToDynamicPropertyPathList("labelTextSize", child);
+ break;
+ default:
+ child.labelTextSize = THEMEING_TEXT_SIZES.sm;
+ }
+
+ /**
+ * Add primaryColor color to missing widgets
+ */
+ if (widgetsWithPrimaryColorProp.includes(child.type)) {
+ child.accentColor = "{{appsmith.theme.colors.primaryColor}}";
+
+ child.dynamicBindingPathList = [
+ ...(child.dynamicBindingPathList || []),
+ {
+ key: "accentColor",
+ },
+ ];
+ }
+
+ // specific fixes
+ if (child.type === "AUDIO_RECORDER_WIDGET") {
+ child.borderRadius = THEMING_BORDER_RADIUS.circle;
+ child.accentColor = child.backgroundColor;
+ }
+
+ if (child.type === "FILE_PICKER_WIDGET_V2") {
+ child.buttonColor = Colors.GREEN;
+ }
+
+ if (
+ child.type === "CHECKBOX_WIDGET" ||
+ child.type === "CHECKBOX_GROUP_WIDGET" ||
+ child.type === "SWITCH_WIDGET" ||
+ child.type === "SWITCH_GROUP_WIDGET"
+ ) {
+ child.accentColor = Colors.GREEN;
+ }
+
+ if (child.type === "TEXT_WIDGET") {
+ child.fontFamily = "System Default";
+ }
+ // Adds childStyleSheets
+ switch (child.type) {
+ case "BUTTON_GROUP_WIDGET":
+ child.childStylesheet = BUTTON_GROUP_CHILD_STYLESHEET;
+ break;
+ case "JSON_FORM_WIDGET":
+ child.childStylesheet = JSON_FORM_WIDGET_CHILD_STYLESHEET;
+ break;
+ case "TABLE_WIDGET":
+ child.childStylesheet = TABLE_WIDGET_CHILD_STYLESHEET;
+ break;
+ }
+
+ if (child.children && child.children.length > 0) {
+ child = migrateStylingPropertiesForTheming(child);
+ }
+ return child;
+ });
+
+ return currentDSL;
+};
+
+/**
+ * This function will add the given propertyName into the dynamicPropertyPathList.
+ * @param propertyName
+ * @param child
+ */
+export const addPropertyToDynamicPropertyPathList = (
+ propertyName: string,
+ child: WidgetProps,
+) => {
+ const isPropertyPathPresent = (child.dynamicPropertyPathList || []).find(
+ (property) => property.key === propertyName,
+ );
+ if (!isPropertyPathPresent) {
+ child.dynamicPropertyPathList = [
+ ...(child.dynamicPropertyPathList || []),
+ { key: propertyName },
+ ];
+ }
+};
diff --git a/app/client/src/utils/storage.ts b/app/client/src/utils/storage.ts
index b919b833dc..063fc61492 100644
--- a/app/client/src/utils/storage.ts
+++ b/app/client/src/utils/storage.ts
@@ -2,7 +2,9 @@ import log from "loglevel";
import moment from "moment";
import localforage from "localforage";
-const STORAGE_KEYS: { [id: string]: string } = {
+export const STORAGE_KEYS: {
+ [id: string]: string;
+} = {
AUTH_EXPIRATION: "Auth.expiration",
ROUTE_BEFORE_LOGIN: "RedirectPath",
COPIED_WIDGET: "CopiedWidget",
@@ -18,6 +20,7 @@ const STORAGE_KEYS: { [id: string]: string } = {
FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY:
"FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY",
HIDE_CONCURRENT_EDITOR_WARNING_TOAST: "HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
+ APP_THEMING_BETA_SHOWN: "APP_THEMING_BETA_SHOWN",
};
const store = localforage.createInstance({
@@ -54,6 +57,36 @@ export const saveCopiedWidgets = async (widgetJSON: string) => {
}
};
+const getStoredUsersBetaFlags = (email: any) => {
+ return store.getItem(email);
+};
+
+const setStoredUsersBetaFlags = (email: any, userBetaFlagsObj: any) => {
+ return store.setItem(email, userBetaFlagsObj);
+};
+
+export const setBetaFlag = async (email: any, key: string, value: any) => {
+ const userBetaFlagsObj: any = await getStoredUsersBetaFlags(email);
+ const updatedObj = {
+ ...userBetaFlagsObj,
+ [key]: value,
+ };
+ setStoredUsersBetaFlags(email, updatedObj);
+};
+
+export const getBetaFlag = async (email: any, key: string) => {
+ const userBetaFlagsObj: any = await getStoredUsersBetaFlags(email);
+
+ return userBetaFlagsObj && userBetaFlagsObj[key];
+};
+
+export const getReflowOnBoardingFlag = async (email: any) => {
+ const userBetaFlagsObj: any = await getStoredUsersBetaFlags(email);
+ return (
+ userBetaFlagsObj && userBetaFlagsObj[STORAGE_KEYS.REFLOW_ONBOARDED_FLAG]
+ );
+};
+
export const getCopiedWidgets = async () => {
try {
const widget: string | null = await store.getItem(
diff --git a/app/client/src/widgets/AudioRecorderWidget/component/index.tsx b/app/client/src/widgets/AudioRecorderWidget/component/index.tsx
index 249e69aa0b..52230a66d8 100644
--- a/app/client/src/widgets/AudioRecorderWidget/component/index.tsx
+++ b/app/client/src/widgets/AudioRecorderWidget/component/index.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useMemo, useRef } from "react";
-import styled, { css, keyframes } from "styled-components";
+import styled from "styled-components";
import { Button, Icon } from "@blueprintjs/core";
import { useReactMediaRecorder } from "react-media-recorder";
import { useStopwatch } from "react-timer-hook";
@@ -10,7 +10,7 @@ import { ReactComponent as RecorderPauseIcon } from "assets/icons/widget/recorde
import { ReactComponent as RecorderCompleteIcon } from "assets/icons/widget/recorder/recorder_complete.svg";
import { ReactComponent as RecorderNoPermissionIcon } from "assets/icons/widget/recorder/recorder_no_permission.svg";
import { WIDGET_PADDING } from "constants/WidgetConstants";
-import { hexToRgb, ThemeProp } from "components/ads/common";
+import { ThemeProp } from "components/ads/common";
import { darkenHover } from "constants/DefaultTheme";
import { Colors } from "constants/Colors";
@@ -41,7 +41,6 @@ const RecorderContainer = styled.div`
justify-content: space-evenly;
width: 100%;
height: 100%;
- overflow: auto;
`;
const RightContainer = styled.div`
@@ -67,7 +66,9 @@ const TimerContainer = styled.div`
`;
interface RecorderLeftButtonStyleProps {
- backgroundColor: string;
+ accentColor: string;
+ boxShadow?: string;
+ borderRadius: string;
dimension: number;
disabled: boolean;
iconColor: string;
@@ -75,45 +76,16 @@ interface RecorderLeftButtonStyleProps {
status: RecorderStatus;
}
-const getRgbaColor = (color: string, alpha: number) => {
- const rgb = hexToRgb(color);
-
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
-};
-
-const pulse = (boxShadowColor: string, dimension: number) => {
- return keyframes`
- 0% {
- box-shadow: 0 0 0 0px ${getRgbaColor(boxShadowColor, 0.4)};
- }
- 100% {
- box-shadow: 0 0 0 ${dimension * 0.1}px rgba(0, 0, 0, 0);
- }
-`;
-};
-
-const animation = (props: RecorderLeftButtonStyleProps) => css`
- ${pulse(props.backgroundColor, props.dimension)} 2s infinite
-`;
-
const StyledRecorderLeftButton = styled(Button)<
ThemeProp & RecorderLeftButtonStyleProps
>`
background-image: none !important;
- border-radius: 50%;
+ border-radius: ${({ borderRadius }) => borderRadius};
height: ${({ dimension }) => dimension * 0.8}px;
width: ${({ dimension }) => dimension * 0.8}px;
-
- box-shadow: ${({ backgroundColor, status }) =>
- status === RecorderStatusTypes.RECORDING
- ? `
- 0 0 0 1px 1px ${getRgbaColor(backgroundColor, 0.4)}
- `
- : "none"} !important;
+ box-shadow: ${({ boxShadow }) => `${boxShadow}`} !important;
margin-left: ${({ dimension }) => dimension * 0.1}px;
- animation: ${animation};
-
& > svg {
flex: 1;
height: ${({ status }) =>
@@ -137,13 +109,13 @@ const StyledRecorderLeftButton = styled(Button)<
}
}
- ${({ backgroundColor, permissionDenied, theme }) => `
+ ${({ accentColor, permissionDenied, theme }) => `
&:enabled {
background: ${
- backgroundColor
+ accentColor
? permissionDenied
? theme.colors.button.disabled.bgColor
- : backgroundColor
+ : accentColor
: "none"
} !important;
}
@@ -151,7 +123,7 @@ const StyledRecorderLeftButton = styled(Button)<
background: ${darkenHover(
permissionDenied
? theme.colors.button.disabled.bgColor
- : backgroundColor || "#f6f6f6",
+ : accentColor || "#f6f6f6",
)} !important;
animation: none;
}
@@ -162,7 +134,7 @@ const StyledRecorderLeftButton = styled(Button)<
path, circle {
fill: ${theme.colors.button.disabled.textColor};
}
- }
+ }
}
`}
`;
@@ -191,7 +163,9 @@ const renderRecorderIcon = (
};
interface RecorderLeftProps {
- backgroundColor: string;
+ accentColor: string;
+ borderRadius: string;
+ boxShadow?: string;
dimension: number;
disabled: boolean;
iconColor: string;
@@ -202,7 +176,9 @@ interface RecorderLeftProps {
function RecorderLeft(props: RecorderLeftProps) {
const {
- backgroundColor,
+ accentColor,
+ borderRadius,
+ boxShadow,
denied,
dimension,
disabled,
@@ -217,7 +193,9 @@ function RecorderLeft(props: RecorderLeftProps) {
return (
(
"multiline",
"numeric",
"inputType",
+ "borderRadius",
+ "boxShadow",
+ "accentColor",
])}
/>
))<{
@@ -66,9 +69,30 @@ const InputComponentWrapper = styled((props) => (
inputType: InputType;
compactMode: boolean;
labelPosition: LabelPosition;
+ borderRadius?: string;
+ boxShadow?: string;
+ accentColor?: string;
}>`
${labelLayoutStyles}
+ .${Classes.INPUT_GROUP} {
+ display: flex;
+ background-color: white;
+
+ > {
+
+ &:first-child:not(input) {
+ background: ${(props) =>
+ props.disabled ? Colors.GREY_1 : Colors.WHITE};
+ }
+ input:not(:first-child) {
+ padding-left: 0rem;
+ z-index: 16;
+ line-height: 16px;
+ }
+ }
+ }
+
&&&& {
${({ inputType, labelPosition }) => {
if (!labelPosition && inputType !== InputTypes.TEXT) {
@@ -82,7 +106,7 @@ const InputComponentWrapper = styled((props) => (
return "flex: 1; margin-right: 5px; label { margin-right: 5px; margin-bottom: 0;}";
}
}}
- align-items: flex-start;
+ align-items: centert;
${({ compactMode, labelPosition }) => {
if (!labelPosition && !compactMode) {
return "max-height: 20px; .bp3-popover-wrapper {max-height: 20px}";
@@ -92,108 +116,123 @@ const InputComponentWrapper = styled((props) => (
.currency-type-filter,
.country-type-filter {
width: fit-content;
- height: 36px;
+ height: 100%;
+ position: static;
display: inline-block;
left: 0;
z-index: 16;
- &:hover {
- border: 1px solid ${Colors.GREY_5} !important;
+ svg {
+ path {
+ fill: ${(props) => props.theme.colors.icon?.hover};
+ }
+ }
+ .${Classes.INPUT} {
+ padding-left: 0.5rem;
+ min-height: 36px;
+ box-shadow: none;
+ border: 1px solid;
+ border-radius: 0;
+ height: ${(props) => (props.multiline === "true" ? "100%" : "inherit")};
+ width: 100%;
+ border-color: ${({ hasError }) => {
+ return hasError
+ ? `${Colors.DANGER_SOLID} !important;`
+ : `${Colors.GREY_3};`;
+ }}
+ ${(props) =>
+ props.numeric &&
+ `
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+ ${props.hasError ? "" : "border-right-width: 0px;"}
+ `}
+ &:active {
+ border-color: ${({ hasError }) =>
+ hasError ? Colors.DANGER_SOLID : Colors.HIT_GRAY};
+ }
}
}
+
+ .currency-type-filter .bp3-popover-open > div,
+ .country-type-filter .bp3-popover-open > div {
+ border: 0px solid !important;
+ box-shadow: none !important;
+ }
+
+ .currency-type-filter .bp3-popover-open button
+ .country-type-filter .bp3-popover-open button {
+ border: 0px solid !important;
+ box-shadow: none !important;
+ background: ${Colors.GREY_3};
+ }
+
.${Classes.INPUT} {
- min-height: 36px;
- ${(props) =>
- props.inputType === InputTypes.CURRENCY &&
- props.allowCurrencyChange &&
- `
- padding-left: 45px;`};
- ${(props) =>
- props.inputType === InputTypes.CURRENCY &&
- !props.allowCurrencyChange &&
- `
- padding-left: 35px;`};
- ${(props) =>
- props.inputType === InputTypes.PHONE_NUMBER &&
- `padding-left: 85px;
- `};
+ background: ${Colors.WHITE};
box-shadow: none;
- border: 1px solid;
border-radius: 0;
height: ${(props) => (props.multiline === "true" ? "100%" : "inherit")};
width: 100%;
- border-color: ${({ hasError }) => {
- return hasError
- ? `${Colors.DANGER_SOLID} !important;`
- : `${Colors.GREY_3};`;
- }}
- ${(props) =>
- props.numeric &&
- `
- border-top-right-radius: 0px;
- border-bottom-right-radius: 0px;
- ${props.hasError ? "" : "border-right-width: 0px;"}
- `}
+
${(props) =>
props.inputType === "PASSWORD" &&
`
- & + .bp3-input-action {
- height: 36px;
- width: 36px;
- cursor: pointer;
- padding: 1px;
- .password-input {
- color: ${Colors.GREY_6};
- justify-content: center;
- height: 100%;
- svg {
- width: 20px;
- height: 20px;
- }
- &:hover {
- background-color: ${Colors.GREY_2};
- color: ${Colors.GREY_10};
- }
+ & + .bp3-input-action {
+ height: 100%;
+ width: 36px;
+ cursor: pointer;
+
+ .password-input {
+ color: ${Colors.GREY_6};
+ justify-content: center;
+ height: 100%;
+ svg {
+ width: 20px;
+ height: 20px;
+ }
+ &:hover {
+ background-color: ${Colors.GREY_2};
+ color: ${Colors.GREY_10};
}
}
- `}
- transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
- &:active {
- border-color: ${({ hasError }) =>
- hasError ? Colors.DANGER_SOLID : Colors.HIT_GRAY};
- }
- &:hover {
- border-left: 1px solid ${Colors.GREY_5};
- border-right: 1px solid ${Colors.GREY_5};
- border-color: ${Colors.GREY_5};
- }
- &:focus {
- border-color: ${({ hasError }) =>
- hasError ? Colors.DANGER_SOLID : Colors.MYSTIC};
-
- &:focus {
- outline: 0;
- border: 1px solid ${Colors.GREEN_1};
- box-shadow: 0px 0px 0px 2px ${Colors.GREEN_2} !important;
- }
- }
- &:disabled {
- background-color: ${Colors.GREY_1};
- border: 1.2px solid ${Colors.GREY_3};
- & + .bp3-input-action {
- pointer-events: none;
- }
}
+ `}
}
- .${Classes.INPUT_GROUP} {
- display: block;
+
+ & .${Classes.INPUT_GROUP} {
+ display: flex;
margin: 0;
.bp3-tag {
background-color: transparent;
color: #5c7080;
- margin-top: 8px;
}
+
+ .${Classes.INPUT_ACTION} {
+ height: 100%;
+
+ .${Classes.TAG} {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ .${Classes.ICON} {
+ height: 100%;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ position: relative;
+
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+ }
+
&.${Classes.DISABLED} + .bp3-button-group.bp3-vertical {
- pointer-events: none;
button {
background: ${Colors.GREY_1};
}
@@ -221,60 +260,11 @@ const InputComponentWrapper = styled((props) => (
}
return "flex-start";
}};
-
-
- }
- &&&& .bp3-input-group {
- display: flex;
- > {
- &.bp3-icon:first-child {
- top: 3px;
- }
- input:not(:first-child) {
- line-height: 16px;
-
- &:hover:not(:focus) {
- border-left: 1px solid ${Colors.GREY_5};
- }
- }
- }
-
- ${(props) => {
- if (props.inputType === InputTypes.PHONE_NUMBER) {
- return `
- > {
- input:not(:first-child) {
- padding-left: 10px;
- }
- .currency-type-filter,
- .currency-type-trigger,
- .country-type-filter,
- .country-type-trigger {
- position: static;
- background: rgb(255, 255, 255);
- border-width: 1.2px 0px 1.2px 1.2px;
- border-top-style: solid;
- border-bottom-style: solid;
- border-left-style: solid;
- border-top-color: rgb(235, 235, 235);
- border-bottom-color: rgb(235, 235, 235);
- border-left-color: rgb(235, 235, 235);
- border-image: initial;
- color: rgb(9, 7, 7);
- border-right-style: initial;
- border-right-color: initial;
- }
- }
- `;
- }
- }}
}
`;
const StyledNumericInput = styled(NumericInput)`
&&&& .bp3-button-group.bp3-vertical {
- border: 1.2px solid ${Colors.GREY_3};
- border-left: none;
button {
background: ${Colors.WHITE};
box-shadow: none;
@@ -287,10 +277,6 @@ const StyledNumericInput = styled(NumericInput)`
color: ${Colors.GREY_10};
}
}
- &:focus {
- border: 1px solid ${Colors.GREEN_1};
- box-shadow: 0px 0px 0px 2px ${Colors.GREEN_2};
- }
span {
color: ${Colors.GREY_6};
svg {
@@ -305,11 +291,34 @@ const TextInputWrapper = styled.div<{
inputHtmlType?: InputHTMLType;
compact: boolean;
labelPosition?: LabelPosition;
+ borderRadius?: string;
+ boxShadow?: string;
+ accentColor?: string;
+ hasError?: boolean;
+ disabled?: boolean;
}>`
width: 100%;
display: flex;
flex: 1;
- min-height: 36px;
+ height: 100%;
+ border: 1px solid;
+ overflow: hidden;
+ border-color: ${({ hasError }) =>
+ hasError ? `${Colors.DANGER_SOLID} !important;` : `${Colors.GREY_3};`}
+ border-radius: ${({ borderRadius }) => borderRadius} !important;
+ box-shadow: ${({ boxShadow }) => `${boxShadow}`} !important;
+ min-height: 32px;
+
+ &:focus-within {
+ outline: 0;
+ border-color: ${({ accentColor, hasError }) =>
+ hasError ? Colors.DANGER_SOLID : accentColor};
+ box-shadow: ${({ accentColor, hasError }) =>
+ `0px 0px 0px 3px ${lightenColor(
+ hasError ? Colors.DANGER_SOLID : accentColor,
+ )} !important;`};
+ }
+
${({ inputHtmlType }) =>
inputHtmlType && inputHtmlType !== InputTypes.TEXT && `&&& {flex-grow: 0;}`}
`;
@@ -572,7 +581,7 @@ class BaseInputComponent extends React.Component<
labelPosition={labelPosition}
labelStyle={labelStyle}
labelTextColor={labelTextColor}
- labelTextSize={labelTextSize ? TEXT_SIZES[labelTextSize] : "inherit"}
+ labelTextSize={labelTextSize ?? "inherit"}
multiline={(!!multiline).toString()}
numeric={isNumberInputType(inputHTMLType)}
>
@@ -594,7 +603,11 @@ class BaseInputComponent extends React.Component<
/>
)}
@@ -629,7 +642,7 @@ export interface BaseInputComponentProps extends ComponentProps {
labelPosition?: LabelPosition;
labelWidth?: number;
labelTextColor?: string;
- labelTextSize?: TextSize;
+ labelTextSize?: string;
labelStyle?: string;
tooltip?: string;
leftIcon?: IconName | JSX.Element;
@@ -663,6 +676,9 @@ export interface BaseInputComponentProps extends ComponentProps {
inputRef?: MutableRefObject<
HTMLTextAreaElement | HTMLInputElement | undefined | null
>;
+ borderRadius?: string;
+ boxShadow?: string;
+ accentColor?: string;
}
export default BaseInputComponent;
diff --git a/app/client/src/widgets/BaseInputWidget/index.ts b/app/client/src/widgets/BaseInputWidget/index.ts
index 26476d00a0..51ee367b40 100644
--- a/app/client/src/widgets/BaseInputWidget/index.ts
+++ b/app/client/src/widgets/BaseInputWidget/index.ts
@@ -14,6 +14,7 @@ export const CONFIG = {
label: "Label",
labelPosition: LabelPosition.Left,
labelAlignment: Alignment.LEFT,
+ labelTextSize: "0.875rem",
labelWidth: 5,
columns: 20,
widgetName: "Input",
diff --git a/app/client/src/widgets/BaseInputWidget/widget/index.tsx b/app/client/src/widgets/BaseInputWidget/widget/index.tsx
index 05e1ff0df1..fa0a419f2f 100644
--- a/app/client/src/widgets/BaseInputWidget/widget/index.tsx
+++ b/app/client/src/widgets/BaseInputWidget/widget/index.tsx
@@ -2,7 +2,7 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { Alignment } from "@blueprintjs/core";
import { IconName } from "@blueprintjs/icons";
-import { WidgetType, TextSize } from "constants/WidgetConstants";
+import { WidgetType } from "constants/WidgetConstants";
import {
EventType,
ExecutionResult,
@@ -277,40 +277,43 @@ class BaseInputWidget<
propertyName: "labelTextSize",
label: "Text Size",
controlType: "DROP_DOWN",
+ defaultValue: "0.875rem",
options: [
{
- label: "Heading 1",
- value: "HEADING1",
- subText: "24px",
- icon: "HEADING_ONE",
+ label: "S",
+ value: "0.875rem",
+ subText: "0.875rem",
},
{
- label: "Heading 2",
- value: "HEADING2",
- subText: "18px",
- icon: "HEADING_TWO",
+ label: "M",
+ value: "1rem",
+ subText: "1rem",
},
{
- label: "Heading 3",
- value: "HEADING3",
- subText: "16px",
- icon: "HEADING_THREE",
+ label: "L",
+ value: "1.25rem",
+ subText: "1.25rem",
},
{
- label: "Paragraph",
- value: "PARAGRAPH",
- subText: "14px",
- icon: "PARAGRAPH",
+ label: "XL",
+ value: "1.875rem",
+ subText: "1.875rem",
},
{
- label: "Paragraph 2",
- value: "PARAGRAPH2",
- subText: "12px",
- icon: "PARAGRAPH_TWO",
+ label: "XXL",
+ value: "3rem",
+ subText: "3rem",
+ },
+ {
+ label: "3XL",
+ value: "3.75rem",
+ subText: "3.75rem",
},
],
- isBindProperty: false,
+ isJSConvertible: true,
+ isBindProperty: true,
isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "labelStyle",
@@ -332,6 +335,43 @@ class BaseInputWidget<
},
],
},
+ {
+ sectionName: "Styles",
+ children: [
+ {
+ propertyName: "accentColor",
+ label: "Accent Color",
+ controlType: "COLOR_PICKER",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ invisible: true,
+ },
+ {
+ propertyName: "borderRadius",
+ label: "Border Radius",
+ helpText:
+ "Rounds the corners of the icon button's outer border edge",
+ controlType: "BORDER_RADIUS_OPTIONS",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ propertyName: "boxShadow",
+ label: "Box Shadow",
+ helpText:
+ "Enables you to cast a drop shadow from the frame of the widget",
+ controlType: "BOX_SHADOW_OPTIONS",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ ],
+ },
];
}
@@ -477,7 +517,7 @@ export interface BaseInputWidgetProps extends WidgetProps {
labelAlignment?: Alignment;
labelWidth?: number;
labelTextColor?: string;
- labelTextSize?: TextSize;
+ labelTextSize?: string;
labelStyle?: string;
inputValidators: BaseInputValidator[];
isValid: boolean;
diff --git a/app/client/src/widgets/ButtonGroupWidget/component/index.tsx b/app/client/src/widgets/ButtonGroupWidget/component/index.tsx
index 1b7b745d7b..85dbbccea2 100644
--- a/app/client/src/widgets/ButtonGroupWidget/component/index.tsx
+++ b/app/client/src/widgets/ButtonGroupWidget/component/index.tsx
@@ -12,10 +12,6 @@ import { IconName } from "@blueprintjs/icons";
import tinycolor from "tinycolor2";
import { darkenActive, darkenHover } from "constants/DefaultTheme";
import {
- ButtonBoxShadow,
- ButtonBoxShadowTypes,
- ButtonBorderRadius,
- ButtonBorderRadiusTypes,
ButtonStyleType,
ButtonVariant,
ButtonVariantTypes,
@@ -27,12 +23,13 @@ import { Colors } from "constants/Colors";
import {
getCustomBackgroundColor,
getCustomBorderColor,
- getCustomTextColor,
getCustomJustifyContent,
+ getComplementaryGrayscaleColor,
} from "widgets/WidgetUtils";
import { RenderMode, RenderModes } from "constants/WidgetConstants";
import { DragContainer } from "widgets/ButtonWidget/component/DragContainer";
import { buttonHoverActiveStyles } from "../../ButtonWidget/component/utils";
+import { THEMEING_TEXT_SIZES } from "constants/ThemeConstants";
// Utility functions
interface ButtonData {
@@ -65,9 +62,9 @@ const getButtonData = (
interface WrapperStyleProps {
isHorizontal: boolean;
- borderRadius?: ButtonBorderRadius;
- boxShadow?: ButtonBoxShadow;
- boxShadowColor?: string;
+ borderRadius?: string;
+ boxShadow?: string;
+ buttonVariant: ButtonVariant;
}
const ButtonGroupWrapper = styled.div`
@@ -78,38 +75,36 @@ const ButtonGroupWrapper = styled.div`
justify-content: stretch;
align-items: stretch;
overflow: hidden;
+ cursor: not-allowed;
+ gap: ${({ buttonVariant }) =>
+ `${buttonVariant === ButtonVariantTypes.PRIMARY ? "1px" : "0px"}`};
${(props) =>
props.isHorizontal ? "flex-direction: row" : "flex-direction: column"};
+ box-shadow: ${({ boxShadow }) => boxShadow};
+ border-radius: ${({ borderRadius }) => borderRadius};
- border-radius: ${({ borderRadius }) =>
- borderRadius === ButtonBorderRadiusTypes.ROUNDED
- ? "8px"
- : borderRadius === ButtonBorderRadiusTypes.CIRCLE
- ? "32px"
- : "0px"};
+ & > *:first-child,
+ & > *:first-child button {
+ border-radius: ${({ borderRadius, isHorizontal }) =>
+ isHorizontal
+ ? `${borderRadius} 0px 0px ${borderRadius}`
+ : `${borderRadius} ${borderRadius} 0px 0px`};
+ }
- box-shadow: ${({ boxShadow, boxShadowColor, theme }) =>
- boxShadow === ButtonBoxShadowTypes.VARIANT1
- ? `0px 0px 4px 3px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant1}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT2
- ? `3px 3px 4px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant2}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT3
- ? `0px 1px 3px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant3}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT4
- ? `2px 2px 0px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant4}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT5
- ? `-2px -2px 0px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant5}`
- : "none"} !important;
+ & > *:last-child,
+ & > *:last-child button {
+ border-radius: ${({ borderRadius, isHorizontal }) =>
+ isHorizontal
+ ? `0px ${borderRadius} ${borderRadius} 0`
+ : `0px 0px ${borderRadius} ${borderRadius}`};
+ }
`;
const MenuButtonWrapper = styled.div<{ renderMode: RenderMode }>`
flex: 1 1 auto;
+ cursor: pointer;
+ position: relative;
${({ renderMode }) => renderMode === RenderModes.CANVAS && `height: 100%`};
@@ -127,23 +122,32 @@ const PopoverStyles = createGlobalStyle<{
minPopoverWidth: number;
popoverTargetWidth?: number;
id: string;
+ borderRadius?: string;
}>`
- .menu-button-popover > .${Classes.POPOVER2_CONTENT} {
- background: none;
- }
- ${({ id, minPopoverWidth, popoverTargetWidth }) => `
- .menu-button-width-${id} {
+ ${({ borderRadius, id, minPopoverWidth, popoverTargetWidth }) => `
+ .${id}.${Classes.POPOVER2} {
+ background: none;
+ box-shadow: 0 6px 20px 0px rgba(0, 0, 0, 0.15) !important;
+ margin-top: 8px !important;
+ margin-bottom: 8px !important;
+ border-radius: ${
+ borderRadius === THEMEING_TEXT_SIZES.lg ? `0.375rem` : borderRadius
+ };
+ box-shadow: none;
+ overflow: hidden;
${popoverTargetWidth && `width: ${popoverTargetWidth}px`};
min-width: ${minPopoverWidth}px;
}
+
+ .button-group-menu-popover > .${Classes.POPOVER2_CONTENT} {
+ background: none;
+ }
`}
`;
interface ButtonStyleProps {
isHorizontal: boolean;
- borderRadius?: ButtonBorderRadius;
- borderRadOnStart: boolean;
- borderRadOnEnd: boolean;
+ borderRadius?: string;
buttonVariant?: ButtonVariant; // solid | outline | ghost
buttonColor?: string;
iconAlign?: string;
@@ -152,7 +156,7 @@ interface ButtonStyleProps {
}
/*
- Don't use buttonHoverActiveStyles in a nested function it won't work -
+ Don't use buttonHoverActiveStyles in a nested function it won't work -
const buttonHoverActiveStyles = css ``
@@ -182,17 +186,7 @@ const StyledButton = styled.button`
${buttonHoverActiveStyles}
}
- ${({
- borderRadius,
- borderRadOnEnd,
- borderRadOnStart,
- buttonColor,
- buttonVariant,
- iconAlign,
- isHorizontal,
- isLabel,
- theme,
- }) => `
+ ${({ buttonColor, buttonVariant, iconAlign, isLabel, theme }) => `
& {
background: ${
getCustomBackgroundColor(buttonVariant, buttonColor) !== "none"
@@ -222,57 +216,16 @@ const StyledButton = styled.button`
: "none"
} ${buttonVariant === ButtonVariantTypes.PRIMARY ? "" : "!important"};
- ${
- isHorizontal
- ? buttonVariant === ButtonVariantTypes.PRIMARY
- ? borderRadOnEnd
- ? ""
- : `
- border-right: 1px solid ${getCustomTextColor(theme, buttonColor)};
- `
- : ""
- : buttonVariant === ButtonVariantTypes.PRIMARY
- ? borderRadOnEnd
- ? ""
- : `
- border-bottom: 1px solid ${getCustomTextColor(theme, buttonColor)};
- `
- : ""
- }
-
- border-radius: ${
- borderRadius === ButtonBorderRadiusTypes.ROUNDED
- ? borderRadOnStart // first button
- ? isHorizontal
- ? "8px 0px 0px 8px"
- : "8px 8px 0px 0px"
- : borderRadOnEnd // last button
- ? isHorizontal
- ? "0px 8px 8px 0px"
- : "0px 0px 8px 8px"
- : "0px"
- : borderRadius === ButtonBorderRadiusTypes.CIRCLE
- ? borderRadOnStart // first button
- ? isHorizontal
- ? "32px 0px 0px 32px"
- : "32px 32px 0px 0px"
- : borderRadOnEnd // last button
- ? isHorizontal
- ? "0px 32px 32px 0px"
- : "0px 0px 32px 32px"
- : "0px"
- : "0px"
- };
-
& span {
color: ${
buttonVariant === ButtonVariantTypes.PRIMARY
- ? getCustomTextColor(theme, buttonColor)
+ ? getComplementaryGrayscaleColor(buttonColor)
: getCustomBackgroundColor(ButtonVariantTypes.PRIMARY, buttonColor)
} !important;
}
- &:disabled {
+
+ &:disabled {
cursor: not-allowed;
border: 1px solid ${Colors.ALTO2} !important;
background: ${theme.colors.button.disabled.bgColor} !important;
@@ -280,6 +233,7 @@ const StyledButton = styled.button`
color: ${theme.colors.button.disabled.textColor} !important;
}
}
+
`}
`;
@@ -297,9 +251,8 @@ const StyledButtonContent = styled.div<{
export interface BaseStyleProps {
backgroundColor?: string;
- borderRadius?: ButtonBorderRadius;
- boxShadow?: ButtonBoxShadow;
- boxShadowColor?: string;
+ borderRadius?: string;
+ boxShadow?: string;
buttonColor?: string;
buttonStyle?: ButtonStyleType;
buttonVariant?: ButtonVariant;
@@ -308,6 +261,7 @@ export interface BaseStyleProps {
const BaseMenuItem = styled(MenuItem)`
padding: 8px 10px !important;
+ border-radius: 0px;
${({ backgroundColor, theme }) =>
backgroundColor
? `
@@ -560,29 +514,29 @@ class ButtonGroupComponent extends React.Component<
.filter((item) => item.isVisible === true);
// sort btns by index
items = sortBy(items, ["index"]);
+ const popoverId = `button-group-${widgetId}`;
return (
{items.map((button) => {
- const borderRadOnStart = button.index === 0;
- const borderRadOnEnd = button.index === items.length - 1;
const isButtonDisabled = button.isDisabled || isDisabled;
if (button.buttonType === "MENU" && !isButtonDisabled) {
const { menuItems } = button;
- const popoverId = `${widgetId}-${button.id}`;
+
return (
void;
groupButtons: Record;
diff --git a/app/client/src/widgets/ButtonGroupWidget/index.ts b/app/client/src/widgets/ButtonGroupWidget/index.ts
index 928724fc87..d63a69e632 100644
--- a/app/client/src/widgets/ButtonGroupWidget/index.ts
+++ b/app/client/src/widgets/ButtonGroupWidget/index.ts
@@ -1,5 +1,8 @@
import { ButtonVariantTypes } from "components/constants";
-import { Colors } from "constants/Colors";
+import { get } from "lodash";
+import { WidgetProps } from "widgets/BaseWidget";
+import { BlueprintOperationTypes } from "widgets/constants";
+import { klona as clone } from "klona/full";
import IconSVG from "./icon.svg";
import Widget from "./widget";
@@ -24,7 +27,6 @@ export const CONFIG = {
iconName: "heart",
id: "groupButton1",
widgetId: "",
- buttonColor: Colors.GREEN,
buttonType: "SIMPLE",
placement: "CENTER",
isVisible: true,
@@ -36,7 +38,6 @@ export const CONFIG = {
label: "Add",
iconName: "add",
id: "groupButton2",
- buttonColor: Colors.GREEN,
buttonType: "SIMPLE",
placement: "CENTER",
widgetId: "",
@@ -51,7 +52,6 @@ export const CONFIG = {
id: "groupButton3",
buttonType: "MENU",
placement: "CENTER",
- buttonColor: Colors.GREEN,
widgetId: "",
isVisible: true,
isDisabled: false,
@@ -94,6 +94,48 @@ export const CONFIG = {
},
},
},
+ blueprint: {
+ operations: [
+ {
+ type: BlueprintOperationTypes.MODIFY_PROPS,
+ fn: (widget: WidgetProps & { children?: WidgetProps[] }) => {
+ const groupButtons = clone(widget.groupButtons);
+ const dynamicBindingPathList: any[] = get(
+ widget,
+ "dynamicBindingPathList",
+ [],
+ );
+
+ Object.keys(groupButtons).map((groupButtonKey) => {
+ groupButtons[groupButtonKey].buttonColor = get(
+ widget,
+ "childStylesheet.button.buttonColor",
+ "{{appsmith.theme.colors.primaryColor}}",
+ );
+
+ dynamicBindingPathList.push({
+ key: `groupButtons.${groupButtonKey}.buttonColor`,
+ });
+ });
+
+ const updatePropertyMap = [
+ {
+ widgetId: widget.widgetId,
+ propertyName: "dynamicBindingPathList",
+ propertyValue: dynamicBindingPathList,
+ },
+ {
+ widgetId: widget.widgetId,
+ propertyName: "groupButtons",
+ propertyValue: groupButtons,
+ },
+ ];
+
+ return updatePropertyMap;
+ },
+ },
+ ],
+ },
},
properties: {
derived: Widget.getDerivedPropertiesMap(),
diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts b/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts
new file mode 100644
index 0000000000..4b36182473
--- /dev/null
+++ b/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts
@@ -0,0 +1,21 @@
+import { ButtonGroupWidgetProps } from ".";
+import { AppTheme } from "entities/AppTheming";
+import { get } from "lodash";
+
+/**
+ * this is a getter function to get stylesheet value of the property from the config
+ *
+ * @param props
+ * @param propertyPath
+ * @param widgetStylesheet
+ * @returns
+ */
+export const getStylesheetValue = (
+ props: ButtonGroupWidgetProps,
+ propertyPath: string,
+ widgetStylesheet?: AppTheme["stylesheet"][string],
+) => {
+ const propertyName = propertyPath.split(".").slice(-1)[0];
+
+ return get(widgetStylesheet, `childStylesheet.button.${propertyName}`, "");
+};
diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx
index 753888dcd0..789f47ad26 100644
--- a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx
+++ b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx
@@ -6,15 +6,14 @@ import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { ValidationTypes } from "constants/WidgetValidation";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import {
- ButtonBoxShadow,
ButtonVariant,
- ButtonBorderRadiusTypes,
ButtonPlacementTypes,
ButtonPlacement,
ButtonVariantTypes,
} from "components/constants";
import ButtonGroupComponent from "../component";
import { MinimumPopupRows } from "widgets/constants";
+import { getStylesheetValue } from "./helpers";
class ButtonGroupWidget extends BaseWidget<
ButtonGroupWidgetProps,
@@ -87,6 +86,7 @@ class ButtonGroupWidget extends BaseWidget<
label: "",
isBindProperty: false,
isTriggerProperty: false,
+ dependencies: ["childStylesheet"],
panelConfig: {
editableTitle: true,
titlePropertyName: "label",
@@ -142,14 +142,6 @@ class ButtonGroupWidget extends BaseWidget<
},
},
},
- {
- propertyName: "buttonColor",
- helpText: "Changes the color of the button",
- label: "Button Color",
- controlType: "COLOR_PICKER",
- isBindProperty: false,
- isTriggerProperty: false,
- },
{
propertyName: "isDisabled",
helpText: "Disables input to the widget",
@@ -291,23 +283,7 @@ class ButtonGroupWidget extends BaseWidget<
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
- {
- propertyName: "backgroundColor",
- helpText:
- "Sets the background color of a menu item",
- label: "Background color",
- controlType: "COLOR_PICKER",
- isBindProperty: false,
- isTriggerProperty: false,
- },
- {
- propertyName: "textColor",
- helpText: "Sets the text color of a menu item",
- label: "Text color",
- controlType: "COLOR_PICKER",
- isBindProperty: false,
- isTriggerProperty: false,
- },
+
{
propertyName: "isDisabled",
helpText: "Disables menu item",
@@ -344,14 +320,7 @@ class ButtonGroupWidget extends BaseWidget<
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
- {
- propertyName: "iconColor",
- helpText: "Sets the icon color of a menu item",
- label: "Icon color",
- controlType: "COLOR_PICKER",
- isBindProperty: false,
- isTriggerProperty: false,
- },
+
{
propertyName: "iconAlign",
label: "Icon alignment",
@@ -389,6 +358,38 @@ class ButtonGroupWidget extends BaseWidget<
},
],
},
+ {
+ sectionName: "Style",
+ children: [
+ {
+ propertyName: "iconColor",
+ helpText: "Sets the icon color of a menu item",
+ label: "Icon color",
+ controlType: "COLOR_PICKER",
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ {
+ propertyName: "backgroundColor",
+ helpText:
+ "Sets the background color of a menu item",
+ label: "Background color",
+ controlType: "COLOR_PICKER",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ propertyName: "textColor",
+ helpText: "Sets the text color of a menu item",
+ label: "Text color",
+ controlType: "COLOR_PICKER",
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ ],
+ },
],
},
},
@@ -419,6 +420,22 @@ class ButtonGroupWidget extends BaseWidget<
},
],
},
+ {
+ sectionName: "Styles",
+ children: [
+ {
+ getStylesheetValue,
+ propertyName: "buttonColor",
+ helpText: "Changes the color of the button",
+ label: "Button Color",
+ controlType: "COLOR_PICKER",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ ],
+ },
],
},
},
@@ -467,19 +484,10 @@ class ButtonGroupWidget extends BaseWidget<
helpText:
"Rounds the corners of the icon button's outer border edge",
controlType: "BORDER_RADIUS_OPTIONS",
- options: [
- ButtonBorderRadiusTypes.SHARP,
- ButtonBorderRadiusTypes.ROUNDED,
- ButtonBorderRadiusTypes.CIRCLE,
- ],
- isBindProperty: false,
+ isJSConvertible: true,
+ isBindProperty: true,
isTriggerProperty: false,
- validation: {
- type: ValidationTypes.TEXT,
- params: {
- allowedValues: ["SHARP", "ROUNDED", "CIRCLE"],
- },
- },
+ validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "boxShadow",
@@ -487,35 +495,10 @@ class ButtonGroupWidget extends BaseWidget<
helpText:
"Enables you to cast a drop shadow from the frame of the widget",
controlType: "BOX_SHADOW_OPTIONS",
- isBindProperty: false,
+ isJSConvertible: true,
+ isBindProperty: true,
isTriggerProperty: false,
- validation: {
- type: ValidationTypes.TEXT,
- params: {
- allowedValues: [
- "NONE",
- "VARIANT1",
- "VARIANT2",
- "VARIANT3",
- "VARIANT4",
- "VARIANT5",
- ],
- },
- },
- },
- {
- propertyName: "boxShadowColor",
- helpText: "Sets the shadow color of the widget",
- label: "Shadow Color",
- controlType: "COLOR_PICKER",
- isBindProperty: false,
- isTriggerProperty: false,
- validation: {
- type: ValidationTypes.TEXT,
- params: {
- regex: /^(?![<|{{]).+/,
- },
- },
+ validation: { type: ValidationTypes.TEXT },
},
],
},
@@ -542,7 +525,6 @@ class ButtonGroupWidget extends BaseWidget<
`
- height: 100%;
- background-image: none !important;
- font-weight: ${(props) => props.theme.fontWeights[2]};
- outline: none;
- padding: 0px 10px;
+height: 100%;
+background-image: none !important;
+font-weight: ${(props) => props.theme.fontWeights[2]};
+outline: none;
+padding: 0px 10px;
+gap: 8px;
- &:hover, &:active {
- ${buttonHoverActiveStyles}
- }
+&:hover, &:active {
+ ${buttonHoverActiveStyles}
+ }
- ${({ buttonColor, buttonVariant, theme }) => `
- background: ${
- getCustomBackgroundColor(buttonVariant, buttonColor) !== "none"
- ? getCustomBackgroundColor(buttonVariant, buttonColor)
- : buttonVariant === ButtonVariantTypes.PRIMARY
- ? theme.colors.button.primary.primary.bgColor
- : "none"
- } !important;
-
- &:disabled, &.${Classes.DISABLED} {
- background-color: ${theme.colors.button.disabled.bgColor} !important;
- cursor: not-allowed;
- color: ${theme.colors.button.disabled.textColor} !important;
- border-color: ${theme.colors.button.disabled.bgColor} !important;
- > span {
- color: ${theme.colors.button.disabled.textColor} !important;
- }
- }
-
- border: ${
- getCustomBorderColor(buttonVariant, buttonColor) !== "none"
- ? `1px solid ${getCustomBorderColor(buttonVariant, buttonColor)}`
- : buttonVariant === ButtonVariantTypes.SECONDARY
- ? `1px solid ${theme.colors.button.primary.secondary.borderColor}`
+${({ buttonColor, buttonVariant, theme }) => `
+ background: ${
+ getCustomBackgroundColor(buttonVariant, buttonColor) !== "none"
+ ? getCustomBackgroundColor(buttonVariant, buttonColor)
+ : buttonVariant === ButtonVariantTypes.PRIMARY
+ ? theme.colors.button.primary.primary.bgColor
: "none"
} !important;
- & > span {
- max-height: 100%;
- max-width: 99%;
- text-overflow: ellipsis;
- overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 1;
- -webkit-box-orient: vertical;
- color: ${
- buttonVariant === ButtonVariantTypes.PRIMARY
- ? getCustomTextColor(theme, buttonColor)
- : getCustomBackgroundColor(ButtonVariantTypes.PRIMARY, buttonColor)
- } !important;
+ &:disabled, &.${Classes.DISABLED} {
+ cursor: not-allowed;
+ background-color: ${Colors.GREY_1} !important;
+ color: ${Colors.GREY_9} !important;
+ box-shadow: none !important;
+ pointer-events: none;
+ border-color: ${Colors.GREY_1} !important;
+
+ > span {
+ color: ${Colors.GREY_9} !important;
}
- `}
+ }
- border-radius: ${({ borderRadius }) =>
- borderRadius === ButtonBorderRadiusTypes.ROUNDED ? "5px" : 0};
+ border: ${
+ getCustomBorderColor(buttonVariant, buttonColor) !== "none"
+ ? `1px solid ${getCustomBorderColor(buttonVariant, buttonColor)}`
+ : buttonVariant === ButtonVariantTypes.SECONDARY
+ ? `1px solid ${theme.colors.button.primary.secondary.borderColor}`
+ : "none"
+ } !important;
- box-shadow: ${({ boxShadow, boxShadowColor, theme }) =>
- boxShadow === ButtonBoxShadowTypes.VARIANT1
- ? `0px 0px 4px 3px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant1}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT2
- ? `3px 3px 4px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant2}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT3
- ? `0px 1px 3px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant3}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT4
- ? `2px 2px 0px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant4}`
- : boxShadow === ButtonBoxShadowTypes.VARIANT5
- ? `-2px -2px 0px ${boxShadowColor ||
- theme.colors.button.boxShadow.default.variant5}`
- : "none"} !important;
+ & > * {
+ margin-right: 0;
+ }
- ${({ placement }) =>
- placement
- ? `
- justify-content: ${getCustomJustifyContent(placement)};
- & > span.bp3-button-text {
- flex: unset !important;
- }
- `
- : ""}
+ & > span {
+ max-height: 100%;
+ max-width: 99%;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ line-height: normal;
+
+ color: ${
+ buttonVariant === ButtonVariantTypes.PRIMARY
+ ? getComplementaryGrayscaleColor(buttonColor)
+ : getCustomBackgroundColor(ButtonVariantTypes.PRIMARY, buttonColor)
+ } !important;
+ }
+`}
+
+border-radius: ${({ borderRadius }) => borderRadius};
+box-shadow: ${({ boxShadow }) => `${boxShadow ?? "none"}`} !important;
+
+${({ placement }) =>
+ placement
+ ? `
+ justify-content: ${getCustomJustifyContent(placement)};
+ & > span.bp3-button-text {
+ flex: unset !important;
+ }
+ `
+ : ""}
`;
-const StyledButton = styled((props) => (
+export const StyledButton = styled((props) => (