* temp commit
* using onsubmit to continue using action on form
* added recaptcha site key to env example file
* moved the recaptcha lib loading logic to signup page
* removed unnecessary edit
* handle the case where the recaptcha token is not provided as env var
* added proper env var config for client
* recaptcha config for ansible
* recaptcha config for heroku
* recaptcha config for k8s
* updated app.json
* fixed the typos
* added more description for env vars
* removed api key
* minor typo fix
* added new integration button
* updated the add int default link
* added active and create new tabs
* added the empty components to tabs. will control the section manually.
* added proper grid for integrations page
* added vertical tabs
* Added secondary tabs to integrations page
* added separate page for new apis
* classname changes
* added new components for active queries, new queries etc.
* added a separate component for data source list
* adding screen component conditionally, to be showing upon user's choice
* 1. Added grid styling to datasource home
2. Added connect buttons to em
* fixed data source security banner
* updated the styling for new api page
* added tertiary menu for active integrations
* updated styling for active connections
* updated collapse component to work properly
* added show more option to active data sources
* Slash commands feature init commit
* Added more commands
* Introduced JSX to render custom commands
* Merge conflict fix
* Spacing changes
* removed apis/db tabs and replaced em with integrations tab
* removed the unnecessary + integrations btn
* Added slash commands button
* Adjust styles for better ui
* Ordered the action entries under integrations
* Added new datasource command
* updated the getURL with proper params
* updated the link of create datasource btn
* updated the back btn link from data source editor
* Show connect data cta in property pane
* Styling fixes
* Fix margin
* added scrollable content to create new
* added on click scroll to create new page
* fixed a bug, creating new datasource twice
* added new action creator for integrations.
* Minor changes to add new bindings command.
Changed ui behaviour of / button
* UI style change
* updated the query editor to match the over all theme
* updated the query editor tabs
* Added the run btn to empty response screens
* minor fix
* updated the bg color of api type drop down
* updated the url being visited after delete api/query
* removed log
* Insert binding command UI change
* More UI changes
* removed unnecessary junk from integrations editor index
* clean up, removed unnecessary files
* removed useless routes
* for debugger only checking if integrations editor
* Removed all the links for api/query home pages
* Move command actions to a saga
Added support to binding the data back to the widget when are new API is created from widget
* Added reverse binding for DB queries
* Show / button only on hover
* not routing to integrations on create query/api
* Hide actions from suggestions in action pages
* removed the query/datasource/api home pages
* Changes widget.data to widget in slash commands
* Show dependencies in property pane
* Fix warning
* fixed scrolling issue
* will show a list of queries and apis for action picker
* showing icons for each action under integrations
* Fix dropdown not showing up
* Minor refactoring.
Changed commands
* added a way to list data sources in action creators
* Update query page url
* cam show icons for datasources
* Removed unused code
* Feature/slash commands (#5002)
* Slash commands feature init commit
* Added more commands
* Introduced JSX to render custom commands
* Merge conflict fix
* Spacing changes
* Added slash commands button
* Adjust styles for better ui
* Added new datasource command
* Minor changes to add new bindings command.
Changed ui behaviour of / button
* UI style change
* Insert binding command UI change
* More UI changes
* Move command actions to a saga
Added support to binding the data back to the widget when are new API is created from widget
* Added reverse binding for DB queries
* Show / button only on hover
* Hide actions from suggestions in action pages
* Changes widget.data to widget in slash commands
* Minor refactoring.
Changed commands
* Removed unused code
* remove more unusued code
* Added support to generate new api from a datasource in quick commands
* Code correction to use types
* Refactored commands code
* Minor bug fixes
* Remove new integrations command for actions.
Fixed autocomplete not showing up
* Changes to prevent autocomplete trigger for navigation commands
* Prevent hinter execution when show hint is open already.
* Show hinter on focus
* Update text to be called in the omnibar
* updated the copy for empty active datasources
* Update url
* Fix text decoration
* updated the redirection for back btns
* Use themes
* Add cypress test
* fixed back btn nav
* fetching form configs for datasources
* a callback fixed
* Fix slash command not executed on click (#5540)
* Replace the value if not a string else append
* Log commands menu events
* updated mock data base navigation
* updated mock data base navigation
* updated the close editors and back buttons
* All back btns from editors will go back to data sources and back from data source will go back to canvas
* fixed bg colors
* minor styled updates
* removed margin from header of generic datasource
* warnings fixes
* If user is already on the location not redirecting em
* when editing, will check if the coming from data source and redirect accordingly
* updated redirection for newly created api/queries
* updated back btn for newly created datasources
* back for new curl goes to data sources
* Revert "[Fix] revert new nav (#5533)"
This reverts commit 1647815d
* remaining original reverted chagnes
* fixed the width of incoming/outgoing entity bar in property pane
* removing residue from resolved merge conflicts
* Fix widget icons not visible in dropdown menu
* minor fix to use proper integration URL
* updated the URLs for unified datasources
* converted back and close to btns from banners
* on accessing data source from sidebar, it'll always go to view mode
* updated the edit path for saas editors
* Added saved state for google sheet
* on google sheet delete redirecting to create new
* minor fix
* fixed the redirection call on saving a datasource
* removed save and test cmd as it wasn't needed
* Removing test cases to be fixed by Arun
* commenting more tests to be fixed by Arun
* updated call api cy command
* Fix extra margin issue
* fixed the update datasource saga
* fixed video spec
* Revert "commenting more tests to be fixed by Arun"
This reverts commit 42087a95ad77107401a1619e4c2d4c541a81d6c3.
* Revert "Removing test cases to be fixed by Arun"
This reverts commit f6fad67e558d22045114a90409428ef9b737478f.
* fixed the entity explorer query datasource spec
* cautious fix
* update widget locators
* fixed leave org test
* fixes for FormWidgets
* updated the image spec
* Use memo
* Fix debugger url checks
* for copy and delete widget pointing directly to svgs
* Fix entity text
* Fix styling and show tooltip for property pane dependencies
* removed the unnecessary callback
* added a separate saga to to redirect to new integrations using onSuccess
* Bug Fixes - New nav (#5629)
* will show scrollbar only on hover
* made mock data cards clickable
* fixed the grid view
* fixed the cursor position when clicking on / btn
* updated the hint for `/` command
* binding prompt will close on focus change
* hiding / command for api body
* hiding / command for query pane
* Added 2 new icons
* Fix cursor position on selecting a binding and clicking on the slash menu button
* trying out fix to copyWidget cy command
* removing zero width space characters from the property pane text
Co-authored-by: arunvjn <arun@appsmith.com>
Co-authored-by: Akash N <akash@codemonk.in>
Co-authored-by: arunvjn <32433245+arunvjn@users.noreply.github.com>
Co-authored-by: Rishabh Saxena <rishabh.robben@gmail.com>
455 lines
13 KiB
TypeScript
455 lines
13 KiB
TypeScript
import React from "react";
|
|
import {
|
|
CommonComponentProps,
|
|
hexToRgba,
|
|
ThemeProp,
|
|
Classes,
|
|
Variant,
|
|
} from "./common";
|
|
import styled, { css } from "styled-components";
|
|
import Icon, { IconName, IconSize } from "./Icon";
|
|
import Spinner from "./Spinner";
|
|
import { mediumButton, smallButton, largeButton } from "constants/DefaultTheme";
|
|
|
|
export enum Category {
|
|
primary = "primary",
|
|
secondary = "secondary",
|
|
tertiary = "tertiary",
|
|
}
|
|
|
|
export enum Size {
|
|
xxs = "xxs",
|
|
xs = "xs",
|
|
small = "small",
|
|
medium = "medium",
|
|
large = "large",
|
|
}
|
|
|
|
type stateStyleType = {
|
|
bgColorPrimary: string;
|
|
borderColorPrimary: string;
|
|
txtColorPrimary: string;
|
|
bgColorSecondary: string;
|
|
borderColorSecondary: string;
|
|
txtColorSecondary: string;
|
|
bgColorTertiary: string;
|
|
borderColorTertiary: string;
|
|
txtColorTertiary: string;
|
|
};
|
|
|
|
type BtnColorType = {
|
|
bgColor: string;
|
|
txtColor: string;
|
|
border: string;
|
|
};
|
|
|
|
type BtnFontType = {
|
|
buttonFont: any;
|
|
padding: string;
|
|
height: number;
|
|
};
|
|
|
|
type ButtonProps = CommonComponentProps & {
|
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
text?: string;
|
|
category?: Category;
|
|
variant?: Variant;
|
|
className?: string;
|
|
icon?: IconName;
|
|
size?: Size;
|
|
fill?: boolean;
|
|
href?: string;
|
|
tag?: "a" | "button";
|
|
type?: "submit" | "reset" | "button";
|
|
target?: string;
|
|
};
|
|
|
|
const defaultProps = {
|
|
category: Category.primary,
|
|
variant: Variant.info,
|
|
size: Size.small,
|
|
isLoading: false,
|
|
disabled: false,
|
|
fill: undefined,
|
|
tag: "a",
|
|
};
|
|
|
|
const getDisabledStyles = (props: ThemeProp & ButtonProps) => {
|
|
const variant = props.variant || defaultProps.variant;
|
|
const category = props.category || defaultProps.category;
|
|
|
|
const stylesByCategory = {
|
|
[Category.primary]: {
|
|
txtColorPrimary: props.theme.colors.button.disabledText,
|
|
bgColorPrimary: props.theme.colors[variant].darkest,
|
|
borderColorPrimary: props.theme.colors[variant].darkest,
|
|
},
|
|
[Category.secondary]: {
|
|
txtColorSecondary: props.theme.colors.button.disabledText,
|
|
bgColorSecondary: props.theme.colors[variant].darkest,
|
|
borderColorSecondary: props.theme.colors[variant].darker,
|
|
},
|
|
[Category.tertiary]: {
|
|
txtColorTertiary: props.theme.colors.button.disabledText,
|
|
bgColorTertiary: props.theme.colors.tertiary.darker,
|
|
borderColorTertiary: props.theme.colors.tertiary.dark,
|
|
},
|
|
};
|
|
|
|
return stylesByCategory[category];
|
|
};
|
|
|
|
const getMainStateStyles = (props: ThemeProp & ButtonProps) => {
|
|
const variant = props.variant || defaultProps.variant;
|
|
const category = props.category || defaultProps.category;
|
|
|
|
const stylesByCategory = {
|
|
[Category.primary]: {
|
|
bgColorPrimary: props.theme.colors[variant].main,
|
|
borderColorPrimary: props.theme.colors[variant].main,
|
|
txtColorPrimary: "#fff",
|
|
},
|
|
[Category.secondary]: {
|
|
borderColorSecondary: props.theme.colors[variant].main,
|
|
txtColorSecondary: props.theme.colors[variant].main,
|
|
bgColorSecondary: "transparent",
|
|
},
|
|
[Category.tertiary]: {
|
|
bgColorTertiary: "transparent",
|
|
borderColorTertiary: props.theme.colors.tertiary.main,
|
|
txtColorTertiary: props.theme.colors.tertiary.main,
|
|
},
|
|
};
|
|
|
|
return stylesByCategory[category];
|
|
};
|
|
|
|
const getHoverStateStyles = (props: ThemeProp & ButtonProps) => {
|
|
const variant = props.variant || defaultProps.variant;
|
|
const category = props.category || defaultProps.category;
|
|
|
|
const stylesByCategory = {
|
|
[Category.primary]: {
|
|
bgColorPrimary: props.theme.colors[variant].dark,
|
|
borderColorPrimary: props.theme.colors[variant].dark,
|
|
txtColorPrimary: "#fff",
|
|
},
|
|
[Category.secondary]: {
|
|
bgColorSecondary: hexToRgba(props.theme.colors[variant].main, 0.1),
|
|
txtColorSecondary: props.theme.colors[variant].main,
|
|
borderColorSecondary: props.theme.colors[variant].main,
|
|
},
|
|
[Category.tertiary]: {
|
|
bgColorTertiary: hexToRgba(props.theme.colors.tertiary.main, 0.1),
|
|
borderColorTertiary: props.theme.colors.tertiary.main,
|
|
txtColorTertiary: props.theme.colors.tertiary.main,
|
|
},
|
|
};
|
|
|
|
return stylesByCategory[category];
|
|
};
|
|
|
|
const getActiveStateStyles = (props: ThemeProp & ButtonProps) => {
|
|
const variant = props.variant || defaultProps.variant;
|
|
const category = props.category || defaultProps.category;
|
|
|
|
const stylesByCategory = {
|
|
[Category.primary]: {
|
|
bgColorPrimary: props.theme.colors[variant].dark,
|
|
borderColorPrimary: props.theme.colors[variant].main,
|
|
txtColorPrimary: "#fff",
|
|
},
|
|
[Category.secondary]: {
|
|
bgColorSecondary: hexToRgba(props.theme.colors[variant].main, 0.1),
|
|
txtColorSecondary: props.theme.colors[variant].light,
|
|
borderColorSecondary: props.theme.colors[variant].light,
|
|
},
|
|
[Category.tertiary]: {
|
|
bgColorTertiary: hexToRgba(props.theme.colors.tertiary.main, 0.1),
|
|
borderColorTertiary: props.theme.colors.tertiary.light,
|
|
txtColorTertiary: props.theme.colors.tertiary.light,
|
|
},
|
|
};
|
|
|
|
return stylesByCategory[category];
|
|
};
|
|
|
|
const stateStyles = (
|
|
props: ThemeProp & ButtonProps,
|
|
stateArg: string,
|
|
): stateStyleType => {
|
|
const styles = {
|
|
bgColorPrimary: "",
|
|
borderColorPrimary: "",
|
|
txtColorPrimary: "",
|
|
bgColorSecondary: "",
|
|
borderColorSecondary: "",
|
|
txtColorSecondary: "",
|
|
bgColorTertiary: "",
|
|
borderColorTertiary: "",
|
|
txtColorTertiary: "",
|
|
};
|
|
const state =
|
|
props.isLoading || props.disabled
|
|
? "disabled"
|
|
: (stateArg as keyof typeof stylesByState);
|
|
const stylesByState = {
|
|
disabled: getDisabledStyles(props),
|
|
main: getMainStateStyles(props),
|
|
hover: getHoverStateStyles(props),
|
|
active: getActiveStateStyles(props),
|
|
};
|
|
|
|
return {
|
|
...styles,
|
|
...stylesByState[state],
|
|
};
|
|
};
|
|
|
|
const btnColorStyles = (
|
|
props: ThemeProp & ButtonProps,
|
|
state: string,
|
|
): BtnColorType => {
|
|
let bgColor = "",
|
|
txtColor = "",
|
|
border = "";
|
|
switch (props.category) {
|
|
case Category.primary:
|
|
bgColor = stateStyles(props, state).bgColorPrimary;
|
|
txtColor = stateStyles(props, state).txtColorPrimary;
|
|
border = `2px solid ${stateStyles(props, state).borderColorPrimary}`;
|
|
break;
|
|
case Category.secondary:
|
|
bgColor = stateStyles(props, state).bgColorSecondary;
|
|
txtColor = stateStyles(props, state).txtColorSecondary;
|
|
border = `2px solid ${stateStyles(props, state).borderColorSecondary}`;
|
|
break;
|
|
case Category.tertiary:
|
|
bgColor = stateStyles(props, state).bgColorTertiary;
|
|
txtColor = stateStyles(props, state).txtColorTertiary;
|
|
border = `2px solid ${stateStyles(props, state).borderColorTertiary}`;
|
|
break;
|
|
}
|
|
return { bgColor, txtColor, border };
|
|
};
|
|
|
|
const getPaddingBySize = (props: ThemeProp & ButtonProps) => {
|
|
const paddingBySize = {
|
|
[Size.small]: `0px ${props.theme.spaces[3]}px`,
|
|
[Size.medium]: `0px ${props.theme.spaces[7]}px`,
|
|
[Size.large]: `0px ${props.theme.spaces[12] - 4}px`,
|
|
};
|
|
const paddingBySizeForJustIcon = {
|
|
[Size.small]: `0px ${props.theme.spaces[1]}px`,
|
|
[Size.medium]: `0px ${props.theme.spaces[2]}px`,
|
|
[Size.large]: `0px ${props.theme.spaces[3]}px`,
|
|
};
|
|
|
|
const isIconOnly = !props.text && props.icon;
|
|
const paddingConfig = isIconOnly ? paddingBySizeForJustIcon : paddingBySize;
|
|
|
|
const iSizeInConfig =
|
|
Object.keys(paddingConfig).indexOf(props.size || "") !== -1;
|
|
const size: any = props.size && iSizeInConfig ? props.size : Size.small;
|
|
|
|
return paddingConfig[size as keyof typeof paddingConfig];
|
|
};
|
|
|
|
const getHeightBySize = (props: ThemeProp & ButtonProps) => {
|
|
const heightBySize = {
|
|
[Size.small]: 20,
|
|
[Size.medium]: 30,
|
|
[Size.large]: 38,
|
|
};
|
|
|
|
const iSizeInConfig =
|
|
Object.keys(heightBySize).indexOf(props.size || "") !== -1;
|
|
const size: any = props.size && iSizeInConfig ? props.size : Size.small;
|
|
|
|
return heightBySize[size as keyof typeof heightBySize];
|
|
};
|
|
|
|
const getBtnFontBySize = (props: ThemeProp & ButtonProps) => {
|
|
const fontBySize = {
|
|
[Size.small]: smallButton,
|
|
[Size.medium]: mediumButton,
|
|
[Size.large]: largeButton,
|
|
};
|
|
|
|
const iSizeInConfig =
|
|
Object.keys(fontBySize).indexOf(props.size || "") !== -1;
|
|
const size: any = props.size && iSizeInConfig ? props.size : Size.small;
|
|
|
|
return fontBySize[size as keyof typeof fontBySize];
|
|
};
|
|
|
|
const btnFontStyles = (props: ThemeProp & ButtonProps): BtnFontType => {
|
|
const padding = getPaddingBySize(props);
|
|
const height = getHeightBySize(props);
|
|
const buttonFont = getBtnFontBySize(props);
|
|
|
|
return { buttonFont, padding, height };
|
|
};
|
|
|
|
const ButtonStyles = css<ThemeProp & ButtonProps>`
|
|
width: ${(props) => (props.fill ? "100%" : "auto")};
|
|
height: ${(props) => btnFontStyles(props).height}px;
|
|
border: none;
|
|
text-decoration: none;
|
|
outline: none;
|
|
text-transform: uppercase;
|
|
background-color: ${(props) => btnColorStyles(props, "main").bgColor};
|
|
color: ${(props) => btnColorStyles(props, "main").txtColor};
|
|
border: ${(props) => btnColorStyles(props, "main").border};
|
|
border-radius: 0;
|
|
${(props) => btnFontStyles(props).buttonFont};
|
|
padding: ${(props) => btnFontStyles(props).padding};
|
|
.${Classes.ICON} {
|
|
margin-right: ${(props) =>
|
|
props.text && props.icon ? `${props.theme.spaces[2] - 1}px` : `0`};
|
|
path {
|
|
fill: ${(props) => btnColorStyles(props, "main").txtColor};
|
|
}
|
|
}
|
|
&:hover {
|
|
text-decoration: none;
|
|
background-color: ${(props) => btnColorStyles(props, "hover").bgColor};
|
|
color: ${(props) => btnColorStyles(props, "hover").txtColor};
|
|
border: ${(props) => btnColorStyles(props, "hover").border};
|
|
cursor: ${(props) =>
|
|
props.isLoading || props.disabled ? `not-allowed` : `pointer`};
|
|
.${Classes.ICON} {
|
|
margin-right: ${(props) =>
|
|
props.text && props.icon ? `${props.theme.spaces[2] - 1}px` : `0`};
|
|
path {
|
|
fill: ${(props) => btnColorStyles(props, "hover").txtColor};
|
|
}
|
|
}
|
|
}
|
|
font-style: normal;
|
|
&:active {
|
|
background-color: ${(props) => btnColorStyles(props, "active").bgColor};
|
|
color: ${(props) => btnColorStyles(props, "active").txtColor};
|
|
border: ${(props) => btnColorStyles(props, "active").border};
|
|
cursor: ${(props) =>
|
|
props.isLoading || props.disabled ? `not-allowed` : `pointer`};
|
|
.${Classes.ICON} {
|
|
path {
|
|
fill: ${(props) => btnColorStyles(props, "active").txtColor};
|
|
}
|
|
}
|
|
}
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
.${Classes.SPINNER} {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
`;
|
|
|
|
const StyledButton = styled("button")`
|
|
${ButtonStyles}
|
|
`;
|
|
|
|
const StyledLinkButton = styled("a")`
|
|
${ButtonStyles}
|
|
`;
|
|
|
|
export const VisibilityWrapper = styled.div`
|
|
visibility: hidden;
|
|
`;
|
|
|
|
const IconSizeProp = (size?: Size) => {
|
|
const sizeMapping = {
|
|
[Size.xxs]: IconSize.XXS,
|
|
[Size.xs]: IconSize.XS,
|
|
[Size.small]: IconSize.SMALL,
|
|
[Size.medium]: IconSize.MEDIUM,
|
|
[Size.large]: IconSize.LARGE,
|
|
};
|
|
|
|
return size ? sizeMapping[size] : IconSize.SMALL;
|
|
};
|
|
|
|
function TextLoadingState({ text }: { text?: string }) {
|
|
return <VisibilityWrapper>{text}</VisibilityWrapper>;
|
|
}
|
|
|
|
function IconLoadingState({ icon, size }: { size?: Size; icon?: IconName }) {
|
|
return <Icon invisible name={icon} size={IconSizeProp(size)} />;
|
|
}
|
|
|
|
const getIconContent = (props: ButtonProps) =>
|
|
props.icon ? (
|
|
props.isLoading ? (
|
|
<IconLoadingState {...props} />
|
|
) : (
|
|
<Icon name={props.icon} size={IconSizeProp(props.size)} />
|
|
)
|
|
) : null;
|
|
|
|
const getTextContent = (props: ButtonProps) =>
|
|
props.text ? (
|
|
props.isLoading ? (
|
|
<TextLoadingState text={props.text} />
|
|
) : (
|
|
props.text
|
|
)
|
|
) : null;
|
|
|
|
const getButtonContent = (props: ButtonProps) => (
|
|
<>
|
|
{getIconContent(props)}
|
|
<span>{getTextContent(props)}</span>
|
|
{props.isLoading ? <Spinner size={IconSizeProp(props.size)} /> : null}
|
|
</>
|
|
);
|
|
|
|
function ButtonComponent(props: ButtonProps) {
|
|
return (
|
|
<StyledButton
|
|
className={props.className}
|
|
data-cy={props.cypressSelector}
|
|
{...props}
|
|
onClick={(e: React.MouseEvent<HTMLElement>) =>
|
|
props.onClick && props.onClick(e)
|
|
}
|
|
>
|
|
{getButtonContent(props)}
|
|
</StyledButton>
|
|
);
|
|
}
|
|
|
|
function LinkButtonComponent(props: ButtonProps) {
|
|
return (
|
|
<StyledLinkButton
|
|
className={props.className}
|
|
data-cy={props.cypressSelector}
|
|
href={props.href}
|
|
{...props}
|
|
onClick={(e: React.MouseEvent<HTMLElement>) =>
|
|
props.onClick && props.onClick(e)
|
|
}
|
|
>
|
|
{getButtonContent(props)}
|
|
</StyledLinkButton>
|
|
);
|
|
}
|
|
|
|
function Button(props: ButtonProps) {
|
|
return props.tag === "button" ? (
|
|
<ButtonComponent {...props} />
|
|
) : (
|
|
<LinkButtonComponent {...props} />
|
|
);
|
|
}
|
|
|
|
export default Button;
|
|
|
|
Button.defaultProps = defaultProps;
|