Merge branch 'master' into feature/layout-scrolling

This commit is contained in:
Alexey Safronov 2019-11-20 11:41:10 +03:00
commit 4d2db41258
61 changed files with 1945 additions and 645 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@
/packages/asc-web-components
/products/ASC.People/Data/
Data/
Logs/

View File

@ -9,7 +9,7 @@
"ConnectionStrings": {
"default": {
"name": "default",
"connectionString": "Server=172.18.0.5;Port=3306;Database=onlyoffice;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none",
"connectionString": "Server=172.18.0.3;Port=3306;Database=onlyoffice;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none",
"providerName": "MySql.Data.MySqlClient"
}
},

View File

@ -10,6 +10,7 @@ import ProfileAction from './components/pages/ProfileAction';
import GroupAction from './components/pages/GroupAction';
import { Error404 } from "./components/pages/Error";
import Reassign from './components/pages/Reassign';
import Import from './components/pages/Import';
import history from './history';
/*const Profile = lazy(() => import("./components/pages/Profile"));
@ -56,6 +57,11 @@ const App = ({ settings }) => {
component={Reassign}
restricted
/>
<PrivateRoute
path={`${homepage}/import`}
component={Import}
restricted
/>
<PrivateRoute component={Error404} />
</Switch>
</Suspense>

View File

@ -72,7 +72,7 @@ class PureArticleMainButtonContent extends React.Component {
<DropDownItem
icon="ImportIcon"
label={t('ImportPeople')}
onClick={this.onNotImplementedClick.bind(this, "Import people action")}
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/import`)}
/>
</MainButton>
{dialogVisible &&

View File

@ -0,0 +1,254 @@
import React, { memo } from "react";
import { withRouter } from "react-router";
// import { useTranslation } from 'react-i18next';
import { connect } from "react-redux";
import styled from 'styled-components';
import { Text, Button, Avatar, toastr } from 'asc-web-components';
import { toEmployeeWrapper } from "../../../../../store/people/selectors";
import { FixedSizeList as List, areEqual } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
const LoadCsvWrapper = styled.div`
margin-top: 24px;
margin-bottom: 24px;
width: 100%;
height: 400px;
max-width: 1024px;
`;
const SelectSourceWrapper = styled.div`
margin-top: 24px;
`;
const StyledFileInput = styled.div`
position: relative;
width: 100%;
max-width: 400px;
height: 36px;
input[type="file"] {
height: 100%;
font-size: 200px;
position: absolute;
top: 0;
right: 0;
opacity: 0;
}
`;
const StyledFieldContainer = styled.div`
position: relative;
@media (min-width:768px) {
margin-top: 14px;
}
`;
const StyledAvatar = styled.div`
float: left;
@media (max-width:768px) {
margin-top: 8px;
}
`;
const StyledField = styled.div`
line-height: 14px;
min-width: 100px;
padding: 4px 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media (min-width:768px) {
margin-top: 4px;
display: inline-block !important;
}
`;
const StyledProgress = styled.div`
position: absolute;
max-width: 100%;
width: ${props => props.completed && `${props.completed}%`};
height: 100%;
top: 0px;
background-color: #7ACE9B;
opacity: 0.3;
border-radius: 3px;
z-index: -1;
transition-property: width;
transition-duration: 1s;
`;
class ImportRow extends React.PureComponent {
render() {
const { items, completed } = this.props;
const fullName = items[0] + ' ' + items[1];
const firstName = items[0];
const lastName = items[1];
const email = items[2];
return (
<StyledFieldContainer key={email}>
<StyledAvatar>
<Avatar size='small' role='user' userName={fullName} />
</StyledAvatar>
<StyledField style={{ display: 'inline-block' }}>{firstName}</StyledField>
<StyledField style={{ display: 'inline-block' }}>{lastName}</StyledField>
<StyledField style={{ display: 'block' }}>{email}</StyledField>
<StyledProgress completed={completed} />
</StyledFieldContainer>
)
}
}
class SectionBodyContent extends React.Component {
constructor(props) {
super(props);
this.state = {
splittedLines: [],
completion: 0
}
}
getAsText = (fileToRead) => {
let reader = new FileReader();
reader.readAsText(fileToRead);
reader.onload = this.loadHandler;
reader.onerror = this.errorHandler;
}
loadHandler = (event) => {
const csv = event.target.result;
this.processData(csv);
}
errorHandler = (event) => {
if (event.target.error.name === 'NotReadableError') {
alert(event.target.error.name);
}
}
processData = (csv) => {
const allTextLines = csv.split(/\r\n|\n/);
const splittedLines = allTextLines.map(line => line.split(','));
const filteredLines = splittedLines.filter(line => (line[0].length > 0) && line);
this.setState({
splittedLines: filteredLines
});
}
createRows = rows => rows.map(data => {
return (
<ImportRow items={data} />
);
});
renderRow = memo(({ data, index, style }) => {
return (
<div style={style}>
{data[index]}
</div>
)
}, areEqual);
prepareUsersData = data => {
const { splittedLines } = this.state;
const rawUsers = splittedLines || data;
const preparedUsers = rawUsers.map(user => {
const userObj = {};
userObj.firstName = user[0];
userObj.lastName = user[1];
userObj.email = user[2];
return toEmployeeWrapper(userObj);
});
return preparedUsers;
}
runImport = users => {
// Use with axios
}
onImportClick = () => {
const users = this.prepareUsersData();
this.runImport(users);
}
render() {
const { splittedLines, completion } = this.state;
// const { t } = useTranslation();
const rows = this.createRows(splittedLines);
const renderList = ({ height, width }) => {
const itemHeight = (width < 768) ? 56 : 36;
return (
<List
className="List"
height={height}
width={width}
itemSize={itemHeight}
itemCount={rows.length}
itemData={rows}
>
{this.renderRow}
</List>
)
}
return (
<>
<Text.Body fontSize={18} >
Functionality at development stage.
</Text.Body>
<br />
<Text.Body fontSize={14} >
Files are formatted according to CSV RFC rules. <br />
Column Order: FirstName, LastName, Email. <br />
Comma delimiter, strings in unix format. <br />
</Text.Body>
<SelectSourceWrapper>
<StyledFileInput>
<Button size='big' primary={true} scale={true} label="Upload CSV" isDisabled={true} />
{false &&
<input type="file" name="file" onChange={(e) => this.getAsText(e.target.files[0])} accept='.csv' />
}
</StyledFileInput>
</SelectSourceWrapper>
<br />
<Text.Body fontSize={14} >
Ready for import: {`${splittedLines.length} of ${splittedLines.length}`}
</Text.Body>
<div style={{ position: 'relative', width: '100%', height: '30px' }}>
<StyledProgress completed={completion} />
</div>
<LoadCsvWrapper>
<AutoSizer>
{renderList}
</AutoSizer>
</LoadCsvWrapper>
<StyledFileInput>
<Button size='big' primary={true} scale={true} onClick={this.onImportClick} isDisabled={true} label="Start import" />
</StyledFileInput>
</>
);
}
}
function mapStateToProps(state) {
return {
};
}
export default connect(
mapStateToProps,
{
}
)(withRouter(SectionBodyContent));

View File

@ -0,0 +1,45 @@
import React from "react";
import { connect } from "react-redux";
import { Text, IconButton } from "asc-web-components";
import { withRouter } from "react-router";
import { useTranslation } from 'react-i18next';
const wrapperStyle = {
display: "flex",
alignItems: "center"
};
const textStyle = {
marginLeft: "16px",
marginRight: "16px"
};
const SectionHeaderContent = props => {
const { history, settings } = props;
//const { t } = useTranslation();
return (
<div style={wrapperStyle}>
<div style={{ width: "16px" }}>
<IconButton
iconName={"ArrowPathIcon"}
color="#A3A9AE"
size="16"
onClick={() => history.push(settings.homepage)}
/>
</div>
<Text.ContentHeader truncate={true} style={textStyle}>
Add users to the portal
</Text.ContentHeader>
</div>
);
};
function mapStateToProps(state) {
return {
profile: state.profile.targetUser,
settings: state.auth.settings
};
}
export default connect(mapStateToProps)(withRouter(SectionHeaderContent));

View File

@ -0,0 +1,2 @@
export { default as SectionHeaderContent } from './Header';
export { default as SectionBodyContent } from './Body';

View File

@ -0,0 +1,50 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/Reassign/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
}
};
newInstance.init({
resources: resources,
lng: 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,75 @@
import React from "react";
import { connect } from "react-redux";
// import PropTypes from "prop-types";
import { PageLayout } from "asc-web-components";
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
// import { SectionHeaderContent } from './Section';
// import { fetchProfile } from '../../../store/profile/actions';
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import { SectionHeaderContent, SectionBodyContent } from './Section';
class Import extends React.Component {
componentDidMount() {
// const { match, fetchProfile } = this.props;
// const { userId } = match.params;
// if (userId) {
// fetchProfile(userId);
// }
}
componentDidUpdate(prevProps) {
// const { match, fetchProfile } = this.props;
// const { userId } = match.params;
// const prevUserId = prevProps.match.params.userId;
// if (userId !== undefined && userId !== prevUserId) {
// fetchProfile(userId);
// }
}
render() {
console.log("Import render")
// let loaded = false;
// const { profile, match } = this.props;
// const { userId, type } = match.params;
// if (type) {
// loaded = true;
// } else if (profile) {
// loaded = profile.userName === userId || profile.id === userId;
// }
return (
<I18nextProvider i18n={i18n}>
<PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent />}
sectionBodyContent={<SectionBodyContent />}
/>
</I18nextProvider>
);
}
}
Import.propTypes = {
// match: PropTypes.object.isRequired,
// profile: PropTypes.object,
// fetchProfile: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
// profile: state.profile.targetUser
};
}
export default connect(mapStateToProps, {
})(Import);

View File

@ -0,0 +1,16 @@
{
"ReassignmentData": "Reassignment of data",
"ReassignsToUser": "Employee to whom the data will be transferred —",
"ChooseUser": "Choose user",
"ReassignsTransferedListHdr": "Will be transferred:",
"ReassignsTransferedListItem1": "General documents and personal documents that are available to other portal users;",
"ReassignsTransferedListItem2": "Open projects, milestones and tasks;",
"ReassignsTransferedListItem3": "Contacts, open tasks, unclosed opportunities and CRM cases;",
"NotBeUndone": "Note: this action cannot be undone.",
"ReassignsReadMore": "More about data transfer",
"DeleteProfileAfterReassignment": "Delete profile when reassignment will be finished",
"CancelButton": "Cancel",
"ReassignButton": "Reassign",
"LDAPLbl": "LDAP"
}

View File

@ -347,7 +347,13 @@ class ProfileInfo extends React.PureComponent {
className='language-combo'
/>
<TooltipIcon>
<HelpButton place="bottom" offsetLeft={50} offsetRight={0} tooltipContent={tooltipLanguage} />
<HelpButton
place="bottom"
offsetLeft={50}
offsetRight={0}
tooltipContent={tooltipLanguage}
HelpButtonHeaderContent={t('Language')}
/>
</TooltipIcon>
</InfoItemValue>

View File

@ -21,7 +21,8 @@ class RadioField extends React.Component {
radioIsDisabled,
radioOnChange,
tooltipContent
tooltipContent,
HelpButtonHeaderContent
} = this.props;
return (
@ -30,6 +31,7 @@ class RadioField extends React.Component {
hasError={hasError}
labelText={labelText}
tooltipContent={tooltipContent}
HelpButtonHeaderContent={HelpButtonHeaderContent}
>
<RadioButtonGroup
name={radioName}

View File

@ -32,7 +32,8 @@ class TextChangeField extends React.Component {
buttonOnClick,
buttonTabIndex,
tooltipContent
tooltipContent,
HelpButtonHeaderContent
} = this.props;
return (
@ -41,6 +42,7 @@ class TextChangeField extends React.Component {
hasError={hasError}
labelText={labelText}
tooltipContent={tooltipContent}
HelpButtonHeaderContent={HelpButtonHeaderContent}
>
<InputContainer>
<TextInput

View File

@ -21,7 +21,8 @@ class TextField extends React.Component {
inputOnChange,
inputAutoFocussed,
inputTabIndex,
tooltipContent
tooltipContent,
HelpButtonHeaderContent
} = this.props;
return (
@ -30,6 +31,7 @@ class TextField extends React.Component {
hasError={hasError}
labelText={labelText}
tooltipContent={tooltipContent}
HelpButtonHeaderContent={HelpButtonHeaderContent}
>
<TextInput
name={inputName}

View File

@ -356,6 +356,7 @@ class CreateUserForm extends React.Component {
inputOnChange={this.onInputChange}
inputTabIndex={3}
HelpButtonHeaderContent={t("Mail")}
tooltipContent={
<Trans i18nKey="EmailPopupHelper" i18n={i18n}>
The main e-mail is needed to restore access to the portal in case of loss of the password and send notifications. <p className="tooltip_email" style={{marginTop: "1rem", marginBottom: "1rem"}} >You can create a new mail on the domain as the primary. In this case, you must set a one-time password so that the user can log in to the portal for the first time.</p> The main e-mail can be used as a login when logging in to the portal.

View File

@ -506,6 +506,7 @@ class UpdateUserForm extends React.Component {
buttonOnClick={this.onEmailChange}
buttonTabIndex={1}
HelpButtonHeaderContent={t("Mail")}
tooltipContent={
<Trans i18nKey="EmailPopupHelper" i18n={i18n}>
The main e-mail is needed to restore access to the portal in case of loss of the password and send notifications. <p style={{height: "0", visibility: "hidden"}}>You can create a new mail on the domain as the primary. In this case, you must set a one-time password so that the user can log in to the portal for the first time.</p> The main e-mail can be used as a login when logging in to the portal.
@ -583,6 +584,7 @@ class UpdateUserForm extends React.Component {
radioOnChange={this.onUserTypeChange}
tooltipContent={tooltipTypeContent}
HelpButtonHeaderContent={t('UserType')}
/>
<DateField
calendarHeaderContent={t("CalendarSelectDate")}

View File

@ -13,6 +13,7 @@ const ActivateEmailForm = lazy(() => import("./sub-components/activateEmail"));
const ChangeEmailForm = lazy(() => import("./sub-components/changeEmail"));
const ChangePhoneForm = lazy(() => import("./sub-components/changePhone"));
const ProfileRemoveForm = lazy(() => import("./sub-components/profileRemove"));
const ChangeOwnerForm = lazy(() => import("./sub-components/changeOwner"));
const Error404 = lazy(() => import("../Error"));
const Confirm = ({ match, language }) => {
@ -61,6 +62,11 @@ const Confirm = ({ match, language }) => {
path={`${match.path}/PhoneActivation`}
component={ChangePhoneForm}
/>
<Route
exact
path={`${match.path}/ownerchange`}
component={ChangeOwnerForm}
/>
<Route component={Error404} />
</Switch>
</Suspense>

View File

@ -22,5 +22,9 @@
"DeleteProfileConfirmation": "Attention! You are about to delete your account.",
"DeleteProfileConfirmationInfo": "By clicking the \"Delete my account\" button you agree with our Privacy policy.",
"DeleteProfileSuccessMessage": "Your account has been successfully deleted.",
"DeleteProfileSuccessMessageInfo": "See our Privacy policy to learn more about deleting your account and data accociated with it."
"DeleteProfileSuccessMessageInfo": "See our Privacy policy to learn more about deleting your account and data accociated with it.",
"ConfirmOwnerPortalTitle": "Please confirm that you want to change portal owner to {{newOwner}}",
"SaveButton": "Save",
"CancelButton": "Cancel",
"ConfirmOwnerPortalSuccessMessage": "Владелец портала был успешно изменен. {0} Через 10 секунд вы будете перенаправлены {1} сюда {2}"
}

View File

@ -17,5 +17,9 @@
"PasswordCustomMode": "Пароль",
"ImportContactsOkButton": "OK",
"LoadingProcessing": "Загрузка...",
"ChangePasswordSuccess": "Пароль был успешно изменен"
"ChangePasswordSuccess": "Пароль был успешно изменен",
"ConfirmOwnerPortalTitle": "Пожалуйста, подтвердите, что Вы хотите изменить владельца портала на {{newOwner}}",
"SaveButton": "Сохранить",
"CancelButton": "Отмена",
"ConfirmOwnerPortalSuccessMessage": "Portal owner has been successfully changed. {0}In 10 seconds you will be redirected {1}here{2}"
}

View File

@ -0,0 +1,146 @@
import React from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import styled from "styled-components";
import { Container, Row, Col, Card, CardTitle, CardImg } from "reactstrap";
import { Button, PageLayout, Text, toastr } from "asc-web-components";
//import { } from "../../../../../src/store/auth/actions";
const BodyStyle = styled(Container)`
margin-top: 70px;
.buttons-style {
margin-top: 20px;
min-width: 110px;
}
.button-style {
margin-right: 8px;
}
.confirm_text {
margin: 12px 0;
}
.password-card {
border: none;
.card-img {
max-width: 216px;
max-height: 35px;
}
.card-title {
word-wrap: break-word;
margin: 8px 0;
text-align: left;
font-size: 24px;
color: #116d9d;
}
}
.row_display {
display: flex;
}
.confirm-text {
margin-top: 32px;
}
`;
class Form extends React.PureComponent {
constructor(props) {
super(props);
this.state = { showButtons: true };
}
onAcceptClick = () => {
this.setState({ showButtons: false });
toastr.success("Accept click");
setTimeout(this.onRedirect, 10000);
};
onRedirect = () => {
this.props.history.push("/");
};
onCancelClick = () => {
this.props.history.push("/");
};
render() {
const { t, greetingTitle } = this.props;
const mdOptions = { size: 6, offset: 2 };
return (
<BodyStyle>
<Row className="password-row">
<Col sm="12" md={mdOptions}>
<Card className="password-card">
<CardImg
className="card-img"
src="images/dark_general.png"
alt="Logo"
top
/>
<CardTitle className="card-title">{greetingTitle}</CardTitle>
</Card>
</Col>
</Row>
<Row>
<Col sm="12" md={{ size: 12, offset: 2 }}>
<Text.Body className="confirm_text" fontSize={18}>
{t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })}
</Text.Body>
</Col>
</Row>
{this.state.showButtons ? (
<Row>
<Col className="row_display" sm="12" md={mdOptions}>
<Button
className="button-style buttons-style"
primary
size="big"
label={t("SaveButton")}
tabIndex={2}
isDisabled={false}
onClick={this.onAcceptClick}
/>
<Button
className="buttons-style"
size="big"
label={t("CancelButton")}
tabIndex={2}
isDisabled={false}
onClick={this.onCancelClick}
/>
</Col>
</Row>
) : (
<Row>
<Col sm="12" md={{ size: 12, offset: 2 }}>
<Text.Body className="confirm-text" fontSize={12}>
{t("ConfirmOwnerPortalSuccessMessage")}
</Text.Body>
</Col>
</Row>
)}
</BodyStyle>
);
}
}
Form.propTypes = {};
Form.defaultProps = {};
const ChangePasswordForm = props => (
<PageLayout sectionBodyContent={<Form {...props} />} />
);
function mapStateToProps(state) {
return { greetingTitle: state.auth.settings.greetingSettings };
}
export default connect(
mapStateToProps,
{}
)(withRouter(withTranslation()(ChangePasswordForm)));

View File

@ -273,6 +273,7 @@ const Form = props => {
/>
<TooltipStyle>
<HelpButton
HelpButtonHeaderContent={t('CookieSettingsTitle')}
tooltipContent={
<Text.Body fontSize={12}>{t("RememberHelper")}</Text.Body>
}

View File

@ -11,5 +11,6 @@
"SendButton": "Send",
"CancelButton": "Cancel",
"Remember": "Remember",
"RememberHelper": "The default session lifetime is 20 minutes. Check this option to set it to 1 year. To set your own value, go to the settings."
"RememberHelper": "The default session lifetime is 20 minutes. Check this option to set it to 1 year. To set your own value, go to the settings.",
"CookieSettingsTitle": "Session Lifetime"
}

View File

@ -11,5 +11,6 @@
"SendButton": "Отправить",
"CancelButton": "Отмена",
"Remember": "Запомнить",
"RememberHelper": "Время существования сессии по умолчанию составляет 20 минут. Отметьте эту опцию, чтобы установить значение 1 год. Чтобы задать собственное значение, перейдите в настройки."
"RememberHelper": "Время существования сессии по умолчанию составляет 20 минут. Отметьте эту опцию, чтобы установить значение 1 год. Чтобы задать собственное значение, перейдите в настройки.",
"CookieSettingsTitle": "Время жизни сессии"
}

View File

@ -177,6 +177,7 @@ class Customization extends React.Component {
className='margin-top'
labelText={`${t("Language")}:`}
tooltipContent={tooltipLanguage}
HelpButtonHeaderContent={t("Language")}
isVertical={true}>
<ComboBox
id='comboBoxLanguage'

View File

@ -73,7 +73,6 @@ class PureAccessRights extends Component {
return true;
}
render() {
const { isLoading, selectedTab } = this.state;

View File

@ -7,6 +7,7 @@ import { I18nextProvider, withTranslation } from "react-i18next";
import styled from "styled-components";
import {
changeAdmins,
getUpdateListAdmin,
fetchPeople
} from "../../../../../../store/settings/actions";
import {
@ -22,11 +23,20 @@ import {
toastr,
FilterInput,
Button,
RequestLoader
RequestLoader,
Loader,
EmptyScreenContainer,
Icons
} from "asc-web-components";
import { getUserRole } from "../../../../../../store/settings/selectors";
import isEmpty from "lodash/isEmpty";
const AdminsContainer = styled.div`
.hidden-icon {
position: fixed;
visibility: hidden;
}
`;
const ToggleContentContainer = styled.div`
.buttons_container {
display: flex;
@ -53,7 +63,6 @@ const ToggleContentContainer = styled.div`
}
.filter_container {
margin-bottom: 50px;
margin-top: 16px;
}
`;
@ -68,17 +77,15 @@ class PureAdminsSettings extends Component {
allOptions: [],
options: [],
isLoading: false,
showLoader: true,
selectedOptions: []
};
}
componentDidMount() {
const { admins, options } = this.props;
const { admins, options, fetchPeople } = this.props;
if (isEmpty(admins, true) || isEmpty(options, true)) {
const { fetchPeople } = this.props;
this.onLoading(true);
const newFilter = this.onAdminsFilter();
fetchPeople(newFilter)
.catch(error => {
@ -86,10 +93,12 @@ class PureAdminsSettings extends Component {
})
.finally(() =>
this.setState({
isLoading: false,
showLoader: false,
allOptions: this.props.options
})
);
} else {
this.setState({ showLoader: false });
}
}
@ -153,30 +162,32 @@ class PureAdminsSettings extends Component {
};
onChangePage = pageItem => {
const { filter, fetchPeople } = this.props;
const { filter, getUpdateListAdmin } = this.props;
const newFilter = filter.clone();
newFilter.page = pageItem.key;
this.onLoading(true);
fetchPeople(newFilter)
getUpdateListAdmin(newFilter)
.catch(res => console.log(res))
.finally(() => this.onLoading(false));
};
onChangePageSize = pageItem => {
const { filter, fetchPeople } = this.props;
const { filter, getUpdateListAdmin } = this.props;
const newFilter = filter.clone();
newFilter.page = 0;
newFilter.pageCount = pageItem.key;
this.onLoading(true);
fetchPeople(newFilter)
getUpdateListAdmin(newFilter)
.catch(res => console.log(res))
.finally(() => this.onLoading(false));
};
onPrevClick = e => {
const { filter, fetchPeople } = this.props;
const { filter, getUpdateListAdmin } = this.props;
if (!filter.hasPrev()) {
e.preventDefault();
@ -185,13 +196,13 @@ class PureAdminsSettings extends Component {
const newFilter = filter.clone();
newFilter.page--;
this.onLoading(true);
fetchPeople(newFilter)
getUpdateListAdmin(newFilter)
.catch(res => console.log(res))
.finally(() => this.onLoading(false));
};
onNextClick = e => {
const { filter, fetchPeople } = this.props;
const { filter, getUpdateListAdmin } = this.props;
if (!filter.hasNext()) {
e.preventDefault();
@ -200,7 +211,8 @@ class PureAdminsSettings extends Component {
const newFilter = filter.clone();
newFilter.page++;
this.onLoading(true);
fetchPeople(newFilter)
getUpdateListAdmin(newFilter)
.catch(res => console.log(res))
.finally(() => this.onLoading(false));
};
@ -220,7 +232,7 @@ class PureAdminsSettings extends Component {
};
onFilter = data => {
const { filter, fetchPeople } = this.props;
const { filter, getUpdateListAdmin } = this.props;
const search = data.inputValue || null;
const sortBy = data.sortId;
@ -235,11 +247,23 @@ class PureAdminsSettings extends Component {
newFilter.role = "admin";
newFilter.search = search;
this.onLoading(true);
fetchPeople(newFilter)
getUpdateListAdmin(newFilter)
.catch(res => console.log(res))
.finally(this.onLoading(false));
};
onResetFilter = () => {
const { getUpdateListAdmin, filter } = this.props;
const newFilter = filter.clone(true);
this.onLoading(true);
getUpdateListAdmin(newFilter)
.catch(res => console.log(res))
.finally(() => this.onLoading(false));
};
filterUserSelectorOptions = (options, template) =>
options.filter(option => option.label.indexOf(template) > -1);
@ -304,7 +328,8 @@ class PureAdminsSettings extends Component {
options,
selectedOptions,
isLoading,
showFullAdminSelector
showFullAdminSelector,
showLoader
} = this.state;
const countElements = filter.total;
@ -312,167 +337,198 @@ class PureAdminsSettings extends Component {
console.log("Admins render_");
return (
<>
<RequestLoader
visible={isLoading}
zIndex={256}
loaderSize={16}
loaderColor={"#999"}
label={`${t("LoadingProcessing")} ${t("LoadingDescription")}`}
fontSize={12}
fontColor={"#999"}
className="page_loader"
/>
/*TODO: delete after resolve icon button problem*/
<AdminsContainer>
<IconButton className="hidden-icon" iconName="SearchIcon" />
<ToggleContentContainer>
<div className="buttons_container">
<Button
className="button_style"
size="medium"
primary={true}
label="Set people admin"
isDisabled={isLoading}
onClick={this.onShowGroupSelector}
{showLoader ? (
<Loader className="pageLoader" type="rombs" size={40} />
) : (
<>
<RequestLoader
visible={isLoading}
zIndex={256}
loaderSize={16}
loaderColor={"#999"}
label={`${t("LoadingProcessing")} ${t("LoadingDescription")}`}
fontSize={12}
fontColor={"#999"}
className="page_loader"
/>
<div style={{ right: 180 }} className="advanced-selector">
<AdvancedSelector
displayType="dropdown"
isOpen={showSelector}
placeholder="placeholder"
options={options}
onSearchChanged={this.onSearchUsers}
//groups={groups}
isMultiSelect={true}
buttonLabel="Add members"
onSelect={this.onSelect}
onCancel={this.onShowGroupSelector}
onAddNewClick={() => console.log("onAddNewClick")}
selectAllLabel="selectorSelectAllText"
selectedOptions={selectedOptions}
/>
</div>
<Button
size="medium"
primary={true}
label="Set portal admin"
isDisabled={isLoading}
onClick={this.onShowFullAdminGroupSelector}
/>
<div style={{ right: 160 }} className="advanced-selector">
<AdvancedSelector
displayType="dropdown"
isOpen={showFullAdminSelector}
placeholder="placeholder"
options={options}
onSearchChanged={this.onSearchUsers}
//groups={groups}
isMultiSelect={true}
buttonLabel="Add members"
onSelect={this.onSelectFullAdmin}
onCancel={this.onShowFullAdminGroupSelector}
onAddNewClick={() => console.log("onAddNewClick")}
selectAllLabel="selectorSelectAllText"
selectedOptions={selectedOptions}
/>
</div>
</div>
{countElements > 25 ? (
<FilterInput
className="filter_container"
getFilterData={() => []}
getSortData={this.getSortData}
onFilter={this.onFilter}
/>
) : null}
<div className="wrapper">
<RowContainer manualHeight={`${admins.length * 50}px`}>
{admins.map(user => {
const element = (
<Avatar
size="small"
role={getUserRole(user)}
userName={user.displayName}
source={user.avatar}
<ToggleContentContainer>
<div className="buttons_container">
<Button
className="button_style"
size="medium"
primary={true}
label="Set people admin"
isDisabled={isLoading}
onClick={this.onShowGroupSelector}
/>
<div style={{ right: 180 }} className="advanced-selector">
<AdvancedSelector
displayType="dropdown"
isOpen={showSelector}
placeholder="placeholder"
options={options}
onSearchChanged={this.onSearchUsers}
//groups={groups}
isMultiSelect={true}
buttonLabel="Add members"
onSelect={this.onSelect}
onCancel={this.onShowGroupSelector}
onAddNewClick={() => console.log("onAddNewClick")}
selectAllLabel="selectorSelectAllText"
selectedOptions={selectedOptions}
/>
);
const nameColor =
user.status === "pending" ? "#A3A9AE" : "#333333";
</div>
return (
<Row
key={user.id}
status={user.status}
data={user}
element={element}
>
<RowContent disableSideInfo={true}>
<Link
containerWidth="120px"
type="page"
title={user.displayName}
isBold={true}
fontSize={15}
color={nameColor}
href={user.profileUrl}
>
{user.displayName}
</Link>
<div style={{ maxWidth: 120 }} />
<Button
size="medium"
primary={true}
label="Set portal admin"
isDisabled={isLoading}
onClick={this.onShowFullAdminGroupSelector}
/>
<div style={{ right: 160 }} className="advanced-selector">
<AdvancedSelector
displayType="dropdown"
isOpen={showFullAdminSelector}
placeholder="placeholder"
options={options}
onSearchChanged={this.onSearchUsers}
//groups={groups}
isMultiSelect={true}
buttonLabel="Add members"
onSelect={this.onSelectFullAdmin}
onCancel={this.onShowFullAdminGroupSelector}
onAddNewClick={() => console.log("onAddNewClick")}
selectAllLabel="selectorSelectAllText"
selectedOptions={selectedOptions}
/>
</div>
</div>
<Text.Body>
{user.isAdmin ? "Full access" : "People module admin"}
</Text.Body>
{!user.isOwner ? (
<IconButton
className="remove_icon"
size="16"
isDisabled={isLoading}
onClick={this.onChangeAdmin.bind(
this,
[user.id],
false,
"00000000-0000-0000-0000-000000000000"
)}
iconName={"CatalogTrashIcon"}
isFill={true}
isClickable={false}
/>
) : (
<div />
)}
</RowContent>
</Row>
);
})}
</RowContainer>
</div>
{countElements > 25 ? (
<div className="wrapper">
<Paging
previousLabel={t("PreviousPage")}
nextLabel={t("NextPage")}
openDirection="top"
countItems={this.countItems()}
pageItems={this.pageItems()}
displayItems={false}
selectedPageItem={this.selectedPageItem()}
selectedCountItem={this.selectedCountItem()}
onSelectPage={this.onChangePage}
onSelectCount={this.onChangePageSize}
previousAction={this.onPrevClick}
nextAction={this.onNextClick}
disablePrevious={!filter.hasPrev()}
disableNext={!filter.hasNext()}
<FilterInput
className="filter_container"
getFilterData={() => []}
getSortData={this.getSortData}
onFilter={this.onFilter}
/>
</div>
) : null}
</ToggleContentContainer>
</>
{admins.length > 0 ? (
<div className="wrapper">
<RowContainer manualHeight={`${admins.length * 50}px`}>
{admins.map(user => {
const element = (
<Avatar
size="small"
role={getUserRole(user)}
userName={user.displayName}
source={user.avatar}
/>
);
const nameColor =
user.status === "pending" ? "#A3A9AE" : "#333333";
return (
<Row
key={user.id}
status={user.status}
data={user}
element={element}
>
<RowContent disableSideInfo={true}>
<Link
containerWidth="120px"
type="page"
title={user.displayName}
isBold={true}
fontSize={15}
color={nameColor}
href={user.profileUrl}
>
{user.displayName}
</Link>
<div style={{ maxWidth: 120 }} />
<Text.Body>
{user.isAdmin
? "Full access"
: "People module admin"}
</Text.Body>
{!user.isOwner ? (
<IconButton
className="remove_icon"
size="16"
isDisabled={isLoading}
onClick={this.onChangeAdmin.bind(
this,
[user.id],
false,
"00000000-0000-0000-0000-000000000000"
)}
iconName={"CatalogTrashIcon"}
isFill={true}
isClickable={false}
/>
) : (
<div />
)}
</RowContent>
</Row>
);
})}
</RowContainer>
</div>
) : countElements > 25 ? (
<div className="wrapper">
<Paging
previousLabel={t("PreviousPage")}
nextLabel={t("NextPage")}
openDirection="top"
countItems={this.countItems()}
pageItems={this.pageItems()}
displayItems={false}
selectedPageItem={this.selectedPageItem()}
selectedCountItem={this.selectedCountItem()}
onSelectPage={this.onChangePage}
onSelectCount={this.onChangePageSize}
previousAction={this.onPrevClick}
nextAction={this.onNextClick}
disablePrevious={!filter.hasPrev()}
disableNext={!filter.hasNext()}
/>
</div>
) : (
<EmptyScreenContainer
imageSrc="products/people/images/empty_screen_filter.png"
imageAlt="Empty Screen Filter image"
headerText={t("NotFoundTitle")}
descriptionText={t("NotFoundDescription")}
buttons={
<>
<Icons.CrossIcon
size="small"
style={{ marginRight: "4px" }}
/>
<Link
type="action"
isHovered={true}
onClick={this.onResetFilter}
>
{t("ClearButton")}
</Link>
</>
}
/>
)}
</ToggleContentContainer>
</>
)}
</AdminsContainer>
);
}
}
@ -522,6 +578,8 @@ AdminsSettings.propTypes = {
options: PropTypes.arrayOf(PropTypes.object)
};
export default connect(mapStateToProps, { changeAdmins, fetchPeople })(
withRouter(AdminsSettings)
);
export default connect(mapStateToProps, {
changeAdmins,
fetchPeople,
getUpdateListAdmin
})(withRouter(AdminsSettings));

View File

@ -5,18 +5,39 @@ import { withRouter } from "react-router";
import i18n from "../../../i18n";
import { I18nextProvider, withTranslation } from "react-i18next";
import styled from "styled-components";
import { getPortalOwner } from "../../../../../../store/settings/actions";
import {
getPortalOwner,
getUsersOptions
} from "../../../../../../store/settings/actions";
import {
Text,
Avatar,
Link,
toastr,
Button,
RequestLoader
RequestLoader,
AdvancedSelector,
Loader
} from "asc-web-components";
import { isArrayEqual } from "../../../utils/isArrayEqual";
import isEmpty from "lodash/isEmpty";
const OwnerContainer = styled.div`
.link_style {
margin-right: 16px;
}
.text-body_wrapper {
margin-bottom: 16px;
}
.advanced-selector {
position: relative;
}
.text-body_inline {
display: inline-flex;
}
.button_offset {
margin-right: 16px;
}
`;
const HeaderContainer = styled.div`
margin: 40px 0 16px 0;
`;
@ -60,44 +81,85 @@ class PureOwnerSettings extends Component {
super(props);
this.state = {
isLoading: false
isLoading: false,
showSelector: false,
options: [],
allOptions: [],
showLoader: true,
selectedOwner: null
};
}
componentDidMount() {
const { owner, getPortalOwner, ownerId } = this.props;
const {
owner,
getPortalOwner,
ownerId,
options,
getUsersOptions
} = this.props;
if (isEmpty(owner, true)) {
this.onLoading(true);
getPortalOwner(ownerId)
.catch(error => {
toastr.error(error);
})
.finally(() => this.onLoading(false));
.finally(() => this.setState({ showLoader: false }));
}
}
shouldComponentUpdate(nextProps, nextState) {
const { owner, ownerId } = this.props;
if (
ownerId === nextProps.ownerId &&
this.state.isLoading === nextState.isLoading &&
isArrayEqual(owner, nextProps.owner)
) {
return false;
if (isEmpty(options, true)) {
getUsersOptions()
.catch(error => {
toastr.error(error);
})
.finally(() =>
this.setState({
showLoader: false,
allOptions: this.props.options
})
);
}
return true;
this.setState({ showLoader: false });
}
onChangeOwner = () => {
toastr.warning("onChangeOwner");
const { t, owner } = this.props;
toastr.success(t("DnsChangeMsg", { email: owner.email }));
};
onLoading = status => this.setState({ isLoading: status });
onShowSelector = status => {
this.setState({
options: this.props.options,
showSelector: status
});
};
onSearchUsers = template => {
const options = this.filterUserSelectorOptions(
this.state.allOptions,
template
);
this.setState({ options: options });
};
onSelect = selected => {
this.onShowSelector(false);
this.setState({ selectedOwner: selected });
};
filterUserSelectorOptions = (options, template) =>
options.filter(option => option.label.indexOf(template) > -1);
render() {
const { t, owner } = this.props;
const { isLoading } = this.state;
const {
isLoading,
showLoader,
showSelector,
options,
selectedOwner
} = this.state;
const OwnerOpportunities = t("AccessRightsOwnerOpportunities").split("|");
@ -105,59 +167,110 @@ class PureOwnerSettings extends Component {
return (
<>
<RequestLoader
visible={isLoading}
zIndex={256}
loaderSize={16}
loaderColor={"#999"}
label={`${t("LoadingProcessing")} ${t("LoadingDescription")}`}
fontSize={12}
fontColor={"#999"}
className="page_loader"
/>
<HeaderContainer>
<Text.Body fontSize={18}>{t("PortalOwner")}</Text.Body>
</HeaderContainer>
<BodyContainer>
<AvatarContainer>
<Avatar
className="avatar_wrapper"
size="big"
role="owner"
userName={owner.userName}
source={owner.avatar}
{showLoader ? (
<Loader className="pageLoader" type="rombs" size={40} />
) : (
<OwnerContainer>
<RequestLoader
visible={isLoading}
zIndex={256}
loaderSize={16}
loaderColor={"#999"}
label={`${t("LoadingProcessing")} ${t("LoadingDescription")}`}
fontSize={12}
fontColor={"#999"}
className="page_loader"
/>
<div className="avatar_body">
<Text.Body className="avatar_text" fontSize={16} isBold={true}>
{owner.displayName}
</Text.Body>
{owner.groups &&
owner.groups.map(group => (
<Link fontSize={12} key={group.id} href={owner.profileUrl}>
{group.name}
</Link>
))}
<HeaderContainer>
<Text.Body fontSize={18}>{t("PortalOwner")}</Text.Body>
</HeaderContainer>
<BodyContainer>
<AvatarContainer>
<Avatar
className="avatar_wrapper"
size="big"
role="owner"
userName={owner.userName}
source={owner.avatar}
/>
<div className="avatar_body">
<Text.Body
className="avatar_text"
fontSize={16}
isBold={true}
>
{owner.displayName}
</Text.Body>
{owner.groups &&
owner.groups.map(group => (
<Link
fontSize={12}
key={group.id}
href={owner.profileUrl}
>
{group.name}
</Link>
))}
</div>
</AvatarContainer>
<ProjectsBody>
<Text.Body className="portal_owner" fontSize={12}>
{t("AccessRightsOwnerCan")}:
</Text.Body>
<Text.Body fontSize={12}>
{OwnerOpportunities.map((item, key) => (
<li key={key}>{item};</li>
))}
</Text.Body>
</ProjectsBody>
</BodyContainer>
<Text.Body fontSize={12} className="text-body_wrapper">
{t("AccessRightsChangeOwnerText")}
</Text.Body>
<Link
className="link_style"
isHovered={true}
onClick={this.onShowSelector.bind(this, !showSelector)}
>
{selectedOwner ? selectedOwner.label : t("ChooseOwner")}
</Link>
<Button
className="button_offset"
size="medium"
primary={true}
label="Change portal owner"
isDisabled={!isLoading ? selectedOwner === null : false}
onClick={this.onChangeOwner}
/>
<Text.Body
className="text-body_inline"
fontSize={12}
color="#A3A9AE"
>
{t("AccessRightsChangeOwnerConfirmText")}
</Text.Body>
<div className="advanced-selector">
<AdvancedSelector
displayType="dropdown"
isOpen={showSelector}
placeholder="placeholder"
options={options}
onSearchChanged={this.onSearchUsers}
//groups={groups}
buttonLabel="Add members"
onSelect={this.onSelect}
onCancel={this.onShowSelector.bind(this, false)}
onAddNewClick={() => console.log("onAddNewClick")}
selectAllLabel="selectorSelectAllText"
/>
</div>
</AvatarContainer>
<ProjectsBody>
<Text.Body className="portal_owner" fontSize={12}>
{t("AccessRightsOwnerCan")}:
</Text.Body>
<Text.Body fontSize={12}>
{OwnerOpportunities.map((item, key) => (
<li key={key}>{item};</li>
))}
</Text.Body>
</ProjectsBody>
</BodyContainer>
<Button
size="medium"
primary={true}
label="Change portal owner"
isDisabled={isLoading}
onClick={this.onChangeOwner}
/>
</OwnerContainer>
)}
</>
);
}
@ -178,9 +291,12 @@ const OwnerSettings = props => {
};
function mapStateToProps(state) {
const { owner, users } = state.settings.security.accessRight;
return {
ownerId: state.auth.settings.ownerId,
owner: state.settings.security.accessRight.owner
owner,
options: users
};
}
@ -192,6 +308,6 @@ OwnerSettings.propTypes = {
owner: PropTypes.object
};
export default connect(mapStateToProps, { getPortalOwner })(
export default connect(mapStateToProps, { getPortalOwner, getUsersOptions })(
withRouter(OwnerSettings)
);

View File

@ -58,8 +58,13 @@
"LogoDocsEditor": "Logo for the editors header",
"ChangeLogoButton": "Change Logo",
"BrowserNoCanvasSupport": "Your browser does not support the HTML5 canvas tag.",
"AccessRightsChangeOwnerText": "To change the Portal owner please choose the Name of the new Portal owner below.",
"ChooseOwner": "Choose owner",
"DnsChangeMsg": "A link to confirm the operation has been sent to {{email}} (the email address of the portal owner).",
"AccessRightsChangeOwnerConfirmText": "Changes will be applied after the confirmation via email.",
"NotFoundTitle": "No results matching your search could be found",
"NotFoundDescription": "No people matching your filter can be displayed in this section. Please select other filter options or clear filter to view all the people in this section.",
"ClearButton": "Reset filter",
"ViewProfilesAndGroups": "View profiles and groups",

View File

@ -57,6 +57,13 @@
"LogoDocsEditor": "Логотип для шапки редакторов",
"ChangeLogoButton": "Сменить логотип",
"BrowserNoCanvasSupport": "Ваш браузер не поддерживает тег HTML5 canvas.",
"AccessRightsChangeOwnerText": "Чтобы сменить владельца портала, выберите имя нового владельца портала ниже.",
"ChooseOwner": "Выбрать владельца",
"DnsChangeMsg": "Ссылка для подтверждения операции была отправлена на {{email}} (адрес электронной почты владельца портала).",
"AccessRightsChangeOwnerConfirmText": "Изменения будут применены после подтверждения по электронной почте.",
"NotFoundTitle": "Результатов, соответствующих заданным критериям, не найдено",
"NotFoundDescription": "В данном разделе нет людей, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы просмотреть всех людей в этом разделе.",
"ClearButton": "Сбросить фильтр",
"ViewProfilesAndGroups": "Просматривать профили и группы",

View File

@ -27,7 +27,8 @@
"PasswordRecoveryTitle",
"MessageSendPasswordRecoveryInstructionsOnEmail",
"RememberHelper",
"LoadingDescription"
"LoadingDescription",
"CookieSettingsTitle"
]
},
"Confirm": {
@ -52,7 +53,9 @@
"DeleteProfileConfirmation",
"DeleteProfileConfirmationInfo",
"ChangePasswordSuccess",
"Remember"
"Remember",
"ConfirmOwnerPortalTitle",
"ConfirmOwnerPortalSuccessMessage"
]
},
"Settings": {
@ -94,7 +97,10 @@
"RestoreDefaultButton",
"SuccessfullySaveGreetingSettingsMessage",
"ChangeLogoButton",
"Employees"
"Employees",
"AccessRightsChangeOwnerText",
"DnsChangeMsg",
"AccessRightsChangeOwnerConfirmText"
],
"FeedResource": [
"ProjectsProduct",
@ -122,6 +128,9 @@
"UserControlsCommonResource": [
"NextPage",
"PreviousPage"
],
"ResourceJS": [
"ChooseOwner"
]
}
},

View File

@ -6,15 +6,23 @@ import Filter from "./filter";
export const SET_USERS = "SET_USERS";
export const SET_ADMINS = "SET_ADMINS";
export const SET_OWNER = "SET_OWNER";
export const SET_OPTIONS = "SET_OPTIONS";
export const SET_FILTER = "SET_FILTER";
export const SET_LOGO_TEXT = "SET_LOGO_TEXT";
export const SET_LOGO_SIZES = "SET_LOGO_SIZES";
export const SET_LOGO_URLS = "SET_LOGO_URLS";
export function setUsers(options) {
export function setOptions(options) {
return {
type: SET_OPTIONS,
options
};
}
export function setUsers(users) {
return {
type: SET_USERS,
options
users
};
}
@ -81,7 +89,7 @@ export function changeAdmins(userIds, productId, isAdmin, filter) {
const newOptions = getSelectorOptions(options);
filterData.total = admins.total;
dispatch(setUsers(newOptions));
dispatch(setOptions(newOptions));
dispatch(setAdmins(admins.items));
dispatch(setFilter(filterData));
})
@ -102,45 +110,66 @@ export function fetchPeople(filter) {
}
return dispatch => {
return axios
.all([api.getUserList(filterData), api.getListAdmins(filterData)])
.then(
axios.spread((users, admins) => {
const options = getUserOptions(users.items, admins.items);
const newOptions = getSelectorOptions(options);
filterData.total = admins.total;
return axios.all([api.getUserList(), api.getListAdmins(filterData)]).then(
axios.spread((users, admins) => {
const options = getUserOptions(users.items, admins.items);
const newOptions = getSelectorOptions(options);
const usersOptions = getSelectorOptions(users.items);
filterData.total = admins.total;
dispatch(setAdmins(admins.items));
dispatch(setUsers(newOptions));
dispatch(setFilter(filterData));
})
);
dispatch(setUsers(usersOptions));
dispatch(setAdmins(admins.items));
dispatch(setOptions(newOptions));
dispatch(setFilter(filterData));
})
);
};
}
export function getUpdateListAdmin(filter) {
let filterData = filter && filter.clone();
if (!filterData) {
filterData = Filter.getDefault();
}
return dispatch => {
return api.getListAdmins(filterData).then(admins => {
filterData.total = admins.total;
dispatch(setAdmins(admins.items));
dispatch(setFilter(filterData));
});
};
}
export function getUsersOptions() {
return dispatch => {
return api.getUserList().then(users => {
const usersOptions = getSelectorOptions(users.items);
dispatch(setUsers(usersOptions));
});
};
}
export function getWhiteLabelLogoText() {
return dispatch => {
return api.getLogoText()
.then(res => {
return api.getLogoText().then(res => {
dispatch(setLogoText(res));
});
};
};
}
export function getWhiteLabelLogoSizes() {
return dispatch => {
return api.getLogoSizes()
.then(res => {
return api.getLogoSizes().then(res => {
dispatch(setLogoSizes(res));
});
};
};
}
export function getWhiteLabelLogoUrls() {
return dispatch => {
return api.getLogoUrls()
.then(res => {
return api.getLogoUrls().then(res => {
dispatch(setLogoUrls(Object.values(res)));
});
};
};
}

View File

@ -1,5 +1,5 @@
import { SET_USERS, SET_ADMINS, SET_OWNER, SET_FILTER, SET_LOGO_TEXT, SET_LOGO_SIZES, SET_LOGO_URLS } from "./actions";
import { SET_USERS, SET_ADMINS, SET_OWNER, SET_OPTIONS, SET_FILTER, SET_LOGO_TEXT, SET_LOGO_SIZES, SET_LOGO_URLS } from "./actions";
import Filter from "./filter";
const initialState = {
@ -13,6 +13,7 @@ const initialState = {
security: {
accessRight: {
options: [],
users: [],
admins: [],
owner: {},
filter: Filter.getDefault()
@ -22,7 +23,7 @@ const initialState = {
const peopleReducer = (state = initialState, action) => {
switch (action.type) {
case SET_USERS:
case SET_OPTIONS:
return Object.assign({}, state, {
security: Object.assign({}, state.security, {
accessRight: Object.assign({}, state.security.accessRight, {
@ -30,6 +31,14 @@ const peopleReducer = (state = initialState, action) => {
})
})
});
case SET_USERS:
return Object.assign({}, state, {
security: Object.assign({}, state.security, {
accessRight: Object.assign({}, state.security.accessRight, {
users: action.users
})
})
});
case SET_ADMINS:
return Object.assign({}, state, {
security: Object.assign({}, state.security, {

View File

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

View File

@ -1,43 +1,48 @@
# Avatar Editor
## Usage
Used to display user avatar editor on page.
### Usage
```js
import { AvatarEditor } from 'asc-web-components';
import { AvatarEditor } from "asc-web-components";
```
#### Description
Required to display user avatar editor on page.
#### Usage
```js
```jsx
<AvatarEditor
visible={this.state.isOpen}
onClose={() =>{}}
onSave={(data) =>{console.log(data.isUpdate, data.croppedImageInfo)}}
onImageChange={(data) =>{console.log(croppedImageInfo)}
/>
visible={true}
onClose={() => {}}
onSave={() => {})}
onDeleteImage={() => {})}
onImageChange={() => {})}
onLoadFile={() => {}}
headerLabel="Edit Photo"
chooseFileLabel="Drop files here, or click to select files"
saveButtonLabel="Save"
maxSizeFileError="Maximum file size exceeded"
unknownTypeError="Unknown image file type"
unknownError="Error"
displayType="auto"
/>
```
#### Properties
### Properties
| Props | Type | Required | Values | Default | Description |
| ------------------ | -------- | :------: | -------------------------------- | ----------------------------- | ----------------------------------------------------- |
| `visible` | `bool` | - | | `false` | Display avatar editor or not |
| `image` | `string/file`| - | | | The URL of the image to use, or a File |
| `accept` | `array` | - | | `['image/png', 'image/jpeg']` | Accepted file types |
| `displayType` | `oneOf` | - | `auto`, `modal`, `aside` | `auto` | |
| `chooseFileLabel` | `string` | - | | `Choose a file` | |
| `headerLabel` | `string` | - | | `Edit Photo` | |
| `saveButtonLabel` | `string` | - | | `Save` | |
| `maxSizeFileError` | `string` | - | | `Maximum file size exceeded` | |
| `unknownTypeError` | `string` | - | | `Unknown image file type` | |
| `unknownError` | `string` | - | | `Error` | |
| `maxSize` | `number` | - | | `1` | Max size of image |
| `onSave` | `function` | - | | | |
| `onClose` | `function` | - | | | |
| `onDeleteImage` | `function` | - | | | |
| `onLoadFile` | `function` | - | | | |
| `onImageChange` | `function` | - | | | |
| Props | Type | Required | Values | Default | Description |
| ------------------ | :-------------: | :------: | :----------------------: | :---------------------------: | ---------------------------------------- |
| `visible` | `bool` | - | - | `false` | Display avatar editor |
| `image` | `string`,`file` | - | - | - | The URL of the image to use, or a File |
| `accept` | `array` | - | - | `['image/png', 'image/jpeg']` | Accepted file types |
| `displayType` | `oneOf` | - | `auto`, `modal`, `aside` | `auto` | Display type |
| `chooseFileLabel` | `string` | - | - | `Choose a file` | Translation string for file selection |
| `headerLabel` | `string` | - | - | `Edit Photo` | Translation string for title |
| `saveButtonLabel` | `string` | - | - | `Save` | Translation string for save button |
| `maxSizeFileError` | `string` | - | - | `Maximum file size exceeded` | Translation string for size warning |
| `unknownTypeError` | `string` | - | - | `Unknown image file type` | Translation string for file type warning |
| `unknownError` | `string` | - | - | `Error` | Translation string for warning |
| `maxSize` | `number` | - | - | `1` | Max size of image |
| `onSave` | `function` | - | - | - | Save event |
| `onClose` | `function` | - | - | - | Closing event |
| `onDeleteImage` | `function` | - | - | - | Image deletion event |
| `onLoadFile` | `function` | - | - | - | Image upload event |
| `onImageChange` | `function` | - | - | - | Image change event |

View File

@ -1,29 +1,33 @@
# Avatar
## Usage
Used to display an avatar or brand.
### Usage
```js
import { Avatar } from "asc-web-components";
```
#### Description
Required to display user avatar on page.
If you want to create an avatar with initials, only *first letter of first two words* of line is used.
#### Usage
```js
<Avatar size="max" role="admin" source="" userName="" editing={false} />
```jsx
<Avatar
size="max"
role="admin"
source=""
userName=""
editing={false}
/>
```
#### Properties
If you want to create an avatar with initials, only _first letter of first two words_ of line is used.
| Props | Type | Required | Values | Default | Description |
| ---------- | -------- | :------: | --------------------------------- | -------- | ------------------------------------------ |
| `size` | `oneOf` | - | `max`, `big`, `medium`, `small` | `medium` | Tells what size avatar should be displayed |
| `role` | `oneOf` | - | `owner`, `admin`, `guest`, `user` | - | Adds a user role table |
| `source` | `string` | - | - | - | Avatar image source |
| `userName` | `string` | - | - | - | Need to create an avatar with initials |
| `editing` | `bool` | - | - | `false` | Displays avatar edit layer |
### Properties
| Props | Type | Required | Values | Default | Description |
| ------------ | :------: | :------: | :-------------------------------: | :----------: | -------------------------------------------------------- |
| `size` | `oneOf` | - | `max`, `big`, `medium`, `small` | `medium` | Size of avatar |
| `role` | `oneOf` | - | `owner`, `admin`, `guest`, `user` | `user` | Adds a user role table |
| `source` | `string` | - | - | - | The address of the image for an image avatar |
| `userName` | `string` | - | - | - | Need to create an avatar with initials |
| `editing` | `bool` | - | - | `false` | Displays avatar edit layer |
| `editLabel` | `string` | - | - | `Edit photo` | Label for editing layer |
| `editAction` | `func` | - | - | - | Function called when the avatar change button is pressed |

View File

@ -1,20 +1,23 @@
# Backdrop
#### Description
Background for displaying modal dialogs
#### Usage
### Usage
```js
import { Backdrop } from 'asc-web-components';
<Backdrop visible={false} />
import { Backdrop } from "asc-web-components";
```
#### Properties
```jsx
<Backdrop
visible={true}
zIndex={1}
/>
```
| Props | Type | Required | Values | Default | Description |
| --------------- | ------------------------- | :------: | -------| ------- | ------------------------------------------------ |
| `visible` | `bool` | | | false | Display or not |
| `zIndex` | `number` | | | 100 | CSS z-index |
### Properties
| Props | Type | Required | Values | Default | Description |
| --------- | :------: | :------: | :----: | :-----: | -------------- |
| `visible` | `bool` | - | - | `false` | Display or not |
| `zIndex` | `number` | - | - | `100` | CSS z-index |

View File

@ -1,27 +1,37 @@
# Badge
## Usage
Used for buttons, numbers or status markers next to icons.
### Usage
```js
import { Badge } from 'asc-web-components';
<Badge />;
import { Badge } from "asc-web-components";
```
#### Description
```jsx
<Badge
number={10}
backgroundColor="#ED7309"
color="#FFFFFF"
fontSize="11px"
fontWeight={800}
borderRadius="11px"
padding="0 5px"
maxWidth="50px"
onClick={() => {}}
/>
```
Notify badge
### Properties
#### Properties
| Props | Type | Required | Values | Default | Description |
| ----------------- | -------- | :------: | ------ | --------- | --------------------- |
| `number` | `number` | | | 0 | Number value |
| `backgroundColor` | `string` | | | '#ED7309' | CSS background-color |
| `color` | `string` | | | '#FFFFFF' | CSS color |
| `fontSize` | `string` | | | '11px' | CSS font-size |
| `fontWeight` | `number` | | | '800' | CSS font-weight |
| `borderRadius` | `string` | | | '11px' | CSS border-radius |
| `padding` | `string` | | | '0 5px' | CSS padding |
| `maxWidth` | `string` | | | '50px' | CSS max-width |
| `onClick` | `func` | | | | onClick event |
| Props | Type | Required | Values | Default | Description |
| ----------------- | :------: | :------: | :----: | :-------: | -------------------- |
| `number` | `number` | - | - | `0` | Number value |
| `backgroundColor` | `string` | - | - | `#ED7309` | CSS background-color |
| `color` | `string` | - | - | `#FFFFFF` | CSS color |
| `fontSize` | `string` | - | - | `11px` | CSS font-size |
| `fontWeight` | `number` | - | - | `800` | CSS font-weight |
| `borderRadius` | `string` | - | - | `11px` | CSS border-radius |
| `padding` | `string` | - | - | `0 5px` | CSS padding |
| `maxWidth` | `string` | - | - | `50px` | CSS max-width |
| `onClick` | `func` | - | - | - | onClick event |

View File

@ -1,30 +1,34 @@
# Buttons: Button
## Usage
```js
import { Button } from 'asc-web-components';
```
#### Description
# Button
Button is used for a action on a page.
#### Usage
### Usage
```js
<Button size='base' isDisabled={false} onClick={() => alert('Button clicked')} label="OK" />
import { Button } from "asc-web-components";
```
#### Properties
```jsx
<Button
size="base"
isDisabled={false}
onClick={() => alert("Button clicked")}
label="OK"
/>
```
| Props | Type | Required | Values | Default | Description |
| ------------------ | -------- | :------: | --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `label` | `string` | - | - | - | Button text |
| `primary` | `bool` | - | - | - | Tells when the button should be primary |
| `size` | `oneOf` | - | `base`, `middle`, `big` | `base` | Size of button |
| `scale` | `bool` | - | - | false | Scale width of button to 100% |
| `isDisabled` | `bool` | - | - | - | Tells when the button should present a disabled state |
| `isLoading` | `bool` | - | - | - | Tells when the button should show loader icon |
| `onClick` | `func` | - | - | - | What the button will trigger when clicked |
### Properties
| Props | Type | Required | Values | Default | Description |
| ------------ | :------: | :------: | :---------------------: | :-----: | ----------------------------------------------------- |
| `label` | `string` | - | - | - | Button text |
| `primary` | `bool` | - | - | `false` | Tells when the button should be primary |
| `size` | `oneOf` | - | `base`, `middle`, `big` | `base` | Size of button |
| `scale` | `bool` | - | - | `false` | Scale width of button to 100% |
| `icon` | `node` | - | - | `null` | Icon node element |
| `tabIndex` | `number` | - | - | `-1` | Button tab index |
| `isHovered` | `bool` | - | - | `false` | Tells when the button should present a hovered state |
| `isClicked` | `bool` | - | - | `false` | Tells when the button should present a clicked state |
| `isDisabled` | `bool` | - | - | `false` | Tells when the button should present a disabled state |
| `isLoading` | `bool` | - | - | `false` | Tells when the button should show loader icon |
| `onClick` | `func` | - | - | - | What the button will trigger when clicked |

View File

@ -1,14 +1,14 @@
# Calendar
#### Description
Used to display custom calendar
Custom calendar
#### Usage
### Usage
```js
import { Calendar } from "asc-web-components";
```
```jsx
<Calendar
onChange={date => {
console.log("Selected date:", date);
@ -20,19 +20,19 @@ import { Calendar } from "asc-web-components";
minDate={new Date("1970/01/01")}
maxDate={new Date("3000/01/01")}
locale="ru"
/>;
/>
```
#### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | -------- | :------: | ------ | ------------------------ | ------------------------------------------------------------ |
| `onChange` | `func` | - | - | - | Function called when the user select a day |
| `isDisabled` | `bool` | - | - | - | Disabled react-calendar |
| `themeColor` | `string` | - | - | `#ED7309` | Color of the selected day |
| `selectedDate` | `date` | - | - | (today) | Selected date value |
| `openToDate` | `date` | - | - | (today) | The beginning of a period that shall be displayed by default |
| `minDate` | `date` | - | - | `new Date("1970/01/01")` | Minimum date that the user can select. |
| `maxDate` | `date` | - | - | `new Date("3000/01/01")` | Maximum date that the user can select. |
| `locale` | `string` | - | - | User's browser settings | Browser locale |
| Props | Type | Required | Values | Default | Description |
| -------------- | :------: | :------: | :-----------: | :-----------------------: | ------------------------------------------------------------ |
| `onChange` | `func` | - | - | - | Function called when the user select a day |
| `isDisabled` | `bool` | - | - | - | Disabled react-calendar |
| `themeColor` | `string` | - | - | `#ED7309` | Color of the selected day |
| `selectedDate` | `date` | - | - | `new Date()` | Selected date value |
| `openToDate` | `date` | - | - | `new Date()` | The beginning of a period that shall be displayed by default |
| `minDate` | `date` | - | - | `new Date("1970/01/01")` | Minimum date that the user can select. |
| `maxDate` | `date` | - | - | `new Date("3000/01/01")` | Maximum date that the user can select. |
| `locale` | `string` | - | - | `User's browser settings` | Browser locale |
| `size` | `oneOf` | - | `base`, `big` | `base` | Calendar size |

View File

@ -1,26 +1,35 @@
# Checkbox
#### Description
Custom checkbox input
#### Usage
### Usage
```js
import { Checkbox } from 'asc-web-components';
<Checkbox value="text" onChange={event => alert(event.target.value)}/>
import { Checkbox } from "asc-web-components";
```
#### Properties
```jsx
<Checkbox
id="id"
name="name"
value="value"
label="label"
isChecked={false}
isIndeterminate={false}
isDisabled={false}
onChange={() => {}}
/>
```
| Props | Type | Required | Values | Default | Description |
| ---------------------- | -------- | :------: | ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `id` | `string` | - | - | - | Used as HTML `id` property
| `name` | `string` | - | - | - | Used as HTML `name` property
| `value` | `string` | - | - | - | Value of the input
| `label` | `string` | - | - | - | Label of the input
| `isChecked` | `bool` | - | - | - | The checked property sets the checked state of a checkbox.
| `isIndeterminate` | `bool` | - | - | - | If true, this state is shown as a rectangle in the checkbox
| `isDisabled` | `bool` | - | - | - | Disables the Checkbox input
| `onChange` | `func` | ✅ | - | - | Will be triggered whenever an CheckboxInput is clicked
### Properties
| Props | Type | Required | Values | Default | Description |
| ----------------- | :------: | :------: | :----: | :-----: | ----------------------------------------------------------- |
| `id` | `string` | - | - | - | Used as HTML `id` property |
| `name` | `string` | - | - | - | Used as HTML `name` property |
| `value` | `string` | - | - | - | Value of the input |
| `label` | `string` | - | - | - | Label of the input |
| `isChecked` | `bool` | - | - | `false` | The checked property sets the checked state of a checkbox |
| `isIndeterminate` | `bool` | - | - | - | If true, this state is shown as a rectangle in the checkbox |
| `isDisabled` | `bool` | - | - | - | Disables the Checkbox input |
| `onChange` | `func` | ✅ | - | - | Will be triggered whenever an CheckboxInput is clicked |

View File

@ -1,9 +1,42 @@
# ComboBox
#### Description
Custom combo box input
### Usage
```js
import { ComboBox } from "asc-web-components";
```
```js
const options = [
{
key: 1,
icon: "CatalogEmployeeIcon", // optional item
label: "Option 1",
disabled: false, // optional item
onClick: clickFunction // optional item
}
];
```
```jsx
<ComboBox
options={options}
isDisabled={false}
selectedOption={{
key: 0,
label: "Select"
}}
dropDownMaxHeight={200}
noBorder={false}
scale={true}
scaledOptions={true}
size="content"
onSelect={option => console.log("selected", option)}
/>
```
Options have options:
- key - Item key, may be a string or a number
@ -39,7 +72,9 @@ const advancedOptions = (
</DropDownItem>
</>
);
```
```jsx
<ComboBox
options={[]} // An empty array will enable advancedOptions
advancedOptions={advancedOptions}
@ -54,55 +89,23 @@ const advancedOptions = (
directionX="right"
>
<Icons.NavLogoIcon size="medium" key="comboIcon" />
</ComboBox>;
</ComboBox>
```
#### Usage
### Properties
```js
import { ComboBox } from 'asc-web-components';
const options = [
{
key: 1,
icon: 'CatalogEmployeeIcon', // optional item
label: 'Option 1',
disabled: false, // optional item
onClick: clickFunction // optional item
},
...
];
<ComboBox
options={options}
isDisabled={false}
selectedOption={{
key: 0,
label: 'Select'
}}
dropDownMaxHeight={200}
noBorder={false}
scale={true}
scaledOptions={true}
size='content'
onSelect={option => console.log('selected', option)}
/>
```
#### Properties
| Props | Type | Required | Values | Default | Description |
| ------------------- | --------- | :------: | ------------------------------------------ | ------- | ----------------------------------------------------------- |
| `options` | `array` | ✅ | - | - | Combo box options |
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
| `selectedOption` | `object` | ✅ | - | - | Selected option |
| `onSelect` | `func` | - | - | - | Will be triggered whenever an ComboBox is selected option |
| `dropDownMaxHeight` | `number` | - | - | - | Height of Dropdown |
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
| `scaledOptions` | `bool` | - | - | `false` | Indicates that component`s options is scaled by ComboButton |
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
| `advancedOptions` | `element` | - | - | - | If you need display options not basic options |
| Props | Type | Required | Values | Default | Description |
| ------------------- | :-------: | :------: | :----------------------------------------: | :-----: | ----------------------------------------------------------- |
| `options` | `array` | ✅ | - | - | Combo box options |
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
| `selectedOption` | `object` | ✅ | - | - | Selected option |
| `onSelect` | `func` | - | - | - | Will be triggered whenever an ComboBox is selected option |
| `dropDownMaxHeight` | `number` | - | - | - | Height of Dropdown |
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
| `scaledOptions` | `bool` | - | - | `false` | Indicates that component`s options is scaled by ComboButton |
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
| `advancedOptions` | `element` | - | - | - | If you need display options not basic options |
## ComboButton
@ -113,7 +116,7 @@ const options = [
To create designs using combobox logic, there is a child component ComboButton.
This is an independent element that responds to changes in parameters and serves only to demonstrate set values.
```js
```jsx
<ComboButton
noBorder={false}
isDisabled={false}
@ -132,19 +135,19 @@ This is an independent element that responds to changes in parameters and serves
/>
```
#### Properties
### Properties
| Props | Type | Required | Values | Default | Description |
| ------------------------- | -------- | :------: | -------------------------------- | ---------------- | -------------------------------------------------------- |
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
| `selectedOption` | `object` | - | - | - | Selected option |
| `withOptions` | `bool` | - | - | `true` | Lets you style as ComboBox with options |
| `optionsLength` | `number` | - | - | - | Lets you style as ComboBox with options |
| `withAdvancedOptions` | `bool` | - | - | `false` | Lets you style as a ComboBox with advanced options |
| `innerContainer` | `node` | - | - | - | Allows displaying third-party element inside ComboButton |
| `innerContainerClassName` | `string` | - | - | `innerContainer` | Required to access third-party container |
| `isOpen` | `bool` | - | - | `false` | Lets you style as ComboBox arrow |
| `scaled` | `bool` | - | - | `false` | Indicates that component is scaled by parent |
| `size` | `oneOf` | - | `base`, `...`, `huge`, `content` | `content` | Select component width, one of default |
| `onClick` | `func` | - | - | - | Will be triggered whenever an ComboButton is clicked |
| Props | Type | Required | Values | Default | Description |
| ------------------------- | :------: | :------: | :------------------------------: | :--------------: | -------------------------------------------------------- |
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
| `selectedOption` | `object` | - | - | - | Selected option |
| `withOptions` | `bool` | - | - | `true` | Lets you style as ComboBox with options |
| `optionsLength` | `number` | - | - | - | Lets you style as ComboBox with options |
| `withAdvancedOptions` | `bool` | - | - | `false` | Lets you style as a ComboBox with advanced options |
| `innerContainer` | `node` | - | - | - | Allows displaying third-party element inside ComboButton |
| `innerContainerClassName` | `string` | - | - | `innerContainer` | Required to access third-party container |
| `isOpen` | `bool` | - | - | `false` | Lets you style as ComboBox arrow |
| `scaled` | `bool` | - | - | `false` | Indicates that component is scaled by parent |
| `size` | `oneOf` | - | `base`, `...`, `huge`, `content` | `content` | Select component width, one of default |
| `onClick` | `func` | - | - | - | Will be triggered whenever an ComboButton is clicked |

View File

@ -1,30 +1,29 @@
# ContextMenu
## Usage
ContextMenu is used for a call context actions on a page.
> Implemented as part of RowContainer component.
### Usage
```js
import { ContextMenu } from "asc-web-components";
```
#### Description
ContextMenu is used for a call context actions on a page.
Implemented as part of RowContainer component.
#### Usage
```jsx
<ContextMenu
targetAreaId="rowContainer"
options={[]}
/>
```
For use within separate component it is necessary to determine active zone and events for calling and transferring options in menu.
In particular case, state is created containing options for particular Row element and passed to component when called.
```js
<ContextMenu targetAreaId="rowContainer" options={[]} />
```
### Properties
#### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | -------- | :------: | ------ | ------- | ------------------------ |
| `options` | `array` | - | - | [] | DropDownItems collection |
| `targetAreaId` | `string` | - | - | - | Id of container apply to |
| Props | Type | Required | Values | Default | Description |
| -------------- | :------: | :------: | :----: | :-----: | ------------------------ |
| `options` | `array` | - | - | `[ ]` | DropDownItems collection |
| `targetAreaId` | `string` | - | - | - | Id of container apply to |

View File

@ -27,16 +27,18 @@ import { DatePicker } from "asc-web-components";
#### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | -------- | :------: | ------ | ------------------------ | ------------------------------------------ |
| `onChange` | `func` | - | - | - | Function called when the user select a day |
| `isDisabled` | `bool` | - | - | - | Disabled react-calendar |
| `themeColor` | `string` | - | - | `#ED7309` | Color of the selected day |
| `selectedDate` | `date` | - | - | (today) | Selected date value |
| `minDate` | `date` | - | - | `new Date("1970/01/01")` | Minimum date that the user can select. |
| `maxDate` | `date` | - | - | `new Date("3000/01/01")` | Maximum date that the user can select. |
| `locale` | `string` | - | - | User's browser settings | Browser locale |
| `scaled` | `bool` | - | | - | Selected calendar size |
| `isReadOnly` | `bool` | - | | - | Set input type is read only |
| `hasError` | `bool` | - | | - | Set error date-input style |
| `isOpen` | `bool` | - | | - | Opens calendar |
| Props | Type | Required | Values | Default | Description |
| ----------------------- | -------- | :------: | ----------------------- | ------------------------ | -------------------------------------------------- |
| `onChange` | `func` | - | - | - | Function called when the user select a day |
| `isDisabled` | `bool` | - | - | - | Disabled react-calendar |
| `themeColor` | `string` | - | - | `#ED7309` | Color of the selected day |
| `selectedDate` | `date` | - | - | (today) | Selected date value |
| `minDate` | `date` | - | - | `new Date("1970/01/01")` | Minimum date that the user can select. |
| `maxDate` | `date` | - | - | `new Date("3000/01/01")` | Maximum date that the user can select. |
| `locale` | `string` | - | - | User's browser settings | Browser locale |
| `scaled` | `bool` | - | | - | Selected calendar size |
| `isReadOnly` | `bool` | - | | - | Set input type is read only |
| `hasError` | `bool` | - | | - | Set error date-input style |
| `isOpen` | `bool` | - | | - | Opens calendar |
| `displayType` | `oneOf` | - | `dropdown, aside, auto` | `auto` | Calendar display type |
| `calendarHeaderContent` | `string` | - | - | - | Calendar header content (calendar opened in aside) |

View File

@ -10,68 +10,67 @@ import GroupButton from '../group-button';
const rowStyle = { marginTop: 8 };
storiesOf('Components| DropDown', module)
.addDecorator(withReadme(Readme))
.add('base', () => (
<Container>
<Row style={rowStyle}>
<Col xs="4">Only dropdown</Col>
<Col xs="2"/>
<Col>With Button</Col>
</Row>
<Row style={rowStyle}>
<Col xs="4">
Without active button
.addDecorator(withReadme(Readme))
.add('base', () => (
<Container>
<Row style={rowStyle}>
<Col xs="4">Only dropdown</Col>
<Col xs="2" />
<Col>With Button</Col>
</Row>
<Row style={rowStyle}>
<Col xs="4">
Without active button
<DropDown opened={true}>
<DropDownItem
isHeader
label='Category 1'
/>
<DropDownItem
label='Button 1'
onClick={() => console.log('Button 1 clicked')}
/>
<DropDownItem
label='Button 2'
onClick={() => console.log('Button 2 clicked')}
/>
<DropDownItem
label='Button 3'
onClick={() => console.log('Button 3 clicked')}
/>
<DropDownItem
label='Button 4'
onClick={() => console.log('Button 4 clicked')}
disabled={true}
/>
<DropDownItem isSeparator />
<DropDownItem
label='Button 5'
onClick={() => console.log('Button 5 clicked')}
/>
<DropDownItem
label='Button 6'
onClick={() => console.log('Button 6 clicked')}
/>
</DropDown>
</Col>
<Col xs="2"/>
<Col>
<GroupButton label='Dropdown demo' isDropdown={true}>
<DropDownItem
label='Button 1'
onClick={() => console.log('Button 1 clicked')}
/>
<DropDownItem
label='Button 2'
onClick={() => console.log('Button 2 clicked')}
/>
<DropDownItem
label='Button 3'
onClick={() => console.log('Button 3 clicked')}
/>
</GroupButton>
</Col>
</Row>
</Container>
)
);
<DropDownItem
isHeader
label='Category 1'
/>
<DropDownItem
label='Button 1'
onClick={() => console.log('Button 1 clicked')}
/>
<DropDownItem
label='Button 2'
onClick={() => console.log('Button 2 clicked')}
/>
<DropDownItem
label='Button 3'
onClick={() => console.log('Button 3 clicked')}
/>
<DropDownItem
label='Button 4'
onClick={() => console.log('Button 4 clicked')}
disabled={true}
/>
<DropDownItem isSeparator />
<DropDownItem
label='Button 5'
onClick={() => console.log('Button 5 clicked')}
/>
<DropDownItem
label='Button 6'
onClick={() => console.log('Button 6 clicked')}
/>
</DropDown>
</Col>
<Col xs="2" />
<Col>
<GroupButton label='Dropdown demo' isDropdown={true}>
<DropDownItem
label='Button 1'
onClick={() => console.log('Button 1 clicked')}
/>
<DropDownItem
label='Button 2'
onClick={() => console.log('Button 2 clicked')}
/>
<DropDownItem
label='Button 3'
onClick={() => console.log('Button 3 clicked')}
/>
</GroupButton>
</Col>
</Row>
</Container>
));

View File

@ -68,40 +68,61 @@ class DropDown extends React.PureComponent {
super(props);
this.state = {
width: 100
width: 100,
directionX: props.directionX,
directionY: props.directionY
};
this.dropDown = React.createRef();
}
setDropDownWidthState = () => {
if (this.dropDown.current) {
this.setState({
width: this.dropDown.current.offsetWidth
});
}
this.dropDownRef = React.createRef();
}
componentDidMount () {
this.setDropDownWidthState();
this.checkPosition();
}
componentDidUpdate(prevProps) {
if (this.props.opened !== prevProps.opened || this.props.isOpen !== prevProps.isOpen) {
this.setDropDownWidthState();
this.checkPosition();
}
}
checkPosition = () => {
if (this.dropDownRef.current){
const rects = this.dropDownRef.current.getBoundingClientRect();
const container = {width: window.innerWidth, height: window.innerHeight};
const left = rects.x < rects.width;
const right = rects.x + rects.width > container.width;
const top = rects.y + rects.height > container.height;
const bottom = rects.y < rects.height;
let newDirection = {};
newDirection.directionX = left ? 'left' : right ? 'right' : this.props.directionX;
newDirection.directionY = top ? 'top' : bottom ? 'bottom' : this.props.directionY;
this.setState({
directionX: newDirection.directionX,
directionY: newDirection.directionY,
width: rects.width
});
}
}
render() {
const {maxHeight, withArrow, directionX, children} = this.props;
const {maxHeight, withArrow, children} = this.props;
const {directionX, directionY} = this.state;
const fullHeight = children && children.length * 36;
const calculatedHeight = ((fullHeight > 0) && (fullHeight < maxHeight)) ? fullHeight : maxHeight;
const dropDownMaxHeightProp = maxHeight ? { height: calculatedHeight + 'px' } : {};
//console.log("DropDown render");
return (
<StyledDropdown
ref={this.dropDown}
ref={this.dropDownRef}
{...this.props}
directionX={directionX}
directionY={directionY}
{...dropDownMaxHeightProp}
>
{withArrow && <Arrow directionX={directionX} />}

View File

@ -20,10 +20,11 @@ Responsive form field container
#### Properties
| Props | Type | Required | Values | Default | Description |
| ---------------- | ------------------ | :------: | ------ | ------- | -------------------------------------------- |
| `isVertical` | `bool` | - | - | false | Vertical or horizontal alignment |
| `isRequired` | `bool` | - | - | false | Indicates that the field is required to fill |
| `hasError` | `bool` | - | - | false | Indicates that the field is incorrect |
| `labelText` | `string` | - | - | - | Field label text |
| `tooltipContent` | `object or string` | ✅ | - | - | Tooltip content |
| Props | Type | Required | Values | Default | Description |
| ------------------------- | ------------------ | :------: | ------ | ------- | ------------------------------------------------ |
| `isVertical` | `bool` | - | - | false | Vertical or horizontal alignment |
| `isRequired` | `bool` | - | - | false | Indicates that the field is required to fill |
| `hasError` | `bool` | - | - | false | Indicates that the field is incorrect |
| `labelText` | `string` | - | - | - | Field label text |
| `tooltipContent` | `object or string` | ✅ | - | - | Tooltip content |
| `HelpButtonHeaderContent` | `string` | - | - | - | Tooltip header content (tooltip opened in aside) |

View File

@ -71,7 +71,8 @@ class FieldContainer extends React.Component {
labelText,
children,
tooltipContent,
place
place,
HelpButtonHeaderContent
} = this.props;
return (
@ -88,6 +89,7 @@ class FieldContainer extends React.Component {
<HelpButton
tooltipContent={tooltipContent}
place={place}
HelpButtonHeaderContent={HelpButtonHeaderContent}
/>
)}
</div>
@ -111,7 +113,8 @@ FieldContainer.propTypes = {
PropTypes.node
]),
tooltipContent: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
place: PropTypes.string
place: PropTypes.string,
HelpButtonHeaderContent: PropTypes.string
};
FieldContainer.defaultProps ={

View File

@ -51,7 +51,9 @@ HelpButton is used for a action on a page.
#### Properties
| Props | Type | Required | Values | Default | Description |
| ---------------- | ------------------ | :------: | -------------------------- | ------- | ----------------- |
| `tooltipContent` | `object or string` | ✅ | - | - | Tooltip content |
| `place` | `string` | - | `top, right, bottom, left` | `top` | Tooltip placement |
| Props | Type | Required | Values | Default | Description |
| ------------------------- | ------------------ | :------: | -------------------------- | ------- | ------------------------------------------------ |
| `tooltipContent` | `object or string` | ✅ | - | - | Tooltip content |
| `place` | `string` | - | `top, right, bottom, left` | `top` | Tooltip placement |
| `displayType` | `oneOf` | - | `dropdown, aside, auto` | `auto` | Tooltip display type |
| `HelpButtonHeaderContent` | `string` | - | - | - | Tooltip header content (tooltip opened in aside) |

View File

@ -27,6 +27,7 @@ storiesOf("Components|Buttons", module)
<Section>
<IconButtons>
<HelpButton
displayType="dropdown"
tooltipContent={
<Text.Body fontSize={13}>
Paste you tooltip content here
@ -34,6 +35,8 @@ storiesOf("Components|Buttons", module)
}
/>
<HelpButton
displayType="aside"
HelpButtonHeaderContent="Aside position HelpButton"
tooltipContent={
<Text.Body>
You tooltip content with{" "}
@ -47,10 +50,12 @@ storiesOf("Components|Buttons", module)
}
/>
<HelpButton
displayType="auto"
HelpButtonHeaderContent="Auto position HelpButton"
tooltipContent={
<>
<p>You can put every thing here</p>
<ul style={{marginBottom: 0}}>
<ul style={{ marginBottom: 0 }}>
<li>Word</li>
<li>Chart</li>
<li>Else</li>

View File

@ -4,15 +4,47 @@ import IconButton from "../icon-button";
import Tooltip from "../tooltip";
import { handleAnyClick } from "../../utils/event";
import uniqueId from "lodash/uniqueId";
import Aside from "../layout/sub-components/aside";
import { desktop } from "../../utils/device";
import Backdrop from "../backdrop";
import { Text } from "../text";
import throttle from "lodash/throttle";
import styled from "styled-components";
const Content = styled.div`
position: relative;
width: 100%;
background-color: #fff;
padding: 0 16px 16px;
`;
const Header = styled.div`
display: flex;
align-items: center;
border-bottom: 1px solid #dee2e6;
`;
const Body = styled.div`
position: relative;
padding: 16px 0;
`;
const HeaderText = styled(Text.ContentHeader)`
max-width: 500px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
class HelpButton extends React.Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
this.state = { isOpen: false, displayType: this.getTypeByWidth() };
this.ref = React.createRef();
this.refTooltip = React.createRef();
this.id = uniqueId();
this.throttledResize = throttle(this.resize, 300);
}
afterShow = () => {
@ -41,12 +73,60 @@ class HelpButton extends React.Component {
}
};
onClose = () => {
this.setState({ isOpen: false });
};
componentDidMount() {
window.addEventListener("resize", this.throttledResize);
}
componentWillUnmount() {
handleAnyClick(false, this.handleClick);
window.removeEventListener("resize", this.throttledResize);
}
componentDidUpdate(prevProps) {
if (this.props.displayType !== prevProps.displayType) {
this.setState({ displayType: this.getTypeByWidth() });
}
if (this.state.isOpen && this.state.displayType === "aside") {
window.addEventListener("popstate", this.popstate, false);
}
}
popstate = () => {
window.removeEventListener("popstate", this.popstate, false);
this.onClose();
window.history.go(1);
};
resize = () => {
if (this.props.displayType !== "auto") return;
const type = this.getTypeByWidth();
if (type === this.state.displayType) return;
this.setState({ displayType: type });
};
getTypeByWidth = () => {
if (this.props.displayType !== "auto") return this.props.displayType;
return window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "dropdown";
};
onClick = () => {
this.setState({isOpen: !this.state.isOpen});
}
render() {
const { tooltipContent, place, offsetRight, offsetLeft } = this.props;
const { isOpen, displayType } = this.state;
const {
tooltipContent,
place,
offsetRight,
offsetLeft,
zIndex,
HelpButtonHeaderContent
} = this.props;
return (
<div ref={this.ref}>
@ -56,19 +136,38 @@ class HelpButton extends React.Component {
isClickable={true}
iconName="QuestionIcon"
size={13}
onClick={this.onClick}
/>
<Tooltip
id={this.id}
reference={this.refTooltip}
effect="solid"
place={place}
offsetRight={offsetRight}
offsetLeft={offsetLeft}
afterShow={this.afterShow}
afterHide={this.afterHide}
>
{tooltipContent}
</Tooltip>
{displayType === "dropdown" ? (
<Tooltip
id={this.id}
reference={this.refTooltip}
effect="solid"
place={place}
offsetRight={offsetRight}
offsetLeft={offsetLeft}
afterShow={this.afterShow}
afterHide={this.afterHide}
>
{tooltipContent}
</Tooltip>
) : (
<>
<Backdrop onClick={this.onClose} visible={isOpen} zIndex={zIndex} />
<Aside visible={isOpen} scale={false} zIndex={zIndex}>
<Content>
<Header>
<HeaderText>
<Text.Body isBold={true} fontSize={21}>
{HelpButtonHeaderContent}
</Text.Body>
</HeaderText>
</Header>
<Body>{tooltipContent}</Body>
</Content>
</Aside>
</>
)}
</div>
);
}
@ -84,13 +183,18 @@ HelpButton.propTypes = {
tooltipMaxWidth: PropTypes.number,
tooltipId: PropTypes.string,
place: PropTypes.string,
offsetLeft: PropTypes.number
offsetLeft: PropTypes.number,
zIndex: PropTypes.number,
displayType: PropTypes.oneOf(["dropdown", "aside", "auto"]),
HelpButtonHeaderContent: PropTypes.string
};
HelpButton.defaultProps = {
place: "top",
offsetRight: 120,
offsetLeft: 0
}
offsetLeft: 0,
zIndex: 310,
displayType: "auto"
};
export default HelpButton;

View File

@ -14,7 +14,7 @@ const getSizeStyle = size => {
return `
&:not(:root) {
width: 100%;
height: auto;
height: 100%;
}
`;
case 'small':

View File

@ -61,8 +61,6 @@ import OrigCopyIcon from './copy.react.svg';
import OrigShareEmailIcon from './share.e-mail.react.svg';
import OrigShareGooglePlusIcon from './share.google.plus.react.svg';
import OrigSendClockIcon from './send.clock.react.svg';
import OrigShareFacebookIcon from './share.facebook.react.svg';
import OrigShareTwitterIcon from './share.twitter.react.svg';
import OrigAccessNoneIcon from './access.none.react.svg';
import OrigTimeTrackingNotBilledIcon from './time.tracking.not.billed.react.svg';
import OrigAccessFormIcon from './access.form.react.svg';
@ -141,6 +139,10 @@ import OrigToggleButtonIcon from './toggle.button.react.svg';
import OrigQuestionIcon from './question.react.svg';
import OrigShareGoogleIcon from './share.google.react.svg';
import OrigShareFacebookIcon from './share.facebook.react.svg';
import OrigShareTwitterIcon from './share.twitter.react.svg';
import OrigShareLinkedInIcon from './share.linkedin.react.svg';
export const AZSortingIcon = createStyledIcon(
OrigAZSortingIcon,
@ -619,18 +621,10 @@ export const ShareEmailIcon = createStyledIcon(
OrigShareEmailIcon,
'ShareEmailIcon'
);
export const ShareFacebookIcon = createStyledIcon(
OrigShareFacebookIcon,
'ShareFacebookIcon'
);
export const ShareGooglePlusIcon = createStyledIcon(
OrigShareGooglePlusIcon,
'ShareGooglePlusIcon'
);
export const ShareTwitterIcon = createStyledIcon(
OrigShareTwitterIcon,
'ShareTwitterIcon'
);
export const SkypeIcon = createStyledIcon(
OrigSkypeIcon,
'SkypeIcon'
@ -692,4 +686,23 @@ export const QuestionIcon = createStyledIcon(
OrigQuestionIcon,
'ToggleButtonIcon',
"rect"
);
export const ShareGoogleIcon = createStyledIcon(
OrigShareGoogleIcon,
'ShareGoogleIcon'
);
export const ShareFacebookIcon = createStyledIcon(
OrigShareFacebookIcon,
'ShareFacebookIcon'
);
export const ShareTwitterIcon = createStyledIcon(
OrigShareTwitterIcon,
'ShareTwitterIcon'
);
export const ShareLinkedInIcon = createStyledIcon(
OrigShareLinkedInIcon,
'ShareLinkedInIcon'
);

View File

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 0H2C0.897 0 0 0.897 0 2V14C0 15.103 0.897 16 2 16H8V10.1848H5.81641V8H8V5.90666C8 4.24966 9.343 2.90666 11 2.90666H13.0877V5.09417H11.912C11.36 5.09417 10.912 5.26478 10.912 5.81678V8H13.5L12.5 10.1848H10.912V16H14C15.103 16 16 15.103 16 14V2C16 0.897 15.103 0 14 0Z" fill="#A3A9AE"/>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.75 0H2.25C1.00912 0 0 1.00912 0 2.25V15.75C0 16.9909 1.00912 18 2.25 18H9V11.4579H6.54346V9H9V6.64499C9 4.78087 10.5109 3.26999 12.375 3.26999H14.7237V5.73094H13.401C12.78 5.73094 12.276 5.92288 12.276 6.54388V9H15.1875L14.0625 11.4579H12.276V18H15.75C16.9909 18 18 16.9909 18 15.75V2.25C18 1.00912 16.9909 0 15.75 0Z" fill="#4469B0"/>
</svg>

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 494 B

View File

@ -0,0 +1,6 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.64 9.20419C17.64 8.56601 17.5827 7.95237 17.4764 7.36328H9V10.8446H13.8436C13.635 11.9696 13.0009 12.9228 12.0477 13.561V15.8192H14.9564C16.6582 14.2524 17.64 11.9451 17.64 9.20419Z" fill="#4285F4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 18C11.43 18 13.4673 17.1941 14.9564 15.8195L12.0477 13.5613C11.2418 14.1013 10.2109 14.4204 9 14.4204C6.65591 14.4204 4.67182 12.8372 3.96409 10.71H0.957275V13.0418C2.43818 15.9831 5.48182 18 9 18Z" fill="#34A853"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.96409 10.7098C3.78409 10.1698 3.68182 9.59301 3.68182 8.99983C3.68182 8.40664 3.78409 7.82983 3.96409 7.28983V4.95801H0.957273C0.347727 6.17301 0 7.54755 0 8.99983C0 10.4521 0.347727 11.8266 0.957273 13.0416L3.96409 10.7098Z" fill="#FBBC05"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 3.57955C10.3214 3.57955 11.5077 4.03364 12.4405 4.92545L15.0218 2.34409C13.4632 0.891818 11.4259 0 9 0C5.48182 0 2.43818 2.01682 0.957275 4.95818L3.96409 7.29C4.67182 5.16273 6.65591 3.57955 9 3.57955Z" fill="#EA4335"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.75036 8.05344C9.74586 8.06301 9.73967 8.07201 9.73292 8.08044H9.75036V8.05344Z" fill="#1276B3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.25 0C1.00736 0 0 1.00736 0 2.25V15.75C0 16.9926 1.00736 18 2.25 18H15.75C16.9926 18 18 16.9926 18 15.75V2.25C18 1.00736 16.9926 0 15.75 0H2.25ZM2.89855 6.91044H5.5828V14.9846H2.89855V6.91044ZM4.25755 3.01794C5.17611 3.01794 5.74142 3.62094 5.7583 4.41407C5.7583 5.18751 5.17611 5.80851 4.24011 5.80851H4.22267C3.32211 5.80851 2.73992 5.18751 2.73992 4.41407C2.73992 3.62094 3.34011 3.01794 4.25755 3.01794ZM12.1697 6.72144C13.9354 6.72144 15.26 7.87513 15.26 10.3546L15.2595 14.9846H12.5758V10.6646C12.5758 9.57782 12.1882 8.83869 11.2157 8.83869C10.4754 8.83869 10.0333 9.34044 9.83867 9.82082C9.7678 9.99463 9.75036 10.2314 9.75036 10.4756V14.9846H7.06723C7.06723 14.9846 7.10155 7.66757 7.06723 6.91044H9.75036V8.05344C10.1087 7.50388 10.746 6.72144 12.1697 6.72144Z" fill="#1276B3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4 0H1.6C0.72 0 0 0.72 0 1.6V14.4C0 15.28 0.72 16 1.6 16H14.4C15.28 16 16 15.28 16 14.4V1.6C16 0.72 15.28 0 14.4 0ZM12.56 5.84C12.48 9.52 10.16 12.08 6.64 12.24C5.2 12.32 4.16 11.84 3.2 11.28C4.24 11.44 5.6 11.04 6.32 10.4C5.28 10.32 4.64 9.76 4.32 8.88C4.64 8.96 4.96 8.88 5.2 8.88C4.24 8.56 3.6 8 3.52 6.72C3.76 6.88 4.08 6.96 4.4 6.96C3.68 6.56 3.2 5.04 3.76 4.08C4.8 5.2 6.08 6.16 8.16 6.32C7.6 4.08 10.64 2.88 11.84 4.4C12.4 4.32 12.8 4.08 13.2 3.92C13.04 4.48 12.72 4.8 12.32 5.12C12.72 5.04 13.12 4.96 13.44 4.8C13.36 5.2 12.96 5.52 12.56 5.84Z" fill="#A3A9AE"/>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2 0H1.8C0.81 0 0 0.81 0 1.8V16.2C0 17.19 0.81 18 1.8 18H16.2C17.19 18 18 17.19 18 16.2V1.8C18 0.81 17.19 0 16.2 0ZM14.13 6.57002C14.04 10.71 11.43 13.59 7.47 13.77C5.85 13.86 4.68 13.32 3.6 12.69C4.77 12.8701 6.3 12.4201 7.11 11.7C5.94 11.61 5.22 10.98 4.86 9.99002C5.22 10.08 5.58 9.99002 5.85 9.99002C4.77 9.63002 4.05 9.00002 3.96 7.56002C4.23 7.74002 4.59 7.83002 4.95 7.83002C4.14 7.38002 3.6 5.67002 4.23 4.59002C5.4 5.85002 6.84 6.93002 9.18 7.11002C8.55 4.59002 11.97 3.24002 13.32 4.95002C13.95 4.86002 14.4 4.59002 14.85 4.41002C14.67 5.04002 14.31 5.40002 13.86 5.76002C14.31 5.67002 14.76 5.58002 15.12 5.40002C15.03 5.85002 14.58 6.21002 14.13 6.57002Z" fill="#2AA3EF"/>
</svg>

Before

Width:  |  Height:  |  Size: 725 B

After

Width:  |  Height:  |  Size: 842 B

View File

@ -0,0 +1,29 @@
# Buttons: SocialButton
## Usage
```js
import { SocialButton } from 'asc-web-components';
```
#### Description
Button is used for sign up with help social networks.
#### Usage
```js
<SocialButton iconName={"SocialButtonGoogleIcon"} label={"Sign up with Google"}/>
```
#### Properties
| Props | Type | Required | Values | Default | Description |
| ------------------ | -------- | :------: | --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `label` | `string` | - | - | - | Button text |
| `iconName` | `string` | - | - | SocialButtonGoogleIcon | Icon of button |
| `isDisabled` | `bool` | - | - | false | Tells when the button should present a disabled state |
| `onClick` | `func` | - | - | - | What the button will trigger when clicked |

View File

@ -0,0 +1,137 @@
import React from 'react';
import styled, { css } from 'styled-components';
import PropTypes from 'prop-types';
import { Text } from '../text';
import isEqual from "lodash/isEqual";
import { Icons } from "../icons";
// eslint-disable-next-line no-unused-vars
const ButtonWrapper = ({label, iconName, isDisabled, ...props}) => <button type="button" {...props}></button>;
ButtonWrapper.propTypes = {
label: PropTypes.string,
iconName: PropTypes.string,
tabIndex: PropTypes.number,
isDisabled: PropTypes.bool,
onClick: PropTypes.func,
};
const StyledSocialButton = styled(ButtonWrapper).attrs((props) => ({
disabled: props.isDisabled ? 'disabled' : '',
tabIndex: props.tabIndex
}))`
font-family: 'Open Sans', sans-serif;
border: none;
display: inline-block;
font-weight: 600;
text-decoration: none;
margin: 20px 0 0 20px;
padding: 0;
border-radius: 2px;
width: 201px;
height: 40px;
text-align: left;
touch-callout: none;
-o-touch-callout: none;
-moz-touch-callout: none;
-webkit-touch-callout: none;
stroke: none;
&:focus {
outline: none
}
${props =>
!props.isDisabled ?
css`
background: #FFFFFF;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.24), 0px 0px 1px rgba(0, 0, 0, 0.12);
color: rgba(0, 0, 0, 0.54);
:hover, :active {
cursor: pointer;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.24), 0px 0px 2px rgba(0, 0, 0, 0.12);
}
:hover {
background: #FFFFFF;
}
:active {
background: #EEEEEE;
border: none;
}
`
:
css`
box-shadow: none;
background: rgba(0, 0, 0, 0.08);
color: rgba(0, 0, 0, 0.4);
svg path {
fill: rgba(0, 0, 0, 0.4);
}
`
};
.social_button_text {
position: absolute;
width: 142px;
height: 16px;
margin: 12px 9px 12px 10px;
font-family: Roboto, 'Open Sans', sans-serif, Arial;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 16px;
letter-spacing: 0.21875px;
user-select: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
svg {
margin: 11px;
width: 18px;
height: 18px;
min-width: 18px;
min-height: 18px;
}
`;
class SocialButton extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
render() {
const {label, iconName} = this.props;
return (
<StyledSocialButton {...this.props}>
{React.createElement(Icons[iconName], {})}
{label && (
<Text.Body as="span" className="social_button_text">{label}</Text.Body>
)}
</StyledSocialButton>
);
}
}
SocialButton.propTypes = {
label: PropTypes.string,
iconName: PropTypes.string,
tabIndex: PropTypes.number,
isDisabled: PropTypes.bool
};
SocialButton.defaultProps = {
label: '',
iconName: 'SocialButtonGoogleIcon',
tabIndex: -1,
isDisabled: false
};
export default SocialButton;

View File

@ -0,0 +1,27 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, text, select } from '@storybook/addon-knobs/react';
import withReadme from 'storybook-readme/with-readme';
import Readme from './README.md';
import SocialButton from '.';
storiesOf('Components|Buttons|SocialButtons', module)
.addDecorator(withKnobs)
.addDecorator(withReadme(Readme))
.add('social button', () => {
const socialNetworks = ['ShareGoogleIcon',
'ShareFacebookIcon',
'ShareTwitterIcon',
'ShareLinkedInIcon'];
const iconName = select("iconName", ['', ...socialNetworks], 'ShareGoogleIcon');
return (
<SocialButton
label={text('label', 'Base SocialButton')}
iconName={iconName}
isDisabled={boolean('isDisabled', false)}
onClick={action('clicked')}
/>
)
});

View File

@ -0,0 +1,43 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import SocialButton from '.';
describe('<SocialButton />', () => {
it('renders without error', () => {
const wrapper = mount(
<SocialButton iconName={'ShareGoogleIcon'} label={"Test Caption"}/>
);
expect(wrapper).toExist();
});
it('not re-render test', () => {
// const onClick= () => alert('SocialButton clicked');
const wrapper = shallow(<SocialButton iconName={'ShareGoogleIcon'} label={"Test Caption"}/>).instance();
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
expect(shouldUpdate).toBe(false);
});
it('disabled click test', () => {
const testClick = jest.fn();
const wrapper = mount(<SocialButton iconName={'ShareGoogleIcon'} label={"Test Caption"} onClick={testClick} isDisabled={true}/>);
wrapper.simulate('click');
expect(testClick).toHaveBeenCalledTimes(0);
});
it('click test', () => {
const testClick = jest.fn();
const wrapper = mount(<SocialButton iconName={'ShareGoogleIcon'} label={"Test Caption"} onClick={testClick} isDisabled={false}/>);
wrapper.simulate('click');
expect(testClick).toHaveBeenCalledTimes(1);
});
});

View File

@ -21,6 +21,13 @@ const Label = styled.div`
text-align: center;
margin: 7px 15px 7px 15px;
overflow: hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
${props => (props.isDisabled ? `pointer-events: none;` : ``)}