Feat: Style customisation in the button widget (#6052)

Added multiple style props to the button widget:
- Button styles
- The background colour can change with a hex code
- elevation (box-shadow & colour)
- There are button variant contained (solid button), outlined (only borders), text (text buttons)
- Button can have an end icon or start icon
This commit is contained in:
Paul Li 2021-08-24 09:53:15 -04:00 committed by GitHub
parent 163192d851
commit cb4242e7e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 728 additions and 271 deletions

View File

@ -19,12 +19,20 @@ describe("Button Widget Functionality", function() {
it("Button-Style Validation", function() {
//Changing the style of the button from the property pane and verify it's color.
// Change to Warning button sytle
cy.changeButtonStyle(2, "rgb(254, 184, 17)", "rgba(0, 0, 0, 0)");
cy.get(publishPage.backToEditor).click({ force: true });
cy.openPropertyPane("buttonwidget");
// Change to Info button sytle
cy.changeButtonStyle(4, "rgb(102, 152, 255)", "rgba(0, 0, 0, 0)");
cy.get(publishPage.backToEditor).click({ force: true });
cy.openPropertyPane("buttonwidget");
// Change to Secondary button sytle
cy.changeButtonStyle(2, "rgba(0, 0, 0, 0)", "rgba(0, 0, 0, 0)");
cy.changeButtonStyle(5, "rgb(133, 130, 130)", "rgba(0, 0, 0, 0)");
cy.get(publishPage.backToEditor).click({ force: true });
// Change to Danger button sytle
cy.openPropertyPane("buttonwidget");
cy.changeButtonStyle(3, "rgb(179, 3, 56)", "rgb(139, 2, 43)");
cy.changeButtonStyle(3, "rgb(242, 43, 43)", "rgb(139, 2, 43)");
cy.get(publishPage.backToEditor).click({ force: true });
// Change to Primary button sytle
cy.openPropertyPane("buttonwidget");

View File

@ -146,7 +146,7 @@
"styled-components": "^5.2.0",
"styled-system": "^5.1.5",
"tern": "^0.21.0",
"tinycolor2": "^1.4.1",
"tinycolor2": "^1.4.2",
"toposort": "^2.0.2",
"ts-loader": "^6.0.4",
"tslib": "^2.1.0",

View File

@ -29,9 +29,8 @@ class FilePickerComponent extends React.Component<
}
return (
<BaseButton
accent="primary"
buttonStyle="PRIMARY"
disabled={this.props.isDisabled}
filled
loading={this.props.isLoading}
onClick={this.openModal}
text={label}

View File

@ -7,6 +7,14 @@ import { IconName } from "@blueprintjs/icons";
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
import { ThemeProp } from "components/ads/common";
import { WIDGET_PADDING } from "constants/WidgetConstants";
import {
ButtonBorderRadius,
ButtonBorderRadiusTypes,
} from "components/propertyControls/BorderRadiusOptionsControl";
import {
ButtonBoxShadow,
ButtonBoxShadowTypes,
} from "components/propertyControls/BoxShadowOptionsControl";
const IconButtonContainer = styled.div`
display: flex;
@ -176,23 +184,6 @@ export enum ButtonVariantTypes {
}
export type ButtonVariant = keyof typeof ButtonVariantTypes;
export enum ButtonBorderRadiusTypes {
SHARP = "SHARP",
ROUNDED = "ROUNDED",
CIRCLE = "CIRCLE",
}
export type ButtonBorderRadius = keyof typeof ButtonBorderRadiusTypes;
export enum ButtonBoxShadowTypes {
NONE = "NONE",
VARIANT1 = "VARIANT1",
VARIANT2 = "VARIANT2",
VARIANT3 = "VARIANT3",
VARIANT4 = "VARIANT4",
VARIANT5 = "VARIANT5",
}
export type ButtonBoxShadow = keyof typeof ButtonBoxShadowTypes;
export interface IconButtonComponentProps extends ComponentProps {
iconName?: IconName;
buttonStyle: ButtonStyle;

View File

@ -1,16 +1,18 @@
import React, { useRef, useState } from "react";
import styled from "styled-components";
import tinycolor from "tinycolor2";
import {
IButtonProps,
MaybeElement,
Button,
IconName,
Alignment,
Position,
} from "@blueprintjs/core";
import { IconName } from "@blueprintjs/icons";
import Tooltip from "components/ads/Tooltip";
import styled, { css } from "styled-components";
import { ButtonStyle } from "widgets/ButtonWidget";
import { Theme, darkenHover, darkenActive } from "constants/DefaultTheme";
import _ from "lodash";
import { Theme } from "constants/DefaultTheme";
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
import { useScript, ScriptStatus } from "utils/hooks/useScript";
import {
@ -18,38 +20,126 @@ import {
GOOGLE_RECAPTCHA_DOMAIN_ERROR,
createMessage,
} from "constants/messages";
import { Variant } from "components/ads/common";
import { ThemeProp, Variant } from "components/ads/common";
import { Toaster } from "components/ads/Toast";
import ReCAPTCHA from "react-google-recaptcha";
import {
ButtonBoxShadow,
ButtonBoxShadowTypes,
} from "components/propertyControls/BoxShadowOptionsControl";
import {
ButtonBorderRadius,
ButtonBorderRadiusTypes,
} from "components/propertyControls/BorderRadiusOptionsControl";
const getButtonColorStyles = (props: { theme: Theme } & ButtonStyleProps) => {
if (props.filled) {
return props.accent === "grey"
? props.theme.colors.textOnGreyBG
: props.theme.colors.textOnDarkBG;
export enum ButtonStyleTypes {
PRIMARY = "PRIMARY",
WARNING = "WARNING",
DANGER = "DANGER",
INFO = "INFO",
SECONDARY = "SECONDARY",
CUSTOM = "CUSTOM",
}
export type ButtonStyle = keyof typeof ButtonStyleTypes;
export enum ButtonVariantTypes {
SOLID = "SOLID",
OUTLINE = "OUTLINE",
GHOST = "GHOST",
}
export type ButtonVariant = keyof typeof ButtonVariantTypes;
const getCustomTextColor = (
theme: Theme,
backgroundColor?: string,
prevButtonStyle?: ButtonStyle,
) => {
if (!backgroundColor)
return theme.colors.button[
(prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
].solid.textColor;
const isDark = tinycolor(backgroundColor).isDark();
if (isDark) {
return theme.colors.button.custom.solid.light.textColor;
}
if (props.accent) {
if (props.accent === "secondary") {
return props.theme.colors[AccentColorMap["primary"]];
}
return props.theme.colors[AccentColorMap[props.accent]];
return theme.colors.button.custom.solid.dark.textColor;
};
const getCustomHoverColor = (
theme: Theme,
prevButtonStyle?: ButtonStyle,
buttonVariant?: ButtonVariant,
backgroundColor?: string,
) => {
if (!backgroundColor) {
return theme.colors.button[
(prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
][(buttonVariant || ButtonVariantTypes.SOLID).toLowerCase()].hoverColor;
}
switch (buttonVariant) {
case ButtonVariantTypes.OUTLINE:
return backgroundColor
? tinycolor(backgroundColor)
.lighten(40)
.toString()
: theme.colors.button.primary.outline.hoverColor;
break;
case ButtonVariantTypes.GHOST:
return backgroundColor
? tinycolor(backgroundColor)
.lighten(40)
.toString()
: theme.colors.button.primary.ghost.hoverColor;
break;
default:
return backgroundColor
? tinycolor(backgroundColor)
.darken(10)
.toString()
: theme.colors.button.primary.solid.hoverColor;
break;
}
};
const getButtonFillStyles = (props: { theme: Theme } & ButtonStyleProps) => {
if (props.filled) {
return props.accent === "grey"
? props.theme.colors.dropdownIconDarkBg
: props.theme.colors.textOnDarkBG;
}
if (props.accent) {
if (props.accent === "secondary") {
return props.theme.colors[AccentColorMap["primary"]];
}
return props.theme.colors[AccentColorMap[props.accent]];
}
const getCustomBackgroundColor = (
theme: Theme,
prevButtonStyle?: ButtonStyle,
buttonVariant?: ButtonVariant,
backgroundColor?: string,
) => {
return buttonVariant === ButtonVariantTypes.SOLID
? backgroundColor
? backgroundColor
: theme.colors.button[
(prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
].solid.bgColor
: "none";
};
const getCustomBorderColor = (
theme: Theme,
prevButtonStyle?: ButtonStyle,
buttonVariant?: ButtonVariant,
backgroundColor?: string,
) => {
return buttonVariant === ButtonVariantTypes.OUTLINE
? backgroundColor
? backgroundColor
: theme.colors.button[
(prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
].outline.borderColor
: "none";
};
const RecaptchaWrapper = styled.div`
position: relative;
.grecaptcha-badge {
visibility: hidden;
}
`;
const ToolTipContent = styled.div`
max-width: 350px;
`;
@ -64,118 +154,257 @@ const ToolTipWrapper = styled.div`
}
`;
const ButtonColorStyles = css<ButtonStyleProps>`
color: ${getButtonColorStyles};
svg {
fill: ${getButtonFillStyles};
}
`;
const ButtonContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 100%;
const RecaptchaWrapper = styled.div`
position: relative;
.grecaptcha-badge {
visibility: hidden;
}
`;
const AccentColorMap: Record<ButtonStyleName, string> = {
primary: "primaryOld",
secondary: "secondaryOld",
error: "error",
grey: "dropdownGreyBg",
};
const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
<Button {..._.omit(props, ["accent", "filled", "disabled"])} />
))<ButtonStyleProps>`
&&&& {
${ButtonColorStyles};
width: 100%;
& > button {
height: 100%;
transition: background-color 0.2s;
background-color: ${(props) =>
props.filled &&
props.accent &&
props.theme.colors[AccentColorMap[props.accent]]};
border: 1px solid
${(props) =>
props.accent
? props.theme.colors[AccentColorMap[props.accent]]
: props.theme.colors.primary};
}
`;
border-radius: 0;
font-weight: ${(props) => props.theme.fontWeights[2]};
outline: none;
const StyledButton = styled(Button)<ThemeProp & ButtonStyleProps>`
height: 100%;
background-image: none !important;
font-weight: ${(props) => props.theme.fontWeights[2]};
outline: none;
padding: 0px 10px;
&.bp3-button {
padding: 0px 10px;
${({ buttonColor, buttonStyle, buttonVariant, prevButtonStyle, theme }) => `
&:enabled {
background: ${
buttonStyle === ButtonStyleTypes.WARNING
? buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.warning.solid.bgColor
: "none"
: buttonStyle === ButtonStyleTypes.DANGER
? buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.danger.solid.bgColor
: "none"
: buttonStyle === ButtonStyleTypes.INFO
? buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.info.solid.bgColor
: "none"
: buttonStyle === ButtonStyleTypes.SECONDARY
? buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.secondary.solid.bgColor
: "none"
: buttonStyle === ButtonStyleTypes.CUSTOM
? getCustomBackgroundColor(
theme,
prevButtonStyle,
buttonVariant,
buttonColor,
)
: buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.primary.solid.bgColor
: "none"
} !important;
}
&& .bp3-button-text {
&:hover:enabled, &:active:enabled {
background: ${
buttonStyle === ButtonStyleTypes.WARNING
? buttonVariant === ButtonVariantTypes.OUTLINE
? theme.colors.button.warning.outline.hoverColor
: buttonVariant === ButtonVariantTypes.GHOST
? theme.colors.button.warning.ghost.hoverColor
: theme.colors.button.warning.solid.hoverColor
: buttonStyle === ButtonStyleTypes.DANGER
? buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.danger.solid.hoverColor
: theme.colors.button.danger.outline.hoverColor
: buttonStyle === ButtonStyleTypes.INFO
? buttonVariant === ButtonVariantTypes.SOLID
? theme.colors.button.info.solid.hoverColor
: theme.colors.button.info.outline.hoverColor
: buttonStyle === ButtonStyleTypes.SECONDARY
? buttonVariant === ButtonVariantTypes.OUTLINE
? theme.colors.button.secondary.outline.hoverColor
: buttonVariant === ButtonVariantTypes.GHOST
? theme.colors.button.secondary.ghost.hoverColor
: theme.colors.button.secondary.solid.hoverColor
: buttonStyle === ButtonStyleTypes.CUSTOM
? getCustomHoverColor(
theme,
prevButtonStyle,
buttonVariant,
buttonColor,
)
: buttonVariant === ButtonVariantTypes.OUTLINE
? theme.colors.button.primary.outline.hoverColor
: buttonVariant === ButtonVariantTypes.GHOST
? theme.colors.button.primary.ghost.hoverColor
: theme.colors.button.primary.solid.hoverColor
} !important;
}
&:disabled {
background-color: ${theme.colors.button.disabled.bgColor} !important;
color: ${theme.colors.button.disabled.textColor} !important;
}
border: ${
buttonVariant === ButtonVariantTypes.OUTLINE
? buttonStyle === ButtonStyleTypes.WARNING
? `1px solid ${theme.colors.button.warning.outline.borderColor}`
: buttonStyle === ButtonStyleTypes.DANGER
? `1px solid ${theme.colors.button.danger.outline.borderColor}`
: buttonStyle === ButtonStyleTypes.INFO
? `1px solid ${theme.colors.button.info.outline.borderColor}`
: buttonStyle === ButtonStyleTypes.SECONDARY
? `1px solid ${theme.colors.button.secondary.outline.borderColor}`
: buttonStyle === ButtonStyleTypes.CUSTOM
? `1px solid ${getCustomBorderColor(
theme,
prevButtonStyle,
buttonVariant,
buttonColor,
)}`
: `1px solid ${theme.colors.button.primary.outline.borderColor}`
: "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;
max-height: 100%;
overflow: hidden;
}
&&:hover,
&&:focus {
${ButtonColorStyles};
background-color: ${(props) => {
if (!props.filled) return props.theme.colors.secondaryDarker;
if (props.accent !== "secondary" && props.accent) {
return darkenHover(props.theme.colors[AccentColorMap[props.accent]]);
}
}};
border-color: ${(props) => {
if (!props.filled) return;
if (props.accent !== "secondary" && props.accent) {
return darkenHover(props.theme.colors[AccentColorMap[props.accent]]);
}
}};
color: ${
buttonVariant === ButtonVariantTypes.SOLID
? buttonStyle === ButtonStyleTypes.CUSTOM
? getCustomTextColor(theme, buttonColor, prevButtonStyle)
: `${theme.colors.button.primary.solid.textColor}`
: buttonStyle === ButtonStyleTypes.WARNING
? `${theme.colors.button.warning.outline.textColor}`
: buttonStyle === ButtonStyleTypes.DANGER
? `${theme.colors.button.danger.outline.textColor}`
: buttonStyle === ButtonStyleTypes.INFO
? `${theme.colors.button.info.outline.textColor}`
: buttonStyle === ButtonStyleTypes.SECONDARY
? `${theme.colors.button.secondary.outline.textColor}`
: buttonStyle === ButtonStyleTypes.CUSTOM
? getCustomBackgroundColor(
theme,
prevButtonStyle,
ButtonVariantTypes.SOLID,
buttonColor,
)
: `${theme.colors.button.primary.outline.textColor}`
} !important;
}
&&:active {
${ButtonColorStyles};
background-color: ${(props) => {
if (!props.filled) return props.theme.colors.secondaryDarkest;
if (props.accent !== "secondary" && props.accent) {
return darkenActive(props.theme.colors[AccentColorMap[props.accent]]);
}
}};
border-color: ${(props) => {
if (!props.filled) return;
if (props.accent !== "secondary" && props.accent) {
return darkenActive(props.theme.colors[AccentColorMap[props.accent]]);
}
}};
}
&&.bp3-disabled {
background-color: #d0d7dd;
border: none;
}
}
`}
border-radius: ${({ borderRadius }) =>
borderRadius === ButtonBorderRadiusTypes.ROUNDED ? "5px" : 0};
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;
`;
export type ButtonStyleName = "primary" | "secondary" | "error" | "grey";
type ButtonStyleProps = {
accent?: ButtonStyleName;
filled?: boolean;
buttonColor?: string;
buttonStyle?: ButtonStyle;
prevButtonStyle?: ButtonStyle;
buttonVariant?: ButtonVariant;
boxShadow?: ButtonBoxShadow;
boxShadowColor?: string;
borderRadius?: ButtonBorderRadius;
iconName?: IconName;
iconAlign?: Alignment;
};
// To be used in any other part of the app
export function BaseButton(props: IButtonProps & ButtonStyleProps) {
const className = props.disabled
? `${props.className} bp3-disabled`
: props.className;
return <ButtonWrapper {...props} className={className} />;
const {
borderRadius,
boxShadow,
boxShadowColor,
buttonColor,
buttonStyle,
buttonVariant,
className,
disabled,
icon,
iconAlign,
iconName,
loading,
onClick,
prevButtonStyle,
rightIcon,
text,
} = props;
if (iconAlign === Alignment.RIGHT) {
return (
<StyledButton
alignText={iconName ? Alignment.LEFT : Alignment.CENTER}
borderRadius={borderRadius}
boxShadow={boxShadow}
boxShadowColor={boxShadowColor}
buttonColor={buttonColor}
buttonStyle={buttonStyle}
buttonVariant={buttonVariant}
className={className}
disabled={disabled}
fill
icon={icon}
loading={loading}
onClick={onClick}
prevButtonStyle={prevButtonStyle}
rightIcon={iconName || rightIcon}
text={text}
/>
);
}
return (
<StyledButton
alignText={iconName ? Alignment.RIGHT : Alignment.CENTER}
borderRadius={borderRadius}
boxShadow={boxShadow}
boxShadowColor={boxShadowColor}
buttonColor={buttonColor}
buttonStyle={buttonStyle}
buttonVariant={buttonVariant}
className={className}
disabled={disabled}
fill
icon={iconName || icon}
loading={loading}
onClick={onClick}
prevButtonStyle={prevButtonStyle}
rightIcon={rightIcon}
text={text}
/>
);
}
BaseButton.defaultProps = {
accent: "secondary",
buttonStyle: "SECONDARY",
buttonVariant: "SOLID",
disabled: false,
text: "Button Text",
minimal: true,
@ -194,31 +423,26 @@ interface RecaptchaProps {
recaptchaV2?: boolean;
}
interface ButtonContainerProps extends ComponentProps {
interface ButtonComponentProps extends ComponentProps {
text?: string;
icon?: MaybeElement;
icon?: IconName | MaybeElement;
tooltip?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
disabled?: boolean;
isDisabled?: boolean;
buttonStyle?: ButtonStyle;
prevButtonStyle?: ButtonStyle;
isLoading: boolean;
rightIcon?: IconName | MaybeElement;
type: ButtonType;
buttonColor?: string;
buttonVariant?: ButtonVariant;
borderRadius?: ButtonBorderRadius;
boxShadow?: ButtonBoxShadow;
boxShadowColor?: string;
iconName?: IconName;
iconAlign?: Alignment;
}
const mapButtonStyleToStyleName = (buttonStyle?: ButtonStyle) => {
switch (buttonStyle) {
case "PRIMARY_BUTTON":
return "primary";
case "SECONDARY_BUTTON":
return "secondary";
case "DANGER_BUTTON":
return "error";
default:
return undefined;
}
};
function RecaptchaV2Component(
props: {
children: any;
@ -348,9 +572,7 @@ function BtnWrapper(
}
// To be used with the canvas
function ButtonContainer(
props: ButtonContainerProps & ButtonStyleProps & RecaptchaProps,
) {
function ButtonComponent(props: ButtonComponentProps & RecaptchaProps) {
const btnWrapper = (
<BtnWrapper
clickWithRecaptcha={props.clickWithRecaptcha}
@ -359,16 +581,25 @@ function ButtonContainer(
onClick={props.onClick}
recaptchaV2={props.recaptchaV2}
>
<BaseButton
accent={mapButtonStyleToStyleName(props.buttonStyle)}
disabled={props.disabled}
filled={props.buttonStyle !== "SECONDARY_BUTTON"}
icon={props.icon}
loading={props.isLoading}
rightIcon={props.rightIcon}
text={props.text}
type={props.type}
/>
<ButtonContainer>
<BaseButton
borderRadius={props.borderRadius}
boxShadow={props.boxShadow}
boxShadowColor={props.boxShadowColor}
buttonColor={props.buttonColor}
buttonStyle={props.buttonStyle}
buttonVariant={props.buttonVariant}
disabled={props.isDisabled}
icon={props.icon}
iconAlign={props.iconAlign}
iconName={props.iconName}
loading={props.isLoading}
prevButtonStyle={props.prevButtonStyle}
rightIcon={props.rightIcon}
text={props.text}
type={props.type}
/>
</ButtonContainer>
</BtnWrapper>
);
if (props.tooltip) {
@ -388,4 +619,4 @@ function ButtonContainer(
}
}
export default ButtonContainer;
export default ButtonComponent;

View File

@ -7,10 +7,7 @@ import {
InputGroup,
IMenuProps,
} from "@blueprintjs/core";
import {
BaseButton,
ButtonStyleName,
} from "components/designSystems/blueprint/ButtonComponent";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import {
ItemRenderer,
Select,
@ -85,8 +82,7 @@ class DropdownComponent extends Component<DropdownComponentProps> {
const displayMode = (
<BaseButton
accent="primary"
filled
buttonStyle="PRIMARY"
icon-right="plus"
onClick={this.showTextBox}
text={addItem?.displayText}
@ -95,11 +91,7 @@ class DropdownComponent extends Component<DropdownComponentProps> {
const editMode = (
<ControlGroup fill>
<InputGroup inputRef={this.setNewItemTextInput} />
<BaseButton
filled
onClick={this.handleAddItem}
text={addItem?.displayText}
/>
<BaseButton onClick={this.handleAddItem} text={addItem?.displayText} />
</ControlGroup>
);
return (
@ -154,15 +146,7 @@ class DropdownComponent extends Component<DropdownComponentProps> {
};
render() {
const {
accent,
autocomplete,
filled,
input,
options,
selected,
width,
} = this.props;
const { autocomplete, input, options, selected, width } = this.props;
return (
<StyledDropdown
@ -181,8 +165,8 @@ class DropdownComponent extends Component<DropdownComponentProps> {
{this.props.toggle || (
<StyledButtonWrapper width={width}>
<BaseButton
accent={accent || "secondary"}
filled={!!filled}
buttonStyle="PRIMARY"
buttonVariant="OUTLINE"
rightIcon="chevron-down"
text={this.getSelectedDisplayText()}
/>
@ -207,8 +191,6 @@ export interface DropdownComponentProps {
addItemHandler: (name: string) => void;
};
toggle?: ReactNode;
accent?: ButtonStyleName;
filled?: boolean;
input?: WrappedFieldInputProps;
width?: string;
}

View File

@ -1,13 +1,10 @@
import React from "react";
import { Field, BaseFieldProps } from "redux-form";
import DropdownComponent from "components/editorComponents/DropdownComponent";
import { ButtonStyleName } from "components/designSystems/blueprint/ButtonComponent";
import { DropdownOption } from "widgets/DropdownWidget";
interface DynamicDropdownFieldOptions {
options: DropdownOption[];
accent?: ButtonStyleName;
filled?: boolean;
width?: string;
}

View File

@ -174,8 +174,6 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
<DynamicDropdownFieldWrapper>
<DynamicDropdownField
accent="grey"
filled
name={`${field}.type`}
options={MULTI_PART_DROPDOWN_OPTIONS}
width={DEFAULT_MULTI_PART_DROPDOWN_WIDTH}

View File

@ -101,7 +101,8 @@ class FieldFileInput extends React.Component<Props, FieldFileInputState> {
<div style={{ flexDirection: "row", display: "flex", width: "50vh" }}>
<StyledDiv>{value.name}</StyledDiv>
<SelectButton
accent="secondary"
buttonStyle="PRIMARY"
buttonVariant="OUTLINE"
onClick={() => {
this.openModal();
}}

View File

@ -5,10 +5,13 @@ import { Button, ButtonGroup, IButtonProps } from "@blueprintjs/core";
import BaseControl, { ControlProps } from "./BaseControl";
import { ControlIcons } from "icons/ControlIcons";
import { ThemeProp } from "components/ads/common";
import {
ButtonBorderRadius,
ButtonBorderRadiusTypes,
} from "components/designSystems/appsmith/IconButtonComponent";
export enum ButtonBorderRadiusTypes {
SHARP = "SHARP",
ROUNDED = "ROUNDED",
CIRCLE = "CIRCLE",
}
export type ButtonBorderRadius = keyof typeof ButtonBorderRadiusTypes;
const StyledButtonGroup = styled(ButtonGroup)`
height: 33px;
@ -36,6 +39,7 @@ const StyledButton = styled(Button)<ThemeProp & IButtonProps>`
export interface BorderRadiusOptionsControlProps extends ControlProps {
propertyValue: ButtonBorderRadius | undefined;
onChange: (borderRaidus: ButtonBorderRadius) => void;
options: any[];
}
class BorderRadiusOptionsControl extends BaseControl<
@ -50,11 +54,35 @@ class BorderRadiusOptionsControl extends BaseControl<
}
public render() {
const { propertyValue } = this.props;
const { options, propertyValue } = this.props;
return (
<StyledButtonGroup fill>
<StyledButton
{options.map((option: ButtonBorderRadius) => {
const active =
option === ButtonBorderRadiusTypes.SHARP
? propertyValue === option || propertyValue === undefined
: propertyValue === option;
const icon =
option === ButtonBorderRadiusTypes.SHARP ? (
<ControlIcons.BORDER_RADIUS_SHARP color="#979797" width={15} />
) : option === ButtonBorderRadiusTypes.ROUNDED ? (
<ControlIcons.BORDER_RADIUS_ROUNDED color="#979797" width={15} />
) : (
<ControlIcons.BORDER_RADIUS_CIRCLE color="#979797" width={15} />
);
return (
<StyledButton
active={active}
icon={icon}
key={option}
large
onClick={() => this.toggleOption(option)}
/>
);
})}
{/* <StyledButton
active={propertyValue === ButtonBorderRadiusTypes.SHARP || undefined}
icon={<ControlIcons.BORDER_RADIUS_SHARP color="#979797" width={15} />}
large
@ -75,7 +103,7 @@ class BorderRadiusOptionsControl extends BaseControl<
}
large
onClick={() => this.toggleOption(ButtonBorderRadiusTypes.CIRCLE)}
/>
/> */}
</StyledButtonGroup>
);
}

View File

@ -81,7 +81,7 @@ type IconType = IconName | typeof NONE;
const ICON_NAMES = Object.keys(IconNames).map<IconType>(
(name: string) => IconNames[name as keyof typeof IconNames],
);
ICON_NAMES.push(NONE);
ICON_NAMES.unshift(NONE);
const TypedSelect = Select.ofType<IconType>();

View File

@ -119,6 +119,9 @@ export const Colors = {
BOX_SHADOW_DEFAULT_VARIANT3: "rgba(0, 0, 0, 0.5)",
BOX_SHADOW_DEFAULT_VARIANT4: "rgba(0, 0, 0, 0.25)",
BOX_SHADOW_DEFAULT_VARIANT5: "rgba(0, 0, 0, 0.25)",
BUTTON_CUSTOM_SOLID_DARK_TEXT_COLOR: "#333",
SELECT_DISABLED: "#ced9e080",
};
export type Color = typeof Colors[keyof typeof Colors];

View File

@ -14,9 +14,9 @@ import {
} from "utils/DynamicBindingUtils";
import { Colors } from "constants/Colors";
import FileDataTypes from "widgets/FileDataTypes";
import { ButtonBorderRadiusTypes } from "components/propertyControls/BorderRadiusOptionsControl";
import { ButtonBoxShadowTypes } from "components/propertyControls/BoxShadowOptionsControl";
import {
ButtonBorderRadiusTypes,
ButtonBoxShadowTypes,
ButtonStyleTypes,
ButtonVariantTypes,
} from "components/designSystems/appsmith/IconButtonComponent";
@ -33,7 +33,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
config: {
BUTTON_WIDGET: {
text: "Submit",
buttonStyle: "PRIMARY_BUTTON",
buttonStyle: "PRIMARY",
buttonVariant: "SOLID",
rows: 1 * GRID_DENSITY_MIGRATION_V1,
columns: 2 * GRID_DENSITY_MIGRATION_V1,
widgetName: "Button",

View File

@ -163,14 +163,13 @@ function RapidApiEditorForm(props: Props) {
<ActionButtons>
<ActionButton
accent="error"
buttonStyle="DANGER"
loading={isDeleting}
onClick={onDeleteClick}
text="Delete"
/>
<ActionButton
accent="primary"
filled
buttonStyle="PRIMARY"
loading={isRunning}
onClick={() => {
onRunClick();

View File

@ -188,7 +188,9 @@ class DatasourceDBEditor extends JSONtoForm<Props> {
: undefined}
<SaveButtonContainer>
<ActionButton
accent="error"
buttonStyle="DANGER"
buttonVariant="SOLID"
// accent="error"
className="t--delete-datasource"
loading={isDeleting}
onClick={() => handleDelete(datasourceId)}
@ -196,7 +198,9 @@ class DatasourceDBEditor extends JSONtoForm<Props> {
/>
<ActionButton
accent="secondary"
// accent="secondary"
buttonStyle="PRIMARY"
buttonVariant="OUTLINE"
className="t--test-datasource"
loading={isTesting}
onClick={this.test}

View File

@ -332,7 +332,9 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
return (
<SaveButtonContainer>
<ActionButton
accent="error"
// accent="error"
buttonStyle="DANGER"
buttonVariant="SOLID"
className="t--delete-datasource"
loading={isDeleting}
onClick={() => deleteDatasource(datasourceId)}

View File

@ -145,9 +145,8 @@ function DatasourceCard(props: DatasourceCardProps) {
text="Edit Datasource"
/>
<ActionButton
accent="primary"
buttonStyle="PRIMARY"
className="t--create-api"
filled
icon={"plus"}
onClick={() => props.onCreate(datasource)}
text="New API"

View File

@ -180,7 +180,9 @@ class DatasourceSaaSEditor extends JSONtoForm<Props> {
: null}
<SaveButtonContainer>
<ActionButton
accent="error"
// accent="error"
buttonStyle="DANGER"
buttonVariant="SOLID"
className="t--delete-datasource"
loading={isDeleting}
onClick={() =>

View File

@ -1,13 +1,24 @@
import React from "react";
import * as Sentry from "@sentry/react";
import { Alignment } from "@blueprintjs/core";
import { IconName } from "@blueprintjs/icons";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import ButtonComponent, {
ButtonStyle,
ButtonStyleTypes,
ButtonType,
ButtonVariant,
} from "components/designSystems/blueprint/ButtonComponent";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { ValidationTypes } from "constants/WidgetValidation";
import * as Sentry from "@sentry/react";
import withMeta, { WithMeta } from "./MetaHOC";
import { ButtonBoxShadow } from "components/propertyControls/BoxShadowOptionsControl";
import {
ButtonBorderRadius,
ButtonBorderRadiusTypes,
} from "components/propertyControls/BorderRadiusOptionsControl";
class ButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
onButtonClickBound: (event: React.MouseEvent<HTMLElement>) => void;
@ -36,39 +47,6 @@ class ButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "buttonStyle",
label: "Button Style",
controlType: "DROP_DOWN",
helpText: "Changes the style of the button",
options: [
{
label: "Primary Button",
value: "PRIMARY_BUTTON",
},
{
label: "Secondary Button",
value: "SECONDARY_BUTTON",
},
{
label: "Danger Button",
value: "DANGER_BUTTON",
},
],
isJSConvertible: true,
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: [
"PRIMARY_BUTTON",
"SECONDARY_BUTTON",
"DANGER_BUTTON",
],
},
},
},
{
helpText: "Show helper text with button on hover",
propertyName: "tooltip",
@ -135,9 +113,223 @@ class ButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
},
],
},
{
sectionName: "Styles",
children: [
{
propertyName: "buttonStyle",
label: "Button Style",
controlType: "DROP_DOWN",
helpText: "Changes the style of the button",
options: [
{
label: "Primary",
value: "PRIMARY",
},
{
label: "Warning",
value: "WARNING",
},
{
label: "Danger",
value: "DANGER",
},
{
label: "Info",
value: "INFO",
},
{
label: "Secondary",
value: "SECONDARY",
},
{
label: "Custom",
value: "CUSTOM",
},
],
updateHook: (
props: ButtonWidgetProps,
propertyPath: string,
propertyValue: string,
) => {
let propertiesToUpdate = [
{ propertyPath, propertyValue },
{ propertyPath: "prevButtonStyle", propertyValue },
];
if (propertyValue === "CUSTOM") {
propertiesToUpdate = [{ propertyPath, propertyValue }];
}
propertiesToUpdate.push({
propertyPath: "buttonColor",
propertyValue: "",
});
return propertiesToUpdate;
},
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: [
"PRIMARY",
"WARNING",
"DANGER",
"INFO",
"SECONDARY",
"CUSTOM",
],
},
},
},
{
propertyName: "buttonColor",
helpText:
"Sets the custom color preset based on the button variant",
label: "Button Color",
controlType: "COLOR_PICKER",
isBindProperty: false,
isTriggerProperty: false,
hidden: (props: ButtonWidgetProps) =>
props.buttonStyle !== ButtonStyleTypes.CUSTOM,
dependencies: ["buttonStyle"],
},
{
propertyName: "buttonVariant",
label: "Button Variant",
controlType: "DROP_DOWN",
helpText: "Sets the variant of the icon button",
options: [
{
label: "Solid",
value: "SOLID",
},
{
label: "Outline",
value: "OUTLINE",
},
{
label: "Ghost",
value: "GHOST",
},
],
isJSConvertible: true,
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedVAlues: ["SOLID", "OUTLINE", "GHOST"],
},
},
},
{
propertyName: "borderRadius",
label: "Border Radius",
helpText:
"Rounds the corners of the icon button's outer border edge",
controlType: "BORDER_RADIUS_OPTIONS",
options: [
ButtonBorderRadiusTypes.SHARP,
ButtonBorderRadiusTypes.ROUNDED,
],
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: ["CIRCLE", "SHARP", "ROUNDED"],
},
},
},
{
propertyName: "boxShadow",
label: "Box Shadow",
helpText:
"Enables you to cast a drop shadow from the frame of the widget",
controlType: "BOX_SHADOW_OPTIONS",
isBindProperty: false,
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: /^(?![<|{{]).+/,
},
},
},
{
propertyName: "iconName",
label: "Icon",
helpText: "Sets the icon to be used for the button",
controlType: "ICON_SELECT",
isBindProperty: false,
isTriggerProperty: false,
updateHook: (
props: ButtonWidgetProps,
propertyPath: string,
propertyValue: string,
) => {
const propertiesToUpdate = [{ propertyPath, propertyValue }];
if (!props.iconAlign) {
propertiesToUpdate.push({
propertyPath: "iconAlign",
propertyValue: Alignment.LEFT,
});
}
return propertiesToUpdate;
},
validation: {
type: ValidationTypes.TEXT,
},
},
{
propertyName: "iconAlign",
label: "Icon Alignment",
helpText: "Sets the icon alignment of the button",
controlType: "ICON_ALIGN",
isBindProperty: false,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: ["center", "left", "right"],
},
},
},
],
},
];
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
prevButtonStyle: "buttonStyle",
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
recaptchaToken: undefined,
@ -188,14 +380,22 @@ class ButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
getPageView() {
return (
<ButtonComponent
borderRadius={this.props.borderRadius}
boxShadow={this.props.boxShadow}
boxShadowColor={this.props.boxShadowColor}
buttonColor={this.props.buttonColor}
buttonStyle={this.props.buttonStyle}
buttonVariant={this.props.buttonVariant}
clickWithRecaptcha={this.clickWithRecaptchaBound}
disabled={this.props.isDisabled}
googleRecaptchaKey={this.props.googleRecaptchaKey}
handleRecaptchaV2Loading={this.handleRecaptchaV2Loading}
iconAlign={this.props.iconAlign}
iconName={this.props.iconName}
isDisabled={this.props.isDisabled}
isLoading={this.props.isLoading || this.state.isLoading}
key={this.props.widgetId}
onClick={!this.props.isDisabled ? this.onButtonClickBound : undefined}
prevButtonStyle={this.props.prevButtonStyle}
recaptchaV2={this.props.recaptchaV2}
text={this.props.text}
tooltip={this.props.tooltip}
@ -211,21 +411,23 @@ class ButtonWidget extends BaseWidget<ButtonWidgetProps, ButtonWidgetState> {
}
}
export type ButtonStyle =
| "PRIMARY_BUTTON"
| "SECONDARY_BUTTON"
| "SUCCESS_BUTTON"
| "DANGER_BUTTON";
export interface ButtonWidgetProps extends WidgetProps, WithMeta {
text?: string;
buttonStyle?: ButtonStyle;
onClick?: string;
isDisabled?: boolean;
isVisible?: boolean;
recaptchaV2?: boolean;
buttonType?: ButtonType;
googleRecaptchaKey?: string;
buttonStyle?: ButtonStyle;
prevButtonStyle?: ButtonStyle;
buttonVariant?: ButtonVariant;
buttonColor?: string;
borderRadius?: ButtonBorderRadius;
boxShadow?: ButtonBoxShadow;
boxShadowColor?: string;
iconName?: IconName;
iconAlign?: Alignment;
}
interface ButtonWidgetState extends WidgetState {

View File

@ -2,6 +2,7 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import ButtonComponent, {
ButtonStyle,
ButtonType,
} from "components/designSystems/blueprint/ButtonComponent";
import {
@ -194,8 +195,8 @@ class FormButtonWidget extends BaseWidget<
<ButtonComponent
buttonStyle={this.props.buttonStyle}
clickWithRecaptcha={this.clickWithRecaptchaBound}
disabled={disabled}
googleRecaptchaKey={this.props.googleRecaptchaKey}
isDisabled={disabled}
isLoading={this.props.isLoading || this.state.isLoading}
key={this.props.widgetId}
onClick={!disabled ? this.onButtonClickBound : undefined}
@ -213,11 +214,11 @@ class FormButtonWidget extends BaseWidget<
}
}
export type ButtonStyle =
| "PRIMARY_BUTTON"
| "SECONDARY_BUTTON"
| "SUCCESS_BUTTON"
| "DANGER_BUTTON";
// export type ButtonStyle =
// | "PRIMARY_BUTTON"
// | "SECONDARY_BUTTON"
// | "SUCCESS_BUTTON"
// | "DANGER_BUTTON";
export interface FormButtonWidgetProps extends WidgetProps, WithMeta {
text?: string;

View File

@ -8,11 +8,14 @@ import { ValidationTypes } from "constants/WidgetValidation";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import IconButtonComponent, {
ButtonBorderRadius,
ButtonBoxShadow,
ButtonStyle,
ButtonVariant,
} from "components/designSystems/appsmith/IconButtonComponent";
import {
ButtonBorderRadius,
ButtonBorderRadiusTypes,
} from "components/propertyControls/BorderRadiusOptionsControl";
import { ButtonBoxShadow } from "components/propertyControls/BoxShadowOptionsControl";
export interface IconButtonWidgetProps extends WidgetProps {
iconName?: IconName;
@ -99,6 +102,11 @@ class IconButtonWidget extends BaseWidget<IconButtonWidgetProps, WidgetState> {
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,
isTriggerProperty: false,
validation: {

View File

@ -16890,9 +16890,10 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
tinycolor2@^1.4.1:
tinycolor2@^1.4.1, tinycolor2@^1.4.2:
version "1.4.2"
resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
tmp@^0.0.33:
version "0.0.33"