This commit is contained in:
Nikita Gopienko 2019-09-12 11:50:57 +03:00
commit b9edf10894
24 changed files with 819 additions and 420 deletions

2
.gitignore vendored
View File

@ -3,7 +3,6 @@
*.suo
*.user
.vs/
.vscode/
*-lock.json
**/node_modules/
**/storybook-static/
@ -12,3 +11,4 @@
*.log
/packages/asc-web-components
/products/ASC.People/Data/
Data/

View File

@ -0,0 +1,54 @@
import React from "react";
import styled from "styled-components";
import isEqual from "lodash/isEqual";
import { ComboBox, TextInput } from "asc-web-components";
const Container = styled.div`
display: flex;
margin: 0 0 16px 0;
`;
class ContactField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("ContactField render");
const {
isDisabled,
comboBoxName,
comboBoxOptions,
comboBoxSelectedOption,
comboBoxOnChange,
inputName,
inputValue,
inputOnChange
} = this.props;
return (
<Container>
<ComboBox
name={comboBoxName}
options={comboBoxOptions}
onSelect={comboBoxOnChange}
selectedOption={comboBoxSelectedOption}
isDisabled={isDisabled}
scaled={false}
className="field-select"
/>
<TextInput
name={inputName}
value={inputValue}
isDisabled={isDisabled}
onChange={inputOnChange}
/>
</Container>
);
}
}
export default ContactField;

View File

@ -1,6 +1,8 @@
import React from 'react'
import styled from 'styled-components';
import { ComboBox, TextInput } from 'asc-web-components'
import React from "react";
import styled from "styled-components";
import isEqual from "lodash/isEqual";
import ContactField from "./ContactField";
import { ComboBox } from "asc-web-components";
const Container = styled.div`
width: 100%;
@ -12,11 +14,6 @@ const Container = styled.div`
}
`;
const Item = styled.div`
display: flex;
margin: 0 0 16px 0;
`;
const getOptions = (patterns, keyPrefix) => {
return patterns.map((item, index) => {
return {
@ -26,49 +23,67 @@ const getOptions = (patterns, keyPrefix) => {
value: item.type
};
});
}
};
const renderItems = (contacts, pattern, onTypeChange, onTextChange, isDisabled) => {
const renderItems = (
contacts,
pattern,
onTypeChange,
onTextChange,
isDisabled
) => {
const items = contacts.map((contact, index) => {
const prefix = contact.id + "_";
const itemOptions = getOptions(pattern, prefix);
const itemSelectedOption = itemOptions.filter(option => option.value === contact.type)[0];
const itemSelectedOption = itemOptions.filter(
option => option.value === contact.type
)[0];
return (
<Item key={prefix + "item_" + index}>
<ComboBox
name={prefix + "type"}
options={itemOptions}
onSelect={onTypeChange}
selectedOption={itemSelectedOption}
<ContactField
key={prefix + "item_" + index}
isDisabled={isDisabled}
scaled={false}
className="field-select"
comboBoxName={prefix + "type"}
comboBoxOptions={itemOptions}
comboBoxSelectedOption={itemSelectedOption}
comboBoxOnChange={onTypeChange}
inputName={prefix + "value"}
inputValue={contact.value}
inputOnChange={onTextChange}
/>
<TextInput
name={prefix + "value"}
value={contact.value}
isDisabled={isDisabled}
onChange={onTextChange}
/>
</Item>
);
});
return items;
};
const ContactsField = React.memo((props) => {
class ContactsField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
const { pattern, contacts, addItemText, onItemAdd, onItemTypeChange, onItemTextChange, isDisabled } = props;
render() {
console.log("ContactsField render");
const existItems = renderItems(contacts, pattern, onItemTypeChange, onItemTextChange, isDisabled);
const {
pattern,
contacts,
addItemText,
onItemAdd,
onItemTypeChange,
onItemTextChange,
isDisabled
} = this.props;
const existItems = renderItems(
contacts,
pattern,
onItemTypeChange,
onItemTextChange,
isDisabled
);
const prefix = "null_";
const options = getOptions(pattern, prefix);
return (
@ -88,6 +103,7 @@ const ContactsField = React.memo((props) => {
/>
</Container>
);
});
}
}
export default ContactsField
export default ContactsField;

View File

@ -1,7 +1,15 @@
import React from 'react'
import { FieldContainer, DateInput } from 'asc-web-components'
import React from "react";
import isEqual from "lodash/isEqual";
import { FieldContainer, DatePicker } from "asc-web-components";
class DateField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("DateField render");
const DateField = React.memo((props) => {
const {
isRequired,
hasError,
@ -12,7 +20,7 @@ const DateField = React.memo((props) => {
inputIsDisabled,
inputOnChange,
inputTabIndex
} = props;
} = this.props;
return (
<FieldContainer
@ -20,9 +28,9 @@ const DateField = React.memo((props) => {
hasError={hasError}
labelText={labelText}
>
<DateInput
<DatePicker
name={inputName}
selected={inputValue}
selectedDate={inputValue}
disabled={inputIsDisabled}
onChange={inputOnChange}
hasError={hasError}
@ -30,6 +38,7 @@ const DateField = React.memo((props) => {
/>
</FieldContainer>
);
});
}
}
export default DateField
export default DateField;

View File

@ -1,7 +1,19 @@
import React from 'react'
import { FieldContainer, SelectorAddButton, SelectedItem } from 'asc-web-components'
import React from "react";
import isEqual from "lodash/isEqual";
import {
FieldContainer,
SelectorAddButton,
SelectedItem
} from "asc-web-components";
class DepartmentField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("DepartmentField render");
const DepartmentField = React.memo((props) => {
const {
isRequired,
isDisabled,
@ -11,7 +23,7 @@ const DepartmentField = React.memo((props) => {
departments,
onAddDepartment,
onRemoveDepartment
} = props;
} = this.props;
return (
<FieldContainer
@ -26,17 +38,20 @@ const DepartmentField = React.memo((props) => {
onClick={onAddDepartment}
className="department-add-btn"
/>
{departments && departments.map((department) => (
{departments.map(department => (
<SelectedItem
key={`department_${department.id}`}
text={department.name}
onClose={() => { onRemoveDepartment(department.id) }}
onClose={() => {
onRemoveDepartment(department.id);
}}
isInline={true}
className="department-item"
/>
))}
</FieldContainer>
);
});
}
}
export default DepartmentField
export default DepartmentField;

View File

@ -1,6 +1,6 @@
import React from 'react'
import styled from 'styled-components';
import { Text } from 'asc-web-components'
import React from "react";
import styled from "styled-components";
import { Text } from "asc-web-components";
const Container = styled.div`
margin: 0 0 40px 0;
@ -11,7 +11,7 @@ const Header = styled(Text.ContentHeader)`
line-height: unset;
`;
const InfoFieldContainer = React.memo((props) => {
const InfoFieldContainer = React.memo(props => {
const { headerText, children } = props;
return (
@ -22,4 +22,4 @@ const InfoFieldContainer = React.memo((props) => {
);
});
export default InfoFieldContainer
export default InfoFieldContainer;

View File

@ -1,7 +1,19 @@
import React from 'react'
import { FieldContainer, RadioButtonGroup, PasswordInput } from 'asc-web-components'
import React from "react";
import isEqual from "lodash/isEqual";
import {
FieldContainer,
RadioButtonGroup,
PasswordInput
} from "asc-web-components";
class PasswordField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("PasswordField render");
const PasswordField = React.memo((props) => {
const {
isRequired,
hasError,
@ -21,10 +33,11 @@ const PasswordField = React.memo((props) => {
inputOnChange,
inputTabIndex,
copyLinkText,
} = props;
copyLinkText
} = this.props;
const tooltipPasswordLength = 'from ' + passwordSettings.minLength + ' to 30 characters';
const tooltipPasswordLength =
"from " + passwordSettings.minLength + " to 30 characters";
return (
<FieldContainer
@ -48,19 +61,20 @@ const PasswordField = React.memo((props) => {
inputTabIndex={inputTabIndex}
onChange={inputOnChange}
clipActionResource={copyLinkText}
clipEmailResource='E-mail: '
clipPasswordResource='Password: '
tooltipPasswordTitle='Password must contain:'
clipEmailResource="E-mail: "
clipPasswordResource="Password: "
tooltipPasswordTitle="Password must contain:"
tooltipPasswordLength={tooltipPasswordLength}
tooltipPasswordDigits='digits'
tooltipPasswordCapital='capital letters'
tooltipPasswordSpecial='special characters (!@#$%^&*)'
generatorSpecial='!@#$%^&*'
tooltipPasswordDigits="digits"
tooltipPasswordCapital="capital letters"
tooltipPasswordSpecial="special characters (!@#$%^&*)"
generatorSpecial="!@#$%^&*"
passwordSettings={passwordSettings}
isDisabled={inputIsDisabled}
/>
</FieldContainer>
);
});
}
}
export default PasswordField;

View File

@ -1,7 +1,15 @@
import React from 'react'
import { FieldContainer, RadioButtonGroup } from 'asc-web-components'
import React from "react";
import isEqual from "lodash/isEqual";
import { FieldContainer, RadioButtonGroup } from "asc-web-components";
class RadioField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("RadioField render");
const RadioField = React.memo((props) => {
const {
isRequired,
hasError,
@ -12,7 +20,7 @@ const RadioField = React.memo((props) => {
radioOptions,
radioIsDisabled,
radioOnChange
} = props;
} = this.props;
return (
<FieldContainer
@ -30,6 +38,7 @@ const RadioField = React.memo((props) => {
/>
</FieldContainer>
);
});
}
}
export default RadioField
export default RadioField;

View File

@ -1,6 +1,7 @@
import React from 'react'
import styled from 'styled-components';
import { FieldContainer, TextInput, Button } from 'asc-web-components'
import React from "react";
import styled from "styled-components";
import isEqual from "lodash/isEqual";
import { FieldContainer, TextInput, Button } from "asc-web-components";
const InputContainer = styled.div`
width: 100%;
@ -9,7 +10,14 @@ const InputContainer = styled.div`
align-items: center;
`;
const TextChangeField = React.memo((props) => {
class TextChangeField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("TextChangeField render");
const {
isRequired,
hasError,
@ -23,7 +31,7 @@ const TextChangeField = React.memo((props) => {
buttonIsDisabled,
buttonOnClick,
buttonTabIndex
} = props;
} = this.props;
return (
<FieldContainer
@ -50,6 +58,7 @@ const TextChangeField = React.memo((props) => {
</InputContainer>
</FieldContainer>
);
});
}
}
export default TextChangeField;

View File

@ -1,7 +1,15 @@
import React from 'react'
import { FieldContainer, TextInput } from 'asc-web-components'
import React from "react";
import isEqual from "lodash/isEqual";
import { FieldContainer, TextInput } from "asc-web-components";
class TextField extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
console.log("TextField render");
const TextField = React.memo((props) => {
const {
isRequired,
hasError,
@ -13,7 +21,7 @@ const TextField = React.memo((props) => {
inputOnChange,
inputAutoFocussed,
inputTabIndex
} = props;
} = this.props;
return (
<FieldContainer
@ -33,6 +41,7 @@ const TextField = React.memo((props) => {
/>
</FieldContainer>
);
});
}
}
export default TextField;

View File

@ -1,7 +1,7 @@
import React from 'react'
import { withRouter } from 'react-router'
import { connect } from 'react-redux'
import { Avatar, Button, Textarea, Text, toastr } from 'asc-web-components'
import { Avatar, Button, Textarea, toastr } from 'asc-web-components'
import { withTranslation } from 'react-i18next';
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts } from "../../../../../store/people/selectors";
import { createProfile } from '../../../../../store/profile/actions';

View File

@ -1,4 +1,4 @@
import { find, filter } from "lodash";
import { find, filter, cloneDeep } from "lodash";
import { EmployeeActivationStatus, EmployeeStatus } from "../../helpers/constants";
export function getSelectedUser(selection, userId) {
@ -141,5 +141,5 @@ export function toEmployeeWrapper(profile) {
contacts: []
};
return { ...emptyData, ...profile };
return cloneDeep({ ...emptyData, ...profile });
}

View File

@ -318,6 +318,10 @@ namespace ASC.Employee.Core.Controllers
[Authorize(AuthenticationSchemes = "confirm")]
public EmployeeWraperFull AddMember(MemberModel memberModel)
{
if (HttpContext.User.IsInRole(ASC.Common.Security.Authorizing.Role.System))
{
SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem);
}
SecurityContext.DemandPermissions(Tenant, Constants.Action_AddRemoveUser);
if (string.IsNullOrEmpty(memberModel.Password))

View File

@ -60,8 +60,6 @@ namespace ASC.People
var builder = services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
config.Filters.Add(new AuthorizeFilter(policy));
config.Filters.Add(new TypeFilterAttribute(typeof(TenantStatusFilter)));
config.Filters.Add(new TypeFilterAttribute(typeof(PaymentFilter)));
config.Filters.Add(new TypeFilterAttribute(typeof(IpSecurityFilter)));

View File

@ -4,6 +4,7 @@ import { Loader } from "asc-web-components";
import StudioLayout from "./components/Layout/index";
import Login from "./components/pages/Login";
import { PrivateRoute } from "./helpers/privateRoute";
import { PublicRoute } from "./helpers/publicRoute";
import { Error404 } from "./components/pages/Error";
const Home = lazy(() => import("./components/pages/Home"));
@ -18,8 +19,8 @@ const App = () => {
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
>
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/confirm" component={Confirm} />
<PublicRoute exact path="/login" component={Login} />
<PublicRoute path="/confirm" component={Confirm} />
<PrivateRoute exact path="/" component={Home} />
<PrivateRoute exact path="/about" component={About} />
<PrivateRoute component={Error404} />

View File

@ -0,0 +1,24 @@
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { AUTH_KEY } from './constants';
import Cookies from 'universal-cookie';
export const PublicRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props =>
(new Cookies()).get(AUTH_KEY) ? (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
) : (
<Component {...props} />
)
}
/>
)
};

View File

@ -0,0 +1,37 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
}
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"${fileBasenameNoExtension}",
"--config",
"jest.config.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
}
}
]
}

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-components",
"version": "1.0.63",
"version": "1.0.66",
"description": "Ascensio System SIA component library",
"license": "AGPL-3.0",
"main": "dist/asc-web-components.js",

View File

@ -6,7 +6,7 @@ import withReadme from "storybook-readme/with-readme";
import Readme from "./README.md";
import AdvancedSelector from "./";
import Section from "../../../.storybook/decorators/section";
import { boolean } from "@storybook/addon-knobs/dist/deprecated";
import { boolean, select } from "@storybook/addon-knobs/dist/deprecated";
import { ArrayValue, BooleanValue } from "react-values";
import Button from "../button";
@ -77,6 +77,7 @@ storiesOf("Components|AdvancedSelector", module)
>
{({ value, set }) => (
<AdvancedSelector
size={select("size", ["compact", "full"], "compact")}
placeholder={text("placeholder", "Search users")}
onSearchChanged={value => {
action("onSearchChanged")(value);
@ -179,6 +180,7 @@ storiesOf("Components|AdvancedSelector", module)
>
{({ value, set }) => (
<AdvancedSelector
size={select("size", ["compact", "full"], "compact")}
isDropDown={true}
isOpen={isOpen}
placeholder={text("placeholder", "Search users")}

View File

@ -12,6 +12,8 @@ import { isArrayEqual } from "../../utils/array";
import findIndex from "lodash/findIndex";
import filter from "lodash/filter";
import DropDown from "../drop-down";
import { handleAnyClick } from "../../utils/event";
import isEmpty from 'lodash/isEmpty';
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
@ -19,7 +21,7 @@ const Container = ({
value,
placeholder,
isMultiSelect,
mode,
size,
width,
maxHeight,
isDisabled,
@ -32,16 +34,27 @@ const Container = ({
groups,
selectedGroups,
onChangeGroup,
isOpen,
isDropDown,
containerWidth,
containerHeight,
...props
}) => <div {...props} />;
/* eslint-enable react/prop-types */
/* eslint-enable no-unused-vars */
const StyledContainer = styled(Container)`
${props => (props.width ? `width: ${props.width}px;` : "")}
display: flex;
flex-direction: column;
${props => (props.containerWidth ? `width: ${props.containerWidth}px;` : "")}
${props =>
props.containerHeight
? `height: ${props.containerHeight}px;`
: ""}
.data_container {
margin: 16px;
margin: 16px 16px 0 16px;
.options_searcher {
margin-bottom: 12px;
@ -53,7 +66,7 @@ const StyledContainer = styled(Container)`
.option_select_all_checkbox {
margin-bottom: 12px;
margin-left: 8px;
/*margin-left: 8px;*/
}
.options_list {
@ -62,25 +75,26 @@ const StyledContainer = styled(Container)`
cursor: pointer;
.option_checkbox {
margin-left: 8px;
/*margin-left: 8px;*/
}
.option_link {
padding-left: 8px;
}
&:hover {
/*&:hover {
background-color: #eceef1;
}
}*/
}
}
}
.button_container {
border-top: 1px solid #eceef1;
display: flex;
.add_members_btn {
margin: 16px;
width: 293px;
}
}
`;
@ -89,6 +103,8 @@ class AdvancedSelector extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
const groups = this.convertGroups(this.props.groups);
const currentGroup = this.getCurrentGroup(groups);
@ -98,25 +114,53 @@ class AdvancedSelector extends React.Component {
groups: groups,
currentGroup: currentGroup
};
if (props.isOpen) handleAnyClick(true, this.handleClick);
}
handleClick = e => {
if (this.props.isOpen && !this.ref.current.contains(e.target)) {
this.props.onSelect && this.props.onSelect(this.state.selectedOptions);
}
};
componentWillUnmount() {
handleAnyClick(false, this.handleClick);
}
componentDidUpdate(prevProps) {
let newState = {};
if (!isArrayEqual(this.props.selectedOptions, prevProps.selectedOptions)) {
this.setState({ selectedOptions: this.props.selectedOptions });
newState = { selectedOptions: this.props.selectedOptions };
}
if (this.props.isMultiSelect !== prevProps.isMultiSelect) {
this.setState({ selectedOptions: [] });
newState = Object.assign({}, newState, {
selectedOptions: []
});
}
if (this.props.selectedAll !== prevProps.selectedAll) {
this.setState({ selectedAll: this.props.selectedAll });
newState = Object.assign({}, newState, {
selectedAll: this.props.selectedAll
});
}
if (!isArrayEqual(this.props.groups, prevProps.groups)) {
const groups = this.convertGroups(this.props.groups);
const currentGroup = this.getCurrentGroup(groups);
this.setState({ groups, currentGroup });
newState = Object.assign({}, newState, {
groups, currentGroup
});
}
if(!isEmpty(newState)) {
this.setState({ ...this.state, ...newState });
}
if (this.props.isOpen !== prevProps.isOpen) {
handleAnyClick(this.props.isOpen, this.handleClick);
}
}
@ -215,19 +259,29 @@ class AdvancedSelector extends React.Component {
const {
value,
placeholder,
maxHeight,
isDisabled,
onSearchChanged,
options,
isMultiSelect,
buttonLabel,
selectAllLabel
selectAllLabel,
size
} = this.props;
const { selectedOptions, selectedAll, currentGroup, groups } = this.state;
const containerHeight = size === "compact" ? (!groups || !groups.length ? 336 : 326) : 545;
const containerWidth = size === "compact" ? (!groups || !groups.length ? 325 : 326) : 690;
const listHeight = size === "compact" ? (!groups || !groups.length ? 176 : 120) : 345;
const itemHeight = 32;
return (
<StyledContainer {...this.props}>
<div className="data_container">
<StyledContainer
containerHeight={containerHeight}
containerWidth={containerWidth}
{...this.props}
>
<div className="data_container" ref={this.ref}>
<SearchInput
className="options_searcher"
isDisabled={isDisabled}
@ -264,10 +318,10 @@ class AdvancedSelector extends React.Component {
)}
<FixedSizeList
className="options_list"
height={maxHeight}
itemSize={32}
itemCount={options.length}
itemData={options}
height={listHeight}
itemSize={itemHeight}
itemCount={this.props.options.length}
itemData={this.props.options}
outerElementType={CustomScrollbarsVirtualList}
>
{this.renderRow.bind(this)}
@ -310,8 +364,7 @@ AdvancedSelector.propTypes = {
value: PropTypes.string,
placeholder: PropTypes.string,
isMultiSelect: PropTypes.bool,
mode: PropTypes.oneOf(["base", "compact"]),
width: PropTypes.number,
size: PropTypes.oneOf(["compact", "full"]),
maxHeight: PropTypes.number,
isDisabled: PropTypes.bool,
onSearchChanged: PropTypes.func,
@ -330,9 +383,7 @@ AdvancedSelector.propTypes = {
AdvancedSelector.defaultProps = {
isMultiSelect: false,
width: 325,
maxHeight: 545,
mode: "base",
size: "compact",
buttonLabel: "Add members",
selectAllLabel: "Select all"
};

View File

@ -0,0 +1,98 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import { withKnobs, text } from "@storybook/addon-knobs/react";
import AdvancedSelector from "../advanced-selector";
import Section from "../../../.storybook/decorators/section";
import { boolean } from "@storybook/addon-knobs/dist/deprecated";
import { ArrayValue, BooleanValue } from "react-values";
import Button from "../button";
storiesOf("EXAMPLES|AdvancedSelector", module)
.addDecorator(withKnobs)
// To set a default viewport for all the stories for this component
.addParameters({ viewport: { defaultViewport: "responsive" } })
.add("people group selector", () => {
const options = [
{
key: "group-all",
label: "All groups",
total: 0
},
{
key: "group-dev",
label: "Development",
total: 0
},
{
key: "group-management",
label: "Management",
total: 0
},
{
key: "group-marketing",
label: "Marketing",
total: 0
},
{
key: "group-mobile",
label: "Mobile",
total: 0
},
{
key: "group-support",
label: "Support",
total: 0
},
{
key: "group-web",
label: "Web",
total: 0
}
];
return (
<Section>
<BooleanValue
defaultValue={true}
onChange={() => action("isOpen changed")}
>
{({ value: isOpen, toggle }) => (
<div style={{ position: "relative" }}>
<Button label="Toggle dropdown" onClick={toggle} />
<ArrayValue
defaultValue={options}
onChange={() => action("options onChange")}
>
{({ value, set }) => (
<AdvancedSelector
isDropDown={true}
isOpen={isOpen}
maxHeight={336}
width={379}
placeholder={text("placeholder", "Search")}
onSearchChanged={value => {
action("onSearchChanged")(value);
set(
options.filter(option => {
return option.label.indexOf(value) > -1;
})
);
}}
options={value}
isMultiSelect={boolean("isMultiSelect", true)}
buttonLabel={text("buttonLabel", "Add departments")}
selectAllLabel={text("selectAllLabel", "Select all")}
onSelect={selectedOptions => {
action("onSelect")(selectedOptions);
toggle();
}}
/>
)}
</ArrayValue>
</div>
)}
</BooleanValue>
</Section>
);
});

View File

@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import styled from 'styled-components';
import { Icons } from '../icons';
import isEqual from 'lodash/isEqual';
const StyledOuter = styled.div`
width: ${props => props.size ? Math.abs(parseInt(props.size)) + "px" : "20px"};
@ -73,30 +74,19 @@ class IconButton extends React.Component{
}
}
shouldComponentUpdate(nextProps, nextState) {
if(!this.isNeedUpdate){
for (let propsKey in this.props) {
if(typeof this.props[propsKey] != "function" && typeof this.props[propsKey] != "object" && this.props[propsKey] != nextProps[propsKey]){
this.isNeedUpdate = true;
if(propsKey == "iconName"){
this.setState({
currentIconName: nextProps[propsKey]
});
break;
if (!isEqual(this.props, nextProps)) {
let newState = {
currentIconName: this.state.currentIconName,
currentIconColor: this.state.currentIconColor
}
}
}
for (let stateKey in this.state) {
if(typeof this.state[stateKey] != "function" && typeof this.state[stateKey] != "object" && this.state[stateKey] != nextState[stateKey]){
this.isNeedUpdate = true;
break;
}
}
if(!this.isNeedUpdate) return false;
else return true;
}
this.isNeedUpdate = false;
if (this.props.iconName !== nextProps.iconName) newState.currentIconName = nextProps.iconName;
if (this.props.color !== nextProps.color) newState.currentIconColor = nextProps.color;
this.setState(newState);
return true;
}
return !isEqual(this.state, nextState);
}
render() {
//console.log("IconButton render");
return (

View File

@ -273,6 +273,7 @@ class PasswordInput extends React.PureComponent {
} = this.state;
const iconsColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
const iconName = type === 'password' ? 'EyeIcon' : 'EyeOffIcon';
const tooltipContent = (
<StyledTooltipContainer forwardedAs='div' title={tooltipPasswordTitle}>
@ -305,7 +306,7 @@ class PasswordInput extends React.PureComponent {
name={inputName}
hasError={hasError}
isDisabled={isDisabled}
iconName='EyeIcon'
iconName={iconName}
value={inputValue}
onIconClick={this.changeInputType}
onChange={this.onChangeAction}

View File

@ -2,40 +2,98 @@ import React from 'react';
import { mount } from 'enzyme';
import PasswordInput from '.';
describe('<PasswordInput />', () => {
it('renders without error', () => {
const settings = {
const basePasswordSettings = {
minLength: 6,
upperCase: false,
digits: false,
specSymbols: false
};
const wrapper = mount(
<PasswordInput
inputName="demoPasswordInput"
emailInputName="demoEmailInput"
inputValue={""}
onChange={e => {
console.log(e.target.value);
}}
clipActionResource="Copy e-mail and password"
clipEmailResource="E-mail: "
clipPasswordResource="Password: "
tooltipPasswordTitle="Password must contain:"
tooltipPasswordLength="from 6 to 30 characters"
tooltipPasswordDigits="digits"
tooltipPasswordCapital="capital letters"
tooltipPasswordSpecial="special characters (!@#$%^&*)"
generatorSpecial="!@#$%^&*"
passwordSettings={settings}
isDisabled={false}
placeholder="password"
onValidateInput={a => console.log(a)}
onCopyToClipboard={b => console.log("Data " + b + " copied to clipboard")}
/>
);
const baseProps = {
inputName: 'demoPasswordInput',
emailInputName: 'demoEmailInput',
inputValue: '',
clipActionResource: 'Copy e-mail and password',
clipEmailResource: 'E-mail: ',
clipPasswordResource: 'Password: ',
tooltipPasswordTitle: 'Password must contain:',
tooltipPasswordLength: 'from 6 to 30 characters',
tooltipPasswordDigits: 'digits',
tooltipPasswordCapital: 'capital letters',
tooltipPasswordSpecial: 'special characters (!@#$%^&*)',
generatorSpecial: '!@#$%^&*',
passwordSettings: basePasswordSettings,
isDisabled: false,
placeholder: 'password',
onChange: () => jest.fn(),
onValidateInput: () => jest.fn(),
onCopyToClipboard: () => jest.fn()
}
describe('<PasswordInput />', () => {
it('renders without error', () => {
const wrapper = mount(<PasswordInput {...baseProps} />);
expect(wrapper).toExist();
});
it('render password input', () => {
const wrapper = mount(<PasswordInput {...baseProps} />);
expect(wrapper.find('input').prop('type')).toEqual('password');
});
it('have an HTML name', () => {
const wrapper = mount(<PasswordInput {...baseProps} />);
expect(wrapper.find('input').prop('name')).toEqual('demoPasswordInput');
});
it('forward passed value', () => {
const wrapper = mount(<PasswordInput {...baseProps} inputValue='demo' />);
expect(wrapper.props().inputValue).toEqual('demo');
});
it('call onChange when changing value', () => {
const onChange = jest.fn(event => {
expect(event.target.id).toEqual('demoPasswordInput');
expect(event.target.name).toEqual('demoPasswordInput');
expect(event.target.value).toEqual('demo');
});
const wrapper = mount(<PasswordInput {...baseProps} id="demoPasswordInput" name="demoPasswordInput" onChange={onChange} />);
const event = { target: { value: "demo" } };
wrapper.simulate('change', event);
});
it('call onFocus when input is focused', () => {
const onFocus = jest.fn(event => {
expect(event.target.id).toEqual('demoPasswordInput');
expect(event.target.name).toEqual('demoPasswordInput');
});
const wrapper = mount(<PasswordInput {...baseProps} id="demoPasswordInput" name="demoPasswordInput" onFocus={onFocus} />);
wrapper.simulate('focus');
});
it('call onBlur when input loses focus', () => {
const onBlur = jest.fn(event => {
expect(event.target.id).toEqual('demoPasswordInput');
expect(event.target.name).toEqual('demoPasswordInput');
});
const wrapper = mount(<PasswordInput {...baseProps} id="demoPasswordInput" name="demoPasswordInput" onBlur={onBlur} />);
wrapper.simulate('blur');
});
it('disabled when isDisabled is passed', () => {
const wrapper = mount(<PasswordInput {...baseProps} isDisabled={true} />);
expect(wrapper.prop('isDisabled')).toEqual(true);
});
});