Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Automated Github Action 2020-10-12 08:08:31 +00:00
commit c2ce945c29
23 changed files with 606 additions and 293 deletions

View File

@ -25,10 +25,10 @@
"inviteUserMembersPage": "[data-cy=t--invite-users]",
"email": "//input[@type='email']",
"selectRole": "//span[text()='Select a role']",
"adminRole": "//div[@class='bp3-overlay bp3-overlay-open']//div[contains(text(),'Administrator')]",
"viewerRole": "//div[@class='bp3-overlay bp3-overlay-open']//div[contains(text(),'Viewer')]",
"developerRole": "//div[@class='bp3-overlay bp3-overlay-open']//div[contains(text(),'Developer')]",
"inviteBtn": "//span[text()='Invite']",
"adminRole": "//div[@class='label-title']//span[text()='Administrator']",
"viewerRole": "//div[@class='label-title']//span[text()='App Viewer']",
"developerRole": "//div[@class='label-title']//span[text()='Developer']",
"inviteBtn": "//button[text()='Invite']",
"manageUsers": ".manageUsers",
"DeleteBtn": "[data-cy=t--deleteUser]",
"ShareBtn": "//a[text()='Share']",

View File

@ -0,0 +1,4 @@
<svg width="9" height="8" viewBox="0 0 9 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 4H5.4" stroke="#9F9F9F" stroke-width="1.5" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.40002 0L9.00002 4L4.40002 8V0Z" fill="#9F9F9F"/>
</svg>

After

Width:  |  Height:  |  Size: 281 B

View File

@ -1,6 +1,6 @@
import React from "react";
import { CommonComponentProps, hexToRgba, ThemeProp, Classes } from "./common";
import styled from "styled-components";
import styled, { css } from "styled-components";
import Icon, { IconName, IconSize } from "./Icon";
import Spinner from "./Spinner";
import { mediumButton, smallButton, largeButton } from "constants/DefaultTheme";
@ -58,6 +58,7 @@ type ButtonProps = CommonComponentProps & {
size?: Size;
fill?: boolean;
href?: string;
tag?: "a" | "button";
};
const stateStyles = (
@ -243,7 +244,7 @@ const btnFontStyles = (props: ThemeProp & ButtonProps): BtnFontType => {
return { buttonFont, padding, height };
};
const StyledButton = styled("a")<ThemeProp & ButtonProps>`
const ButtonStyles = css<ThemeProp & ButtonProps>`
width: ${props => (props.fill ? "100%" : "auto")};
height: ${props => btnFontStyles(props).height}px;
border: none;
@ -295,7 +296,7 @@ const StyledButton = styled("a")<ThemeProp & ButtonProps>`
align-items: center;
justify-content: center;
position: relative;
.new-spinner {
.${Classes.SPINNER} {
position: absolute;
left: 0;
right: 0;
@ -304,6 +305,14 @@ const StyledButton = styled("a")<ThemeProp & ButtonProps>`
}
`;
const StyledButton = styled("button")`
${ButtonStyles}
`;
const StyledLinkButton = styled("a")`
${ButtonStyles}
`;
export const VisibilityWrapper = styled.div`
visibility: hidden;
`;
@ -327,6 +336,7 @@ Button.defaultProps = {
isLoading: false,
disabled: false,
fill: false,
tag: "a",
};
function Button(props: ButtonProps) {
@ -336,16 +346,8 @@ function Button(props: ButtonProps) {
const TextLoadingState = <VisibilityWrapper>{props.text}</VisibilityWrapper>;
return (
<StyledButton
href={props.href}
className={props.className}
data-cy={props.cypressSelector}
{...props}
onClick={(e: React.MouseEvent<HTMLElement>) =>
props.onClick && props.onClick(e)
}
>
const buttonContent = (
<>
{props.icon ? (
props.isLoading ? (
IconLoadingState
@ -357,8 +359,37 @@ function Button(props: ButtonProps) {
{props.text ? (props.isLoading ? TextLoadingState : props.text) : null}
{props.isLoading ? <Spinner size={IconSizeProp(props.size)} /> : null}
</StyledButton>
</>
);
if (props.tag === "button") {
return (
<StyledButton
className={props.className}
data-cy={props.cypressSelector}
{...props}
onClick={(e: React.MouseEvent<HTMLElement>) =>
props.onClick && props.onClick(e)
}
>
{buttonContent}
</StyledButton>
);
} else {
return (
<StyledLinkButton
href={props.href}
className={props.className}
data-cy={props.cypressSelector}
{...props}
onClick={(e: React.MouseEvent<HTMLElement>) =>
props.onClick && props.onClick(e)
}
>
{buttonContent}
</StyledLinkButton>
);
}
}
export default Button;

View File

@ -2,13 +2,10 @@ import React from "react";
import { CommonComponentProps, Classes } from "./common";
import Text, { TextType } from "./Text";
import styled from "styled-components";
export type Variant = "note" | "warning";
export type Background = "dark" | "light";
import { Variant } from "./Button";
type CalloutProps = CommonComponentProps & {
variant?: Variant;
background?: Background;
text: string;
fill?: boolean;
};
@ -16,14 +13,11 @@ type CalloutProps = CommonComponentProps & {
const CalloutContainer = styled.div<{
variant?: Variant;
fill?: boolean;
background?: Background;
}>`
padding: ${props => props.theme.spaces[5]}px
${props => props.theme.spaces[11] + 1}px;
background: ${props =>
props.variant && props.background
? props.theme.colors.callout[props.variant][props.background].bgColor
: null};
props.variant ? props.theme.colors.callout[props.variant].bgColor : null};
height: 42px;
${props =>
@ -37,25 +31,18 @@ const CalloutContainer = styled.div<{
.${Classes.TEXT} {
color: ${props =>
props.variant && props.background
? props.theme.colors.callout[props.variant][props.background].color
: null};
props.variant ? props.theme.colors.callout[props.variant].color : null};
}
`;
Callout.defaultProps = {
fill: false,
variant: "note",
background: "dark",
};
function Callout(props: CalloutProps) {
return (
<CalloutContainer
variant={props.variant}
background={props.background}
fill={props.fill}
>
<CalloutContainer variant={props.variant} fill={props.fill}>
<Text type={TextType.P2}>{props.text}</Text>
</CalloutContainer>
);

View File

@ -6,38 +6,42 @@ import Text, { TextType } from "./Text";
type DropdownOption = {
label?: string;
value: string;
value?: string;
id?: string;
icon?: IconName;
onSelect?: (option: DropdownOption) => void;
children?: DropdownOption[];
onSelect?: (value?: string) => void;
};
type DropdownProps = CommonComponentProps & {
options: DropdownOption[];
selected: DropdownOption;
onSelect?: (value?: string) => void;
};
const DropdownContainer = styled.div`
width: 260px;
position: relative;
`;
const Selected = styled.div<{ isOpen: boolean; disabled?: boolean }>`
height: 38px;
padding: ${props => props.theme.spaces[4]}px
${props => props.theme.spaces[6]}px;
background: ${props =>
props.disabled
? props.theme.colors.dropdown.header.disabledText
: props.theme.colors.dropdown.header.disabledBg};
? props.theme.colors.dropdown.header.disabledBg
: props.theme.colors.dropdown.header.bg};
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
cursor: pointer;
${props =>
props.isOpen && !props.disabled
? `border: 1.2px solid ${props.theme.colors.info.main}`
: null};
props.isOpen
? `border: 1px solid ${props.theme.colors.info.main}`
: props.disabled
? `border: 1px solid ${props.theme.colors.dropdown.header.disabledBg}`
: `border: 1px solid ${props.theme.colors.dropdown.header.bg}`};
${props =>
props.isOpen && !props.disabled ? "box-sizing: border-box" : null};
${props =>
@ -47,12 +51,16 @@ const Selected = styled.div<{ isOpen: boolean; disabled?: boolean }>`
.${Classes.TEXT} {
${props =>
props.disabled
? `color: ${props.theme.colors.dropdown.text}`
: `color: ${props.theme.colors.dropdown.disabledText}`};
? `color: ${props.theme.colors.dropdown.header.disabledText}`
: `color: ${props.theme.colors.dropdown.header.text}`};
}
`;
const DropdownWrapper = styled.div`
position: absolute;
top: 38px;
left: 0px;
z-index: 1;
margin-top: ${props => props.theme.spaces[2] - 1}px;
background: ${props => props.theme.colors.dropdown.menuBg};
box-shadow: 0px 12px 28px ${props => props.theme.colors.dropdown.menuShadow};
@ -117,6 +125,7 @@ const LabelWrapper = styled.div<{ label?: string }>`
`;
export default function Dropdown(props: DropdownProps) {
const { onSelect } = { ...props };
const [isOpen, setIsOpen] = useState<boolean>(false);
const [selected, setSelected] = useState<DropdownOption>(props.selected);
@ -124,11 +133,15 @@ export default function Dropdown(props: DropdownProps) {
setSelected(props.selected);
}, [props.selected]);
const optionClickHandler = useCallback((option: DropdownOption) => {
setSelected(option);
setIsOpen(false);
option.onSelect && option.onSelect(option);
}, []);
const optionClickHandler = useCallback(
(option: DropdownOption) => {
setSelected(option);
setIsOpen(false);
onSelect && onSelect(option.value);
option.onSelect && option.onSelect(option.value);
},
[onSelect],
);
return (
<DropdownContainer
@ -142,7 +155,7 @@ export default function Dropdown(props: DropdownProps) {
onClick={() => setIsOpen(!isOpen)}
>
<Text type={TextType.P1}>{selected.value}</Text>
<Icon name="downArrow" size={IconSize.SMALL} />
<Icon name="downArrow" size={IconSize.XXS} />
</Selected>
{isOpen && !props.disabled ? (
@ -159,7 +172,9 @@ export default function Dropdown(props: DropdownProps) {
) : null}
<LabelWrapper label={option.label}>
{option.label ? (
<Text type={TextType.H5}>{option.value}</Text>
<div className="label-title">
<Text type={TextType.H5}>{option.value}</Text>
</div>
) : (
<Text type={TextType.P1}>{option.value}</Text>
)}

View File

@ -128,15 +128,22 @@ const IconWrapper = styled.div`
`;
export const EditableText = (props: EditableTextProps) => {
const [isEditing, setIsEditing] = useState(!!props.isEditingDefault);
const [value, setValue] = useState(props.defaultValue);
const [lastValidValue, setLastValidValue] = useState(props.defaultValue);
const {
onBlur,
onTextChanged,
isInvalid: inputValidation,
defaultValue,
isEditingDefault,
} = props;
const [isEditing, setIsEditing] = useState(!!isEditingDefault);
const [value, setValue] = useState(defaultValue);
const [lastValidValue, setLastValidValue] = useState(defaultValue);
const [isInvalid, setIsInvalid] = useState<string | boolean>(false);
const [changeStarted, setChangeStarted] = useState<boolean>(false);
const [savingState, setSavingState] = useState<SavingState>(
SavingState.NOT_STARTED,
);
const valueRef = React.useRef(props.defaultValue);
const valueRef = React.useRef(defaultValue);
useEffect(() => {
setSavingState(props.savingState);
@ -144,18 +151,19 @@ export const EditableText = (props: EditableTextProps) => {
useEffect(() => {
return () => {
props.onBlur(valueRef.current);
onBlur(valueRef.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setValue(props.defaultValue);
setIsEditing(!!props.isEditingDefault);
}, [props.defaultValue, props.isEditingDefault]);
setValue(defaultValue);
setIsEditing(!!isEditingDefault);
}, [defaultValue, isEditingDefault]);
useEffect(() => {
if (props.forceDefault === true) setValue(props.defaultValue);
}, [props.forceDefault, props.defaultValue]);
if (props.forceDefault === true) setValue(defaultValue);
}, [props.forceDefault, defaultValue]);
const themeDetails = useSelector(getThemeDetails);
const bgColor = useMemo(
@ -167,35 +175,41 @@ export const EditableText = (props: EditableTextProps) => {
const editMode = useCallback(
(e: React.MouseEvent) => {
setIsEditing(true);
const errorMessage =
props.isInvalid && props.isInvalid(props.defaultValue);
const errorMessage = inputValidation && inputValidation(defaultValue);
setIsInvalid(errorMessage ? errorMessage : false);
e.preventDefault();
e.stopPropagation();
},
[props],
[inputValidation, defaultValue],
);
const onConfirm = useCallback(
(_value: string) => {
if (savingState === SavingState.ERROR || isInvalid) {
setValue(lastValidValue);
props.onBlur(lastValidValue);
onBlur(lastValidValue);
setSavingState(SavingState.NOT_STARTED);
} else if (changeStarted) {
props.onTextChanged(_value);
props.onBlur(_value);
onTextChanged(_value);
onBlur(_value);
}
setIsEditing(false);
setChangeStarted(false);
},
[changeStarted, lastValidValue, props.onBlur, props.onTextChanged],
[
changeStarted,
savingState,
isInvalid,
lastValidValue,
onBlur,
onTextChanged,
],
);
const onInputchange = useCallback(
(_value: string) => {
const finalVal: string = _value;
const errorMessage = props.isInvalid && props.isInvalid(finalVal);
const errorMessage = inputValidation && inputValidation(finalVal);
const error = errorMessage ? errorMessage : false;
if (!error) {
setLastValidValue(finalVal);
@ -205,7 +219,7 @@ export const EditableText = (props: EditableTextProps) => {
setIsInvalid(error);
setChangeStarted(true);
},
[props.isInvalid],
[inputValidation],
);
const iconName =

View File

@ -18,6 +18,7 @@ import { ReactComponent as ViewAllIcon } from "assets/icons/ads/view-all.svg";
import { ReactComponent as ContextMenuIcon } from "assets/icons/ads/context-menu.svg";
import { ReactComponent as DuplicateIcon } from "assets/icons/ads/duplicate.svg";
import { ReactComponent as LogoutIcon } from "assets/icons/ads/logout.svg";
import { ReactComponent as ManageIcon } from "assets/icons/ads/manage.svg";
import styled from "styled-components";
import { CommonComponentProps, Classes } from "./common";
import { noop } from "lodash";
@ -88,6 +89,7 @@ export const IconCollection = [
"context-menu",
"duplicate",
"logout",
"manage",
] as const;
export type IconName = typeof IconCollection[number];
@ -97,6 +99,7 @@ const IconWrapper = styled.span<IconProps>`
outline: none;
}
display: flex;
align-items: center;
svg {
width: ${props => sizeHandler(props.size)}px;
height: ${props => sizeHandler(props.size)}px;
@ -189,6 +192,9 @@ const Icon = (props: IconProps & CommonComponentProps) => {
case "logout":
returnIcon = <LogoutIcon />;
break;
case "manage":
returnIcon = <ManageIcon />;
break;
default:
returnIcon = null;
break;

View File

@ -1,23 +1,35 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { TagInput } from "@blueprintjs/core";
import {
Intent,
IntentColors,
getColorWithOpacity,
} from "constants/DefaultTheme";
import { Classes, TagInput } from "@blueprintjs/core";
import { Intent } from "constants/DefaultTheme";
const TagInputWrapper = styled.div<{ intent?: Intent }>`
margin-right: 8px;
&&& {
.bp3-tag {
color: ${props => props.theme.colors.textDefault};
font-size: ${props => props.theme.fontSizes[3]}px;
background: ${props =>
props.intent
? getColorWithOpacity(IntentColors[props.intent], 0.2)
: getColorWithOpacity(IntentColors.none, 0.2)};
border: 1px solid
${props =>
props.intent ? IntentColors[props.intent] : IntentColors.none};
.${Classes.TAG_INPUT} {
background-color: ${props => props.theme.colors.tagInput.bg};
min-height: 38px;
border: 1px solid ${props => props.theme.colors.tagInput.bg};
border-radius: 0px;
}
.${Classes.TAG_INPUT}.${Classes.ACTIVE} {
border: 1px solid ${props => props.theme.colors.info.main};
box-shadow: ${props => props.theme.colors.tagInput.shadow};
}
.${Classes.INPUT_GHOST} {
color: ${props => props.theme.colors.tagInput.text};
&::placeholder {
color: ${props => props.theme.colors.tagInput.placeholder};
}
}
.${Classes.TAG} {
padding: 3px 10px;
color: ${props => props.theme.colors.tagInput.tag.text};
background-color: ${props => props.theme.colors.info.main};
border-radius: 0px;
font-size: 11px;
line-height: 13px;
letter-spacing: 0.4px;
}
}
`;

View File

@ -39,6 +39,7 @@ export type TextInputProps = CommonComponentProps & {
defaultValue?: string;
validator?: (value: string) => { isValid: boolean; message: string };
onChange?: (value: string) => void;
readOnly?: boolean;
};
type boxReturnType = {
@ -61,6 +62,11 @@ const boxStyles = (
color = theme.colors.textInput.disable.text;
borderColor = theme.colors.textInput.disable.border;
}
if (props.readOnly) {
bgColor = theme.colors.textInput.readOnly.bg;
color = theme.colors.textInput.readOnly.text;
borderColor = theme.colors.textInput.readOnly.border;
}
if (!isValid) {
bgColor = hexToRgba(theme.colors.danger.main, 0.1);
color = theme.colors.danger.main;
@ -77,28 +83,35 @@ const StyledInput = styled.input<
outline: 0;
box-shadow: none;
border: 1px solid ${props => props.inputStyle.borderColor};
padding: ${props => props.theme.spaces[4]}px
${props => props.theme.spaces[6]}px;
padding: 0px ${props => props.theme.spaces[6]}px;
height: 38px;
background-color: ${props => props.inputStyle.bgColor};
color: ${props => props.inputStyle.color};
&::placeholder {
color: ${props => props.theme.colors.textInput.placeholder};
}
&:focus {
border: 1px solid
${props =>
props.isValid
? props.theme.colors.info.main
: props.theme.colors.danger.main};
box-shadow: ${props =>
props.isValid
? "0px 0px 4px 4px rgba(203, 72, 16, 0.18)"
: "0px 0px 4px 4px rgba(226, 44, 44, 0.18)"};
}
&:disabled {
cursor: not-allowed;
}
${props =>
!props.readOnly
? `
&:focus {
border: 1px solid
${
props.isValid
? props.theme.colors.info.main
: props.theme.colors.danger.main
};
box-shadow: ${
props.isValid
? "0px 0px 4px 4px rgba(203, 72, 16, 0.18)"
: "0px 0px 4px 4px rgba(226, 44, 44, 0.18)"
};
}
`
: null};
`;
const InputWrapper = styled.div`
@ -172,6 +185,7 @@ const TextInput = forwardRef(
{...props}
placeholder={props.placeholder}
onChange={memoizedChangeHandler}
readOnly={props.readOnly}
/>
{ErrorMessage}
</InputWrapper>

View File

@ -1,4 +1,4 @@
import { CommonComponentProps, Classes, lighten, darken } from "./common";
import { CommonComponentProps, Classes } from "./common";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Spinner from "./Spinner";

View File

@ -1,36 +1,17 @@
import React, { createRef, useState } from "react";
import styled from "styled-components";
import copy from "copy-to-clipboard";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import TextInput from "components/ads/TextInput";
import Button, { Category, Size } from "components/ads/Button";
const Wrapper = styled.div`
display: flex;
flex-direction: row;
`;
const StyledInput = styled.input`
flex: 1;
border: 1px solid #d3dee3;
border-right: none;
padding: 6px 12px;
font-size: 14px;
color: #768896;
border-radius: 4px 0 0 4px;
width: 90%;
overflow: hidden;
`;
const SelectButton = styled(BaseButton)`
&&&& {
max-width: 70px;
margin: 0 0px;
min-height: 32px;
border-radius: 0px 4px 4px 0px;
font-weight: bold;
background-color: #f6f7f8;
font-size: 14px;
&.bp3-button {
padding: 0px 0px;
}
div {
flex-basis: calc(100% - 110px);
}
a {
flex-basis: 110px;
}
`;
@ -54,21 +35,23 @@ const CopyToClipboard = (props: any) => {
};
return (
<Wrapper>
<StyledInput
type="text"
<TextInput
fill
ref={copyURLInput}
readOnly
onClick={() => {
onChange={() => {
selectText();
}}
value={copyText}
defaultValue={copyText}
/>
<SelectButton
<Button
text={isCopied ? "Copied" : "Copy"}
accent="secondary"
category={Category.tertiary}
onClick={() => {
copyToClipboard(copyText);
}}
size={Size.large}
/>
</Wrapper>
);

View File

@ -5,12 +5,36 @@ import { isPermitted } from "pages/Applications/permissionHelpers";
const StyledDialog = styled(Dialog)<{ setMaxWidth?: boolean }>`
&& {
background: white;
& .bp3-dialog-header {
padding: ${props => props.theme.spaces[4]}px
${props => props.theme.spaces[4]}px;
border-radius: 0px;
padding-bottom: 5px;
background: ${props => props.theme.colors.modal.bg};
width: 640px;
& .${Classes.DIALOG_HEADER} {
padding: ${props => props.theme.spaces[4]}px;
background: ${props => props.theme.colors.modal.bg};
box-shadow: none;
.${Classes.ICON} {
color: ${props => props.theme.colors.modal.iconColor};
}
.${Classes.HEADING} {
color: ${props => props.theme.colors.modal.headerText};
display: flex;
justify-content: center;
margin-top: 20px;
font-size: 20px;
line-height: 24px;
font-weight: 500;
}
.${Classes.BUTTON}.${Classes.MINIMAL}:hover {
background-color: ${props => props.theme.colors.modal.bg};
}
}
& .bp3-dialog-footer-actions {
& .${Classes.DIALOG_BODY} {
margin: ${props => props.theme.spaces[9]}px;
}
& .${Classes.DIALOG_FOOTER_ACTIONS} {
display: block;
}
${props => props.setMaxWidth && `width: 100vh;`}

View File

@ -0,0 +1,38 @@
import Dropdown from "components/ads/Dropdown";
import React, { useEffect, useState } from "react";
type DropdownWrapperProps = {
placeholder: string;
input?: {
value?: string;
onChange?: (value?: string) => void;
};
options: Array<{ id: string; value: string; label: string }>;
};
const DropdownWrapper = (props: DropdownWrapperProps) => {
const [selectedOption, setSelectedOption] = useState({
value: props.placeholder,
});
const onSelectHandler = (value?: string) => {
props.input && props.input.onChange && props.input.onChange(value);
};
useEffect(() => {
if (props.placeholder) {
setSelectedOption({ value: props.placeholder });
} else if (props.input && props.input.value) {
setSelectedOption({ value: props.input.value });
}
}, [props.input, props.placeholder]);
return (
<Dropdown
options={props.options}
selected={selectedOption}
onSelect={onSelectHandler}
/>
);
};
export default DropdownWrapper;

View File

@ -4,7 +4,7 @@ import {
WrappedFieldMetaProps,
WrappedFieldInputProps,
} from "redux-form";
import SelectComponent from "components/editorComponents/SelectComponent";
import DropdownWrapper from "./DropdownWrapper";
const renderComponent = (
componentProps: SelectFieldProps & {
@ -14,15 +14,15 @@ const renderComponent = (
) => {
return (
<React.Fragment>
<SelectComponent {...componentProps} />
<DropdownWrapper {...componentProps} />
</React.Fragment>
);
};
type SelectFieldProps = {
name: string;
placeholder?: string;
options?: Array<{ id: string; name: string; value?: string }>;
placeholder: string;
options: Array<{ id: string; value: string; label: string }>;
size?: "large" | "small";
outline?: boolean;
};

View File

@ -4,7 +4,7 @@ import {
WrappedFieldMetaProps,
WrappedFieldInputProps,
} from "redux-form";
import TagInputComponent from "components/editorComponents/TagInputComponent";
import TagInputComponent from "components/ads/TagInputComponent";
import { Intent } from "constants/DefaultTheme";
const renderComponent = (

View File

@ -2,6 +2,7 @@ import React from "react";
import { withKnobs, select, text, boolean } from "@storybook/addon-knobs";
import Callout from "components/ads/Callout";
import { StoryWrapper } from "components/ads/common";
import { Variant } from "components/ads/Button";
export default {
title: "Callout",
@ -13,9 +14,8 @@ export const CalloutStory = () => (
<StoryWrapper>
<Callout
text={text("text", "Lorem ipsum dolar sit adicipling dolare")}
variant={select("variant", ["note", "warning"], "note")}
background={select("background", ["light", "dark"], "dark")}
fill={boolean("fill", false)}
variant={select("variant", Object.values(Variant), Variant.info)}
/>
</StoryWrapper>
);

View File

@ -32,6 +32,7 @@ export const Text = () => (
onSelect: action("selected-option"),
},
]}
onSelect={action("selected-option")}
selected={{
id: select("Selected id", ["111abc", "222abc", "333abc"], "111abc"),
value: text("Selected value", "First option"),
@ -64,6 +65,7 @@ export const IconAndText = () => (
onSelect: action("selected-option"),
},
]}
onSelect={action("selected-option")}
selected={{
id: select("Selected id", ["111abc", "222abc", "333abc"], "111abc"),
value: text("Selected value", "Delete"),
@ -102,6 +104,7 @@ export const LabelAndText = () => (
onSelect: action("selected-option"),
},
]}
onSelect={action("selected-option")}
selected={{
id: select("Selected id", ["111abc", "222abc", "333abc"], "111abc"),
value: text("Selected value", "Developer"),

View File

@ -479,7 +479,6 @@ type ColorType = {
hoverBG: Color;
hoverBGOpacity: number;
hoverBorder: ShadeColor;
targetBg: string;
iconColor: ShadeColor;
};
appCardColors: string[];
@ -522,7 +521,7 @@ type ColorType = {
dropdown: {
header: {
text: ShadeColor;
disabled: ShadeColor;
disabledText: ShadeColor;
bg: ShadeColor;
disabledBg: ShadeColor;
};
@ -563,6 +562,11 @@ type ColorType = {
border: ShadeColor;
};
placeholder: ShadeColor;
readOnly: {
bg: ShadeColor;
border: ShadeColor;
text: ShadeColor;
};
};
menuBorder: ShadeColor;
editableText: {
@ -637,6 +641,46 @@ type ColorType = {
profileDropdown: {
userName: ShadeColor;
};
modal: {
bg: ShadeColor;
headerText: ShadeColor;
iconColor: string;
user: {
textColor: ShadeColor;
};
email: {
message: ShadeColor;
desc: ShadeColor;
};
manageUser: ShadeColor;
};
tagInput: {
bg: ShadeColor;
tag: {
text: ShadeColor;
};
text: ShadeColor;
placeholder: ShadeColor;
shadow: string;
};
callout: {
info: {
color: string;
bgColor: string;
};
success: {
color: string;
bgColor: string;
};
danger: {
color: string;
bgColor: string;
};
warning: {
color: string;
bgColor: string;
};
};
};
export const dark: ColorType = {
@ -683,7 +727,6 @@ export const dark: ColorType = {
hoverBG: Colors.BLACK,
hoverBGOpacity: 0.5,
hoverBorder: darkShades[4],
targetBg: "rgba(0, 0, 0, 0.1)",
iconColor: darkShades[9],
},
appCardColors: [
@ -736,9 +779,9 @@ export const dark: ColorType = {
dropdown: {
header: {
text: darkShades[7],
disabled: darkShades[6],
bg: darkShades[2],
disabledBg: darkShades[0],
disabledText: darkShades[6],
bg: darkShades[0],
disabledBg: darkShades[2],
},
menuBg: darkShades[3],
menuShadow: "rgba(0, 0, 0, 0.6)",
@ -777,6 +820,11 @@ export const dark: ColorType = {
border: darkShades[0],
},
placeholder: darkShades[5],
readOnly: {
bg: darkShades[0],
border: darkShades[0],
text: darkShades[7],
},
},
menuBorder: darkShades[4],
editableText: {
@ -851,6 +899,46 @@ export const dark: ColorType = {
profileDropdown: {
userName: darkShades[9],
},
modal: {
bg: darkShades[1],
headerText: darkShades[9],
iconColor: "#6D6D6D",
user: {
textColor: darkShades[7],
},
email: {
message: darkShades[8],
desc: darkShades[6],
},
manageUser: darkShades[6],
},
tagInput: {
bg: darkShades[0],
tag: {
text: darkShades[9],
},
text: darkShades[9],
placeholder: darkShades[5],
shadow: "0px 0px 4px 4px rgba(203, 72, 16, 0.18)",
},
callout: {
info: {
color: "#EE5A1A",
bgColor: "#241C1B",
},
success: {
color: "#30CF89",
bgColor: "#17211E",
},
danger: {
color: "#FF4D4D",
bgColor: "#2B1A1D",
},
warning: {
color: "#E0B30E",
bgColor: "#29251A",
},
},
};
export const light: ColorType = {
@ -897,7 +985,6 @@ export const light: ColorType = {
hoverBG: Colors.WHITE,
hoverBGOpacity: 0.7,
hoverBorder: lightShades[2],
targetBg: "rgba(0, 0, 0, 0.1)",
iconColor: lightShades[11],
},
appCardColors: [
@ -950,7 +1037,7 @@ export const light: ColorType = {
dropdown: {
header: {
text: lightShades[9],
disabled: darkShades[6],
disabledText: darkShades[6],
bg: lightShades[2],
disabledBg: lightShades[1],
},
@ -990,7 +1077,12 @@ export const light: ColorType = {
text: lightShades[9],
border: lightShades[2],
},
placeholder: lightShades[6],
placeholder: lightShades[7],
readOnly: {
bg: lightShades[2],
border: lightShades[2],
text: lightShades[7],
},
},
menuBorder: lightShades[3],
editableText: {
@ -1065,6 +1157,46 @@ export const light: ColorType = {
profileDropdown: {
userName: lightShades[9],
},
modal: {
bg: lightShades[11],
headerText: lightShades[10],
iconColor: "#A9A7A7",
user: {
textColor: lightShades[9],
},
email: {
message: lightShades[9],
desc: lightShades[7],
},
manageUser: lightShades[6],
},
tagInput: {
bg: lightShades[2],
tag: {
text: lightShades[11],
},
text: lightShades[9],
placeholder: darkShades[7],
shadow: "0px 0px 4px 4px rgba(203, 72, 16, 0.18)",
},
callout: {
info: {
color: "#D44100",
bgColor: "#F8F3F0",
},
success: {
color: "#007340",
bgColor: "#D9FDED",
},
danger: {
color: "#C60707",
bgColor: "#FFE9E9",
},
warning: {
color: "#DCAD00",
bgColor: "#FAF6E6",
},
},
};
export const theme: Theme = {
@ -1171,28 +1303,6 @@ export const theme: Theme = {
lightBg: lightShades[0],
darkBg: lightShades[10],
},
callout: {
note: {
dark: {
color: "#EE5A1A",
bgColor: "#241C1B",
},
light: {
color: "#D44100",
bgColor: "#F8F3F0",
},
},
warning: {
light: {
color: "#DCAD00",
bgColor: "#FAF6E6",
},
dark: {
color: "#E0B30E",
bgColor: "#29251A",
},
},
},
appBackground: "#EFEFEF",
primaryOld: Colors.GREEN,
primaryDarker: Colors.JUNGLE_GREEN,

View File

@ -45,11 +45,10 @@ import { Classes as CsClasses } from "components/ads/common";
type NameWrapperProps = {
hasReadPermission: boolean;
showOverlay: boolean;
isMenuOpen: boolean;
};
const NameWrapper = styled((props: HTMLDivProps & NameWrapperProps) => (
<div {...omit(props, ["hasReadPermission", "showOverlay", "isMenuOpen"])} />
<div {...omit(props, ["hasReadPermission", "showOverlay"])} />
))`
.bp3-card {
border-radius: 0;
@ -81,7 +80,7 @@ const NameWrapper = styled((props: HTMLDivProps & NameWrapperProps) => (
& div.image-container {
background: ${
props.hasReadPermission && !props.isMenuOpen
props.hasReadPermission
? getColorWithOpacity(
props.theme.colors.card.hoverBG,
props.theme.colors.card.hoverBGOpacity,
@ -206,8 +205,6 @@ const ContextDropdownWrapper = styled.div`
.${Classes.POPOVER_TARGET} {
span {
background: ${props => props.theme.colors.card.targetBg};
svg {
path {
fill: ${props => props.theme.colors.card.iconColor};
@ -385,7 +382,6 @@ export const ApplicationCard = (props: ApplicationCardProps) => {
!isMenuOpen && setShowOverlay(false);
}}
hasReadPermission={hasReadPermission}
isMenuOpen={isMenuOpen}
className="t--application-card"
>
<Wrapper

View File

@ -465,7 +465,7 @@ const ApplicationsSection = () => {
{applications.map((application: any) => {
return (
application.pages?.length > 0 && (
<PaddingWrapper>
<PaddingWrapper key={application.id}>
<ApplicationCard
key={application.id}
application={application}

View File

@ -21,9 +21,9 @@ type TagProps = CommonComponentProps & {
userName?: string;
};
const ProfileImage = styled.div<{ backgroundColor?: string }>`
width: 30px;
height: 30px;
export const ProfileImage = styled.div<{ backgroundColor?: string }>`
width: 34px;
height: 34px;
display: flex;
align-items: center;
border-radius: 50%;

View File

@ -12,33 +12,24 @@ import {
import { getDefaultPageId } from "sagas/SagaUtils";
import { getApplicationViewerPageURL } from "constants/routes";
import OrgInviteUsersForm from "./OrgInviteUsersForm";
import { StyledSwitch } from "components/propertyControls/StyledControls";
import Spinner from "components/editorComponents/Spinner";
import { getCurrentUser } from "selectors/usersSelectors";
import Text, { TextType } from "components/ads/Text";
import Toggle from "components/ads/Toggle";
const Title = styled.div`
font-weight: bold;
padding: 10px 0px;
`;
const ShareWithPublicOption = styled.div`
{
display: flex;
padding: 10px 0px;
justify-content: space-between;
}
display: flex;
margin-bottom: 15px;
align-items: center;
justify-content: space-between;
`;
const ShareToggle = styled.div`
{
&&& label {
margin-bottom: 0px;
}
&&& div {
margin-right: 5px;
}
display: flex;
}
flex-basis: 48px;
height: 23px;
`;
const AppInviteUsersForm = (props: any) => {
@ -84,29 +75,30 @@ const AppInviteUsersForm = (props: any) => {
{canShareWithPublic && (
<>
<ShareWithPublicOption>
Make the application public
<Text type={TextType.H5}>Make the application public</Text>
<ShareToggle>
{(isChangingViewAccess || isFetchingApplication) && (
<Spinner size={20} />
)}
{currentApplicationDetails && (
<StyledSwitch
onChange={() => {
<Toggle
isLoading={isChangingViewAccess || isFetchingApplication}
value={currentApplicationDetails.isPublic}
disabled={isChangingViewAccess || isFetchingApplication}
onToggle={() => {
changeAppViewAccess(
applicationId,
!currentApplicationDetails.isPublic,
);
}}
disabled={isChangingViewAccess || isFetchingApplication}
checked={currentApplicationDetails.isPublic}
large
/>
)}
</ShareToggle>
</ShareWithPublicOption>
</>
)}
<Title>Get Shareable link for this for this application </Title>
<Title>
<Text type={TextType.H5}>
Get Shareable link for this for this application
</Text>
</Title>
<CopyToClipBoard copyText={getViewApplicationURL()} />
{canInviteToOrg && (

View File

@ -1,12 +1,11 @@
import React, { useEffect } from "react";
import React, { Fragment, useEffect } from "react";
import styled from "styled-components";
import { useLocation } from "react-router-dom";
import TagListField from "components/editorComponents/form/fields/TagListField";
import { reduxForm, SubmissionError } from "redux-form";
import SelectField from "components/editorComponents/form/fields/SelectField";
import Divider from "components/editorComponents/Divider";
import Button from "components/editorComponents/Button";
import { connect } from "react-redux";
import { connect, useSelector } from "react-redux";
import { AppState } from "reducers";
import {
getRolesForField,
@ -17,8 +16,6 @@ import Spinner from "components/editorComponents/Spinner";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { InviteUsersToOrgFormValues, inviteUsersToOrg } from "./helpers";
import { INVITE_USERS_TO_ORG_FORM } from "constants/forms";
import { Classes } from "@blueprintjs/core";
import FormMessage from "components/editorComponents/form/FormMessage";
import {
INVITE_USERS_SUBMIT_SUCCESS,
INVITE_USERS_VALIDATION_EMAILS_EMPTY,
@ -26,7 +23,6 @@ import {
INVITE_USERS_VALIDATION_ROLE_EMPTY,
} from "constants/messages";
import history from "utils/history";
import { Colors } from "constants/Colors";
import { isEmail } from "utils/formhelpers";
import {
isPermitted,
@ -35,32 +31,28 @@ import {
import { getAppsmithConfigs } from "configs";
import { ReactComponent as NoEmailConfigImage } from "assets/images/email-not-configured.svg";
import AnalyticsUtil from "utils/AnalyticsUtil";
import Button, { Variant, Size } from "components/ads/Button";
import Text, { TextType } from "components/ads/Text";
import Icon, { IconSize } from "components/ads/Icon";
import { Classes } from "components/ads/common";
import Callout from "components/ads/Callout";
import { getInitialsAndColorCode } from "utils/AppsmithUtils";
import { getThemeDetails } from "selectors/themeSelectors";
import { ProfileImage } from "pages/common/ProfileDropdown";
const OrgInviteTitle = styled.div`
font-weight: bold;
padding: 10px 0px;
`;
const DropDownOption = styled.div`
padding: 10px 0;
`;
const OptionTitle = styled.div`
font-weight: bold;
`;
const OptionDescription = styled.div`
padding: 5px 0px;
max-width: 250px;
`;
const StyledForm = styled.form`
width: 100%;
background: white;
padding: ${props => props.theme.spaces[5]}px;
background: ${props => props.theme.colors.modal.bg};
&&& {
.wrapper > div {
width: 70%;
.wrapper > div:nth-child(1) {
width: 60%;
}
.wrapper > div:nth-child(2) {
width: 40%;
}
.bp3-input {
box-shadow: none;
@ -69,10 +61,39 @@ const StyledForm = styled.form`
padding-top: 5px;
}
}
.manageUsers {
float: right;
margin-top: 20px;
`;
const ManageUsers = styled("a")`
margin-top: 20px;
display: inline-flex;
&&&& {
text-decoration: none;
}
.${Classes.TEXT} {
color: ${props => props.theme.colors.modal.manageUser};
margin-right: ${props => props.theme.spaces[1]}px;
}
.${Classes.ICON} {
svg path {
fill: ${props => props.theme.colors.modal.manageUser};
}
}
&:hover {
.${Classes.TEXT} {
color: ${props => props.theme.colors.modal.headerText};
}
.${Classes.ICON} {
svg path {
fill: ${props => props.theme.colors.modal.headerText};
}
}
}
`;
const ErrorBox = styled.div<{ message?: boolean }>`
${props => (props.message ? `margin: ${props.theme.spaces[9]}px 0px` : null)};
`;
const StyledInviteFieldGroup = styled.div`
@ -84,36 +105,51 @@ const StyledInviteFieldGroup = styled.div`
display: flex;
width: 85%;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding-right: 5px;
border-width: 1px;
margin-right: 5px;
border-right: 0px;
border-style: solid;
border-color: ${Colors.ATHENS_GRAY};
}
`;
const UserList = styled.div`
max-height: 200px;
margin-top: 20px;
overflow-y: scroll;
.user {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 8px;
margin-bottom: 8px;
margin-top: 10px;
`;
const User = styled.div`
display: flex;
align-items: center;
height: 54px;
padding-left: 15px;
justify-content: space-between;
color: ${props => props.theme.colors.modal.user.textColor};
`;
const UserInfo = styled.div`
display: inline-flex;
align-items: center;
`;
const UserRole = styled.div`
flex-basis: 25%;
.${Classes.TEXT} {
color: ${props => props.theme.colors.modal.headerText};
}
`;
const StyledButton = styled(Button)`
&&&.${Classes.BUTTON} {
width: 83px;
height: 31px;
border-radius: 0px;
const UserName = styled.div`
display: flex;
flex-direction: column;
margin-left: 10px;
span:nth-child(1) {
margin-bottom: 1px;
}
`;
const RoleDivider = styled.div`
border-top: 1px solid ${props => props.theme.colors.menuBorder};
`;
const Loading = styled(Spinner)`
padding-top: 10px;
margin: auto;
@ -123,15 +159,16 @@ const Loading = styled(Spinner)`
const MailConfigContainer = styled.div`
display: flex;
flex-direction: column;
padding: 5px;
padding: ${props => props.theme.spaces[9]}px
${props => props.theme.spaces[2]}px;
align-items: center;
&& > span {
color: #2e3d49;
color: ${props => props.theme.colors.modal.email.message};
font-weight: 500;
font-size: 14px;
}
&& > a {
color: rgba(46, 61, 73, 0.5);
color: ${props => props.theme.colors.modal.email.desc};
font-size: 12px;
text-decoration: underline;
}
@ -208,22 +245,39 @@ const OrgInviteUsersForm = (props: any) => {
const styledRoles = props.roles.map((role: any) => {
return {
id: role.id,
name: role.name,
content: (
<DropDownOption>
<OptionTitle>{role.name}</OptionTitle>
<OptionDescription>{role.description}</OptionDescription>
</DropDownOption>
),
value: role.name,
label: role.description,
};
});
const themeDetails = useSelector(getThemeDetails);
const allUsersProfiles = React.useMemo(
() =>
allUsers.map(
(user: { username: string; roleName: string; name: string }) => {
const details = getInitialsAndColorCode(
user.name || user.username,
themeDetails.theme.colors.appCardColors,
);
return {
...user,
imageBackground: details[1],
initials: details[0],
};
},
),
[allUsers, themeDetails],
);
return (
<>
{isApplicationInvite && (
<>
<Divider />
<OrgInviteTitle>Invite Users to {currentOrg?.name} </OrgInviteTitle>
<OrgInviteTitle>
<Text type={TextType.H5}>Invite Users to {currentOrg?.name} </Text>
</OrgInviteTitle>
</>
)}
<StyledForm
@ -233,12 +287,6 @@ const OrgInviteUsersForm = (props: any) => {
return inviteUsersToOrg({ ...values, orgId: props.orgId }, dispatch);
})}
>
{submitSucceeded && (
<FormMessage intent="primary" message={INVITE_USERS_SUBMIT_SUCCESS} />
)}
{submitFailed && error && (
<FormMessage intent="danger" message={error} />
)}
<StyledInviteFieldGroup>
<div className="wrapper">
<TagListField
@ -258,14 +306,14 @@ const OrgInviteUsersForm = (props: any) => {
data-cy="t--invite-role-input"
/>
</div>
<StyledButton
<Button
tag="button"
className="t--invite-user-btn"
disabled={!valid}
text="Invite"
filled
intent="primary"
loading={submitting && !(submitFailed && !anyTouched)}
type="submit"
size={Size.large}
variant={Variant.info}
isLoading={submitting && !(submitFailed && !anyTouched)}
/>
</StyledInviteFieldGroup>
{isLoading ? (
@ -286,27 +334,63 @@ const OrgInviteUsersForm = (props: any) => {
</MailConfigContainer>
)}
<UserList style={{ justifyContent: "space-between" }}>
{allUsers.map((user: { username: string; roleName: string }) => {
return (
<div className="user" key={user.username}>
<div>{user.username}</div>
<div>{user.roleName}</div>
</div>
);
})}
{allUsersProfiles.map(
(user: {
username: string;
name: string;
roleName: string;
imageBackground: string;
initials: string;
}) => {
return (
<Fragment key={user.username}>
<User>
<UserInfo>
<ProfileImage backgroundColor={user.imageBackground}>
<Text type={TextType.H6} highlight>
{user.initials}
</Text>
</ProfileImage>
<UserName>
<Text type={TextType.H5}>{user.name}</Text>
<Text type={TextType.P2}>{user.username}</Text>
</UserName>
</UserInfo>
<UserRole>
<Text type={TextType.P1}>{user.roleName}</Text>
</UserRole>
</User>
<RoleDivider />
</Fragment>
);
},
)}
</UserList>
</React.Fragment>
)}
<ErrorBox message={submitSucceeded || submitFailed}>
{submitSucceeded && (
<Callout
text={INVITE_USERS_SUBMIT_SUCCESS}
variant={Variant.success}
fill
/>
)}
{submitFailed && error && (
<Callout text={error} variant={Variant.danger} fill />
)}
</ErrorBox>
{!pathRegex.test(currentPath) && canManage && (
<Button
<ManageUsers
className="manageUsers"
text="Manage Users"
filled
intent="primary"
onClick={() => {
history.push(`/org/${props.orgId}/settings/members`);
}}
/>
>
<Text type={TextType.H6}>MANAGE USERS</Text>
<Icon name="manage" size={IconSize.XXS} />
</ManageUsers>
)}
</StyledForm>
</>