This commit is contained in:
Nikita Gopienko 2019-09-02 14:30:07 +03:00
commit aa7bb166bd
19 changed files with 332 additions and 175 deletions

View File

@ -20,6 +20,7 @@
"oidc-client": "^1.9.0",
"prop-types": "^15.7.2",
"react": "^16.9.0",
"react-device-detect": "^1.7.5",
"react-dom": "^16.9.0",
"react-i18next": "10.12.2",
"react-redux": "7.1.1",

View File

@ -31,6 +31,8 @@ import {
} from "../../../../../store/people/selectors";
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
import { EmployeeStatus } from "../../../../../helpers/constants";
import { resendUserInvites } from "../../../../../store/services/api";
import { isMobileOnly } from "react-device-detect";
class SectionBodyContent extends React.PureComponent {
constructor(props) {
@ -51,8 +53,8 @@ class SectionBodyContent extends React.PureComponent {
window.open("mailto:" + email);
};
onSendMessageClick = () => {
toastr.success("Context action: Send message");
onSendMessageClick = mobilePhone => {
window.open(`sms:${mobilePhone}`);
};
onEditClick = user => {
@ -263,8 +265,13 @@ class SectionBodyContent extends React.PureComponent {
});
};
onInviteAgainClick = () => {
toastr.success("Context action: Invite again");
onInviteAgainClick = user => {
const { onLoading } = this.props;
onLoading(true);
resendUserInvites([user.id])
.then(() => toastr.success(<Text.Body>The email activation instructions have been sent to the <b>{user.email}</b> email address</Text.Body>))
.catch(e => toastr.error("ERROR"))
.finally(() => onLoading(false));
};
getUserContextOptions = (user, viewer) => {
let status = "";
@ -288,10 +295,11 @@ class SectionBodyContent extends React.PureComponent {
label: t("LblSendEmail"),
onClick: this.onEmailSentClick.bind(this, user.email)
},
user.mobilePhone && isMobileOnly &&
{
key: "send-message",
label: t("LblSendMessage"),
onClick: this.onSendMessageClick
onClick: this.onSendMessageClick.bind(this, user.mobilePhone)
},
{ key: "separator", isSeparator: true },
{
@ -354,7 +362,7 @@ class SectionBodyContent extends React.PureComponent {
{
key: "invite-again",
label: t("LblInviteAgain"),
onClick: this.onInviteAgainClick
onClick: this.onInviteAgainClick.bind(this, user)
},
!isSelf &&
(user.status === EmployeeStatus.Active

View File

@ -7,6 +7,7 @@ import { withTranslation } from 'react-i18next';
import { updateUserStatus, updateUserType } from '../../../../../store/people/actions';
import { EmployeeStatus, EmployeeType } from '../../../../../helpers/constants';
import { typeUser , typeGuest } from '../../../../../helpers/../helpers/customNames';
import { resendUserInvites } from '../../../../../store/services/api';
const contextOptions = ( t ) => {
return [
@ -65,6 +66,14 @@ const SectionHeaderContent = React.memo(({
toastr.success(t('SuccessChangeUserType'));
}, [selectedUserIds, updateUserType, t]);
const onSentInviteAgain = useCallback(() => {
resendUserInvites(selectedUserIds)
.then(() =>
toastr.success("The invitation was successfully sent")
)
.catch(e => toastr.error("ERROR"));
}, [selectedUserIds]);
const menuItems = [
{
label: t('LblSelect'),
@ -102,7 +111,7 @@ const SectionHeaderContent = React.memo(({
{
label: t('LblInviteAgain'),
disabled: !selection.length,
onClick: toastr.success.bind(this, "Invite again action")
onClick: onSentInviteAgain
},
{
label: t('LblSendEmail'),

View File

@ -4,14 +4,11 @@ import PropTypes from "prop-types";
import { PageLayout, Loader } from "asc-web-components";
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
import { SectionHeaderContent, SectionBodyContent } from './Section';
import { setProfile, fetchProfile, resetProfile } from '../../../store/profile/actions';
import { fetchProfile } from '../../../store/profile/actions';
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
class Profile extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { match, fetchProfile } = this.props;
@ -66,6 +63,7 @@ Profile.propTypes = {
history: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
profile: PropTypes.object,
fetchProfile: PropTypes.func.isRequired,
isLoaded: PropTypes.bool
};
@ -76,7 +74,5 @@ function mapStateToProps(state) {
}
export default connect(mapStateToProps, {
setProfile,
fetchProfile,
resetProfile
fetchProfile
})(Profile);

View File

@ -17,7 +17,7 @@ const DepartmentField = React.memo((props) => {
hasError={hasError}
labelText={labelText}
>
{departments.map((department) => (
{departments && departments.map((department) => (
<SelectedItem
key={`department_${department.id}`}
text={department.name}

View File

@ -3,7 +3,7 @@ import { withRouter } from 'react-router'
import { connect } from 'react-redux'
import { Avatar, Button, Textarea, Text, toastr } from 'asc-web-components'
import { withTranslation } from 'react-i18next';
import { toEmployeeWrapper, getUserRole, profileEqual, createProfile } from '../../../../../store/profile/actions';
import { toEmployeeWrapper, getUserRole, createProfile } from '../../../../../store/profile/actions';
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
import TextField from './FormFields/TextField'
import PasswordField from './FormFields/PasswordField'
@ -30,12 +30,14 @@ class CreateUserForm extends React.Component {
}
componentDidUpdate(prevProps, prevState) {
if (!profileEqual(this.props.profile, prevProps.profile)) {
if (this.props.match.params.type !== prevProps.match.params.type) {
this.setState(this.mapPropsToState(this.props));
}
}
mapPropsToState = (props) => {
const isVisitor = props.match.params.type === "guest";
return {
isLoading: false,
showPassword: false,
@ -45,10 +47,7 @@ class CreateUserForm extends React.Component {
email: false,
password: false,
},
profile: {
...{ passwordType: "link" },
...toEmployeeWrapper(props.profile)
}
profile: toEmployeeWrapper({ isVisitor: isVisitor})
};
}
@ -99,9 +98,9 @@ class CreateUserForm extends React.Component {
this.setState({isLoading: true});
this.props.createProfile(this.state.profile)
.then(() => {
.then((profile) => {
toastr.success("Success");
this.props.history.goBack();
this.props.history.push(`${this.props.settings.homepage}/view/${profile.userName}`);
})
.catch((error) => {
toastr.error(error.message)
@ -110,7 +109,7 @@ class CreateUserForm extends React.Component {
}
onCancel() {
this.props.history.goBack();
this.props.history.push(this.props.settings.homepage)
}
render() {
@ -236,7 +235,7 @@ class CreateUserForm extends React.Component {
const mapStateToProps = (state) => {
return {
profile: state.profile.targetUser
settings: state.auth.settings
}
};

View File

@ -3,7 +3,7 @@ import { withRouter } from 'react-router'
import { connect } from 'react-redux'
import { Avatar, Button, Textarea, Text, toastr, ModalDialog } from 'asc-web-components'
import { withTranslation } from 'react-i18next';
import { toEmployeeWrapper, getUserRole, profileEqual, updateProfile } from '../../../../../store/profile/actions';
import { toEmployeeWrapper, getUserRole, updateProfile } from '../../../../../store/profile/actions';
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
import TextField from './FormFields/TextField'
import TextChangeField from './FormFields/TextChangeField'
@ -32,7 +32,7 @@ class UpdateUserForm extends React.Component {
}
componentDidUpdate(prevProps, prevState) {
if (!profileEqual(this.props.profile, prevProps.profile)) {
if (this.props.match.params.userId !== prevProps.match.params.userId) {
this.setState(this.mapPropsToState(this.props));
}
}
@ -47,10 +47,7 @@ class UpdateUserForm extends React.Component {
email: false,
password: false,
},
profile: {
...{ passwordType: "link" },
...toEmployeeWrapper(props.profile)
}
profile: toEmployeeWrapper(props.profile)
};
}
@ -97,9 +94,9 @@ class UpdateUserForm extends React.Component {
this.setState({isLoading: true});
this.props.updateProfile(this.state.profile)
.then(() => {
.then((profile) => {
toastr.success("Success");
this.props.history.goBack();
this.props.history.push(`${this.props.settings.homepage}/view/${profile.userName}`);
})
.catch((error) => {
toastr.error(error.message)
@ -256,7 +253,8 @@ class UpdateUserForm extends React.Component {
const mapStateToProps = (state) => {
return {
profile: state.profile.targetUser
profile: state.profile.targetUser,
settings: state.auth.settings
}
};

View File

@ -15,14 +15,17 @@ const Header = styled(Text.ContentHeader)`
`;
const SectionHeaderContent = (props) => {
const {profile, history, settings} = props;
const { profile, history, settings, match } = props;
const { type } = match.params;
const { t } = useTranslation();
const headerText = profile && profile.displayName
? profile.displayName
: profile.isVisitor
const headerText = type
? type === "guest"
? t('NewGuest')
: t('NewEmployee');
: t('NewEmployee')
: profile
? profile.displayName
: "";
const onClick = useCallback(() => {
history.push(settings.homepage)

View File

@ -4,31 +4,27 @@ import PropTypes from "prop-types";
import { PageLayout, Loader } from "asc-web-components";
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
import { SectionHeaderContent, CreateUserForm, UpdateUserForm } from './Section';
import { setProfile, fetchProfile, resetProfile } from '../../../store/profile/actions';
import { fetchProfile } from '../../../store/profile/actions';
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
class ProfileAction extends React.Component {
componentDidMount() {
const { match, setProfile, fetchProfile } = this.props;
const { userId, type } = match.params;
if (!userId) {
setProfile({ isVisitor: type === "guest" });
} else {
componentDidMount() {
const { match, fetchProfile } = this.props;
const { userId } = match.params;
if (userId) {
fetchProfile(userId);
}
}
componentDidUpdate(prevProps) {
const { match, setProfile, fetchProfile } = this.props;
const { userId, type } = match.params;
const { match, fetchProfile } = this.props;
const { userId } = match.params;
const prevUserId = prevProps.match.params.userId;
const prevType = prevProps.match.params.type;
if (!userId && type !== prevType) {
setProfile({ isVisitor: type === "guest" });
} else if (userId !== prevUserId) {
if (userId !== undefined && userId !== prevUserId) {
fetchProfile(userId);
}
}
@ -36,17 +32,17 @@ class ProfileAction extends React.Component {
render() {
console.log("ProfileAction render")
const { profile } = this.props;
const { profile, match } = this.props;
return (
<I18nextProvider i18n={i18n}>
{profile
{profile || match.params.type
? <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent />}
sectionBodyContent={profile.id ? <UpdateUserForm /> : <CreateUserForm />}
sectionBodyContent={match.params.type ? <CreateUserForm /> : <UpdateUserForm />}
/>
: <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
@ -62,9 +58,7 @@ class ProfileAction extends React.Component {
ProfileAction.propTypes = {
match: PropTypes.object.isRequired,
profile: PropTypes.object,
setProfile: PropTypes.func.isRequired,
fetchProfile: PropTypes.func.isRequired,
resetProfile: PropTypes.func.isRequired
fetchProfile: PropTypes.func.isRequired
};
function mapStateToProps(state) {
@ -74,7 +68,5 @@ function mapStateToProps(state) {
}
export default connect(mapStateToProps, {
setProfile,
fetchProfile,
resetProfile
fetchProfile
})(ProfileAction);

View File

@ -33,32 +33,6 @@ export function getUserRole(profile) {
return "user";
};
export function profileEqual(profileA, profileB) {
const keys = Object.keys(profileA);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (key === "groups") {
if (profileA[key].length !== profileB[key].length)
return false;
const groupsA = profileA[key].map(group => group.id);
const groupsB = profileA[key].map(group => group.id);
for (let j = 0; j < groupsA.length; j++) {
if (!groupsB.includes(groupsA[j]))
return false;
}
}
if(profileA[key] !== profileB[key])
return false;
}
return true;
}
export function toEmployeeWrapper(profile) {
const emptyData = {
id: "",
@ -68,6 +42,7 @@ export function toEmployeeWrapper(profile) {
password: "",
birthday: "",
sex: "male",
passwordType: "link",
workFrom: "",
location: "",
title: "",
@ -111,12 +86,16 @@ export function createProfile(profile) {
const {people} = getState();
const {filter} = people;
const member = employeeWrapperToMemberModel(profile);
let result;
return api.createUser(member).then(res => {
checkResponseError(res);
return Promise.resolve(dispatch(setProfile(res.data.response)));
result = res.data.response;
return dispatch(setProfile(result));
}).then(() => {
return fetchPeopleByFilter(dispatch, filter);
}).then(() => {
return Promise.resolve(result);
});
};
};
@ -126,12 +105,16 @@ export function updateProfile(profile) {
const {people} = getState();
const {filter} = people;
const member = employeeWrapperToMemberModel(profile);
let result;
return api.updateUser(member).then(res => {
checkResponseError(res);
return Promise.resolve(dispatch(setProfile(res.data.response)));
result = res.data.response;
return Promise.resolve(dispatch(setProfile(result)));
}).then(() => {
return fetchPeopleByFilter(dispatch, filter);
}).then(() => {
return Promise.resolve(result);
});
};
};

View File

@ -86,3 +86,9 @@ export function updateUserType(type, userIds) {
? fakeApi.updateUserType(type, userIds)
: axios.put(`${API_URL}/people/type/${type}`, { userIds });
}
export function resendUserInvites(userIds) {
return IS_FAKE
? fakeApi.resendUserInvites(userIds)
: axios.put(`${API_URL}/people/invite`, { userIds });
}

View File

@ -359,3 +359,50 @@ export function updateUserType(type, userIds) {
}
]);
}
export function resendUserInvites(userIds) {
return fakeResponse([
{
id: userIds[0],
userName: "Mike.Zanyatski",
isVisitor: false,
firstName: "Mike",
lastName: "Zanyatski",
email: "my@gmail.com",
birthday: "2019-08-19T01:39:25.3240031Z",
sex: "male",
status: 1,
activationStatus: 0,
terminated: "2019-08-19T01:39:25.3240031Z",
department: "Marketing",
workFrom: "2019-08-19T01:39:25.3240031Z",
location: "Palo Alto",
notes: "Notes to worker",
displayName: null,
title: "Manager",
contacts: [
{
type: "GTalk",
value: "my@gmail.com"
}
],
groups: [
{
id: "00000000-0000-0000-0000-000000000000",
name: "Group Name",
manager: "Jake.Zazhitski"
}
],
avatarMedium: "url to medium avatar",
avatar: "url to big avatar",
isAdmin: false,
isLDAP: false,
listAdminModules: ["projects", "crm"],
isOwner: false,
cultureName: "en-EN",
isSSO: false,
avatarSmall: "url to small avatar",
profileUrl: ""
}
]);
}

View File

@ -1774,7 +1774,7 @@ asap@~2.0.3, asap@~2.0.6:
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
"asc-web-components@file:../../../packages/asc-web-components":
version "1.0.27"
version "1.0.29"
dependencies:
"@emotion/core" "10.0.16"
prop-types "^15.7.2"
@ -8876,6 +8876,11 @@ react-dev-utils@^9.0.3:
strip-ansi "5.2.0"
text-table "0.2.0"
react-device-detect@^1.7.5:
version "1.7.5"
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-1.7.5.tgz#04c8a6475d67b5ac4f984c8d912ec11134f5b893"
integrity sha512-ccgJuTHVCI3yfqvgU56gQDvjueFyaHVAXsa5rJuzWRasvsd0IalzfyccSECIlygjv3E+DmAGcwNYWUarUA82Fw==
react-dom@^16.9.0:
version "16.9.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0.tgz#5e65527a5e26f22ae3701131bcccaee9fb0d3962"

View File

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

View File

@ -43,7 +43,7 @@ const StyledComboBox = styled.div`
:hover{
border-color: ${state => state.isOpen ? '#2DA7DB' : '#A3A9AE' };
cursor: ${props => (props.isDisabled || !props.options.length) ? 'default' : 'pointer'};
cursor: ${props => (props.isDisabled || !props.options.length ) ? (props.advancedOptions) ? 'pointer' : 'default' : 'pointer'};
${props => props.isDisabled && `
border-color: #ECEEF1;
@ -57,7 +57,6 @@ const StyledComboButton = styled.div`
justify-content: center;
height: ${props => props.noBorder ? `18px` : `30px`};
margin-left: 8px;
`;
@ -68,7 +67,7 @@ const StyledIcon = styled.div`
`;
const StyledOptionalItem = styled.div`
margin-right: 8px;
margin-right: 8px;
`;
const StyledLabel = styled.div`
@ -99,8 +98,8 @@ const StyledArrowIcon = styled.div`
width: 8px;
flex: 0 0 8px;
margin-top: ${props => props.noBorder ? `5px` : `12px`};
margin-right: ${props => props.options.length ? '8px' : '0px'};
margin-left: ${props => props.options.length ? 'auto' : '0px'};
margin-right: ${props => props.needDisplay ? '8px' : '0px'};
margin-left: ${props => props.needDisplay ? 'auto' : '0px'};
${props => props.isOpen && `
transform: scale(1, -1);
@ -132,7 +131,7 @@ class ComboBox extends React.PureComponent {
toggle = (isOpen) => this.setState({ isOpen: isOpen });
comboBoxClick = (e) => {
if (this.props.isDisabled || !this.props.options.length || e.target.closest('.optionalBlock')) return;
if (this.props.isDisabled || e.target.closest('.optionalBlock')) return;
this.toggle(!this.state.isOpen);
};
@ -165,8 +164,17 @@ class ComboBox extends React.PureComponent {
render() {
//console.log("ComboBox render");
const { dropDownMaxHeight, isDisabled, directionX, directionY, scaled, children, options, noBorder } = this.props;
const {
dropDownMaxHeight,
isDisabled,
directionX,
directionY,
scaled,
children,
options,
noBorder,
advancedOptions
} = this.props;
const { isOpen, selectedOption } = this.state;
const dropDownMaxHeightProp = dropDownMaxHeight ? { maxHeight: dropDownMaxHeight } : {};
@ -183,9 +191,11 @@ class ComboBox extends React.PureComponent {
onSelect={this.stopAction}
>
<StyledComboButton noBorder={noBorder}>
{children &&
<StyledOptionalItem className='optionalBlock'>
{children}
</StyledOptionalItem>
}
{selectedOption && selectedOption.icon &&
<StyledIcon>
{React.createElement(Icons[selectedOption.icon],
@ -200,9 +210,8 @@ class ComboBox extends React.PureComponent {
<StyledLabel noBorder={noBorder}>
{selectedOption.label}
</StyledLabel>
<StyledArrowIcon options={options} noBorder={noBorder} isOpen={isOpen}>
{options.length > 0 &&
<StyledArrowIcon needDisplay={options.length > 0 || advancedOptions !== undefined} noBorder={noBorder} isOpen={isOpen}>
{(options.length > 0 || advancedOptions !== undefined) &&
React.createElement(Icons['ExpanderDownIcon'],
{
size: 'scale',
@ -211,7 +220,6 @@ class ComboBox extends React.PureComponent {
})
}
</StyledArrowIcon>
</StyledComboButton>
<DropDown
directionX={directionX}
@ -221,7 +229,9 @@ class ComboBox extends React.PureComponent {
{...dropDownMaxHeightProp}
{...dropDownManualWidthProp}
>
{options.map((option) =>
{advancedOptions
? advancedOptions
: options.map((option) =>
<DropDownItem {...option}
disabled={option.disabled || (option.label === selectedOption.label)}
onClick={this.optionClick.bind(this, option)}
@ -237,12 +247,14 @@ ComboBox.propTypes = {
noBorder: PropTypes.bool,
isDisabled: PropTypes.bool,
selectedOption: PropTypes.object.isRequired,
options: PropTypes.array.isRequired,
advancedOptions: PropTypes.element,
onSelect: PropTypes.func,
dropDownMaxHeight: PropTypes.number,
size: PropTypes.oneOf(['base', 'middle', 'big', 'huge', 'content']),
scaled: PropTypes.bool,
scaled: PropTypes.bool
}
ComboBox.defaultProps = {

View File

@ -14,7 +14,7 @@ const fontStyle = css`
font-style: normal;
`;
const StyledDropdownItem = styled.button`
const StyledDropdownItem = styled.div`
width: ${props => (props.isSeparator ? 'calc(100% - 32px)' : '100%')};
height: ${props => (props.isSeparator && '1px')};
line-height: ${props => (props.isSeparator ? '1px' : '36px')};
@ -80,7 +80,7 @@ const IconWrapper = styled.span`
const DropDownItem = props => {
//console.log("DropDownItem render");
const { isSeparator, label, icon } = props;
const { isSeparator, label, icon, children } = props;
const color = props.disabled ? '#A3A9AE' : '#333333';
return (
@ -90,7 +90,7 @@ const DropDownItem = props => {
{React.createElement(Icons[icon], {size: "scale", color: color, isfill: true})}
</IconWrapper>
}
{isSeparator ? '\u00A0' : label}
{isSeparator ? '\u00A0' : label ? label : children && children}
</StyledDropdownItem>
);
};

View File

@ -5,9 +5,57 @@
Custom combo box input
Options have options:
key - Item key, may be a string or a number,
label - Display text,
icon - Optional name of icon that will be displayed before label
- key - Item key, may be a string or a number
- label - Display text
- icon - Optional name of icon that will be displayed before label
- disabled - Make option disabled
- onClick - On click function
ComboBox perceives all property`s for positioning from DropDown!
If you need to display a custom list of options, you must use advancedOptions property. Like this:
```js
const advancedOptions = (
<>
<DropDownItem>
<RadioButton value="asc" name="first" label="A-Z" isChecked={true} />
</DropDownItem>
<DropDownItem>
<RadioButton value="desc" name="first" label="Z-A" />
</DropDownItem>
<DropDownItem isSeparator />
<DropDownItem>
<RadioButton value="first" name="second" label="First name" />
</DropDownItem>
<DropDownItem>
<RadioButton
value="last"
name="second"
label="Last name"
isChecked={true}
/>
</DropDownItem>
</>
);
<ComboBox
options={[]} // An empty array will enable advancedOptions
advancedOptions={advancedOptions}
onSelect={option => action("Selected option")(option)}
selectedOption={{
key: 0,
label: "Select"
}}
isDisabled={false}
scaled={false}
size="content"
directionX="right"
>
<Icons.NavLogoIcon size="medium" key="comboIcon" />
</ComboBox>;
```
#### Usage
@ -17,38 +65,16 @@ import { ComboBox } from 'asc-web-components';
const options = [
{
key: 1,
icon: 'CatalogEmployeeIcon',
label: 'Option 1'
icon: 'CatalogEmployeeIcon', // optional item
label: 'Option 1',
disabled: false, // optional item
onClick: clickFunction // optional item
},
{
key: 2,
icon: 'CatalogGuestIcon',
label: 'Option 2',
},
{
key: 3,
label: 'Option 3'
},
{
key: 4,
label: 'Option 4'
},
{
key: 5,
icon: 'CopyIcon',
label: 'Option 5'
},
{
key: 6,
label: 'Option 6'
},
{
key: 7,
label: 'Option 7'
}
...
];
<ComboBox options={options}
<ComboBox
options={options}
isDisabled={false}
selectedOption={{
key: 0,
@ -65,7 +91,7 @@ const options = [
#### 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 |
@ -74,3 +100,4 @@ const options = [
| `dropDownMaxHeight` | `number` | - | - | - | Height of Dropdown |
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
| `advancedOptions` | `element` | - | - | - | If you need display options not basic options |

View File

@ -5,8 +5,9 @@ import { withKnobs, boolean, select, number } from '@storybook/addon-knobs/react
import { optionsKnob as options } from '@storybook/addon-knobs';
import withReadme from 'storybook-readme/with-readme';
import Readme from './README.md';
import { ComboBox, Icons, Button } from 'asc-web-components'
import { ComboBox, Icons, Button, RadioButton, DropDownItem } from 'asc-web-components'
import Section from '../../../.storybook/decorators/section';
import styled from 'styled-components'
const iconNames = Object.keys(Icons);
const sizeOptions = ['base', 'middle', 'big', 'huge', 'content'];
@ -79,8 +80,37 @@ storiesOf('Components|Input', module)
}
});
const advancedOptions =
<>
<DropDownItem>
<RadioButton value='asc' name='first' label='A-Z' isChecked={true} />
</DropDownItem>
<DropDownItem >
<RadioButton value='desc' name='first' label='Z-A' />
</DropDownItem>
<DropDownItem isSeparator />
<DropDownItem>
<RadioButton value='first' name='second' label='First name' />
</DropDownItem>
<DropDownItem>
<RadioButton value='last' name='second' label='Last name' isChecked={true} />
</DropDownItem>
</>;
const childrenItems = children.length > 0 ? children : null;
return (
<Section>
<table style={{ width: 584, borderCollapse: "separate" }}>
<thead>
<tr>
<th>Default</th>
<th>Advanced</th>
</tr>
</thead>
<tbody>
<tr>
<td style={{ paddingBottom: 20 }}>
<ComboBox
options={comboOptions}
onSelect={option => action("Selected option")(option)}
@ -94,8 +124,30 @@ storiesOf('Components|Input', module)
scaled={boolean('scaled', false)}
size={select('size', sizeOptions, 'content')}
>
{children}
{childrenItems}
</ComboBox>
</td>
<td style={{ paddingBottom: 20 }}>
<ComboBox
options={[]}
advancedOptions={advancedOptions}
onSelect={option => action("Selected option")(option)}
selectedOption={{
key: 0,
label: 'Select'
}}
isDisabled={boolean('isDisabled', false)}
scaled={false}
size='content'
directionX='right'
>
<Icons.NavLogoIcon size="medium" key='comboIcon' />
</ComboBox>
</td>
</tr>
</tbody>
</table>
</Section>
);
});

View File

@ -27,7 +27,26 @@ or
{toastr.success('Some text for toast')}
</Toast>
```
You can use simple html tags. For this action you should wrap your message by empty tags:
```js
<Toast />
<button onClick={() => toastr.success(<>You have <b>bold text</b></>)}>Click</button>
```
If your notification include only text in html tags or data in JSX tags, you can omit empty tags:
```js
<Toast />
<button onClick={() => toastr.success(<b>Bold text</b>)}>Click</button>
```
```js
import { Text } from 'asc-web-components';
<Toast />
<button onClick={() => toastr.success(<Text.Body>The email activation instructions have been sent to the <b>{user.email}</b> email address</Text.Body>))}>Click</button>
```
#### Properties