Merge branch 'master' into feature/social-button

This commit is contained in:
Ivan Habarov 2019-11-20 09:33:12 +03:00
commit 3a1597c72d
40 changed files with 1653 additions and 655 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

@ -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

@ -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,12 +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";
import { isArrayEqual } from "../../../utils/isArrayEqual";
const AdminsContainer = styled.div`
.hidden-icon {
position: fixed;
visibility: hidden;
}
`;
const ToggleContentContainer = styled.div`
.buttons_container {
display: flex;
@ -54,7 +63,6 @@ const ToggleContentContainer = styled.div`
}
.filter_container {
margin-bottom: 50px;
margin-top: 16px;
}
`;
@ -66,41 +74,32 @@ class PureAdminsSettings extends Component {
this.state = {
showSelector: false,
showFullAdminSelector: false,
allOptions: [],
options: [],
isLoading: false,
showLoader: true,
selectedOptions: []
};
}
componentDidMount() {
const { fetchPeople } = this.props;
this.onLoading(true);
const { admins, options, fetchPeople } = this.props;
const newFilter = this.onAdminsFilter();
fetchPeople(newFilter)
.catch(error => {
toastr.error(error);
})
.finally(() => this.onLoading(false));
}
shouldComponentUpdate(nextProps, nextState) {
const { admins, options } = this.props;
const { isLoading, showSelector, showFullAdminSelector } = this.state;
//options, selectedOptions ???
if (
isArrayEqual(admins, nextProps.admins) &&
isArrayEqual(options, nextProps.options) &&
isLoading === nextState.isLoading &&
showSelector === nextState.showSelector &&
showFullAdminSelector === nextState.showFullAdminSelector
) {
return false;
if (isEmpty(admins, true) || isEmpty(options, true)) {
const newFilter = this.onAdminsFilter();
fetchPeople(newFilter)
.catch(error => {
toastr.error(error);
})
.finally(() =>
this.setState({
showLoader: false,
allOptions: this.props.options
})
);
} else {
this.setState({ showLoader: false });
}
return true;
}
onChangeAdmin = (userIds, isAdmin, productId) => {
@ -117,12 +116,13 @@ class PureAdminsSettings extends Component {
});
};
onShowGroupSelector = () =>
onShowGroupSelector = () => {
this.setState({
showSelector: !this.state.showSelector,
options: this.props.options,
selectedOptions: []
});
};
onShowFullAdminGroupSelector = () => {
this.setState({
@ -154,37 +154,40 @@ class PureAdminsSettings extends Component {
};
onSearchUsers = template => {
//dispatch
this.setState({
options: this.filterUserSelectorOptions(this.props.options, template)
});
const options = this.filterUserSelectorOptions(
this.state.allOptions,
template
);
this.setState({ options: options });
};
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();
@ -193,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();
@ -208,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));
};
@ -228,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;
@ -243,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);
@ -312,182 +328,201 @@ class PureAdminsSettings extends Component {
options,
selectedOptions,
isLoading,
showFullAdminSelector
showFullAdminSelector,
showLoader
} = this.state;
const countElements = filter.total;
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"
/>
{!isLoading
? (console.log("123123123"),
(
<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}
/>
</div>
return admins.length > 0 ? (
/*TODO: delete after resolve icon button problem*/
<AdminsContainer>
<IconButton className="hidden-icon" iconName="SearchIcon" />
<Button
size="medium"
primary={true}
label="Set portal admin"
isDisabled={isLoading}
onClick={this.onShowFullAdminGroupSelector}
{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"
/>
<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}
/>
<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}
<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}
/>
) : null}
</div>
</div>
<FilterInput
className="filter_container"
getFilterData={() => []}
getSortData={this.getSortData}
onFilter={this.onFilter}
/>
<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">
<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>
<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>
{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>
) : null}
</ToggleContentContainer>
))
: null}
</>
);
) : null}
</ToggleContentContainer>
</>
)}
</AdminsContainer>
) : !showLoader ? (
<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>
</>
}
/>
) : null;
}
}
@ -536,6 +571,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 isEmpty from "lodash/isEmpty";
import { isArrayEqual } from "../../../utils/isArrayEqual";
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,42 +81,85 @@ class PureOwnerSettings extends Component {
super(props);
this.state = {
isLoading: false
isLoading: false,
showSelector: false,
options: [],
allOptions: [],
showLoader: true,
selectedOwner: null
};
}
componentDidMount() {
const { getPortalOwner, ownerId } = this.props;
this.onLoading(true);
const {
owner,
getPortalOwner,
ownerId,
options,
getUsersOptions
} = this.props;
getPortalOwner(ownerId)
.catch(error => {
toastr.error(error);
})
.finally(() => this.onLoading(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(owner, true)) {
getPortalOwner(ownerId)
.catch(error => {
toastr.error(error);
})
.finally(() => this.setState({ showLoader: false }));
}
return true;
if (isEmpty(options, true)) {
getUsersOptions()
.catch(error => {
toastr.error(error);
})
.finally(() =>
this.setState({
showLoader: false,
allOptions: this.props.options
})
);
}
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("|");
@ -103,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>
)}
</>
);
}
@ -176,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
};
}
@ -190,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

@ -52,7 +52,9 @@
"DeleteProfileConfirmation",
"DeleteProfileConfirmationInfo",
"ChangePasswordSuccess",
"Remember"
"Remember",
"ConfirmOwnerPortalTitle",
"ConfirmOwnerPortalSuccessMessage"
]
},
"Settings": {
@ -94,7 +96,10 @@
"RestoreDefaultButton",
"SuccessfullySaveGreetingSettingsMessage",
"ChangeLogoButton",
"Employees"
"Employees",
"AccessRightsChangeOwnerText",
"DnsChangeMsg",
"AccessRightsChangeOwnerConfirmText"
],
"FeedResource": [
"ProjectsProduct",
@ -122,6 +127,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.175",
"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

@ -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

@ -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;` : ``)}