diff --git a/products/ASC.People/Client/yarn.lock b/products/ASC.People/Client/yarn.lock index c9309d6583..143021fc95 100644 --- a/products/ASC.People/Client/yarn.lock +++ b/products/ASC.People/Client/yarn.lock @@ -1802,7 +1802,7 @@ asap@~2.0.6: integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= "asc-web-components@file:../../../packages/asc-web-components": - version "1.0.102" + version "1.0.147" dependencies: moment "^2.24.0" prop-types "^15.7.2" @@ -1813,6 +1813,7 @@ asap@~2.0.6: react-dropzone "^10.1.8" react-text-mask "^5.4.3" react-toastify "^5.3.2" + react-tooltip "^3.11.1" react-virtualized-auto-sizer "^1.0.2" react-window "^1.8.5" reactstrap "^8.0.1" @@ -2619,7 +2620,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@2.x, classnames@^2.2.3, classnames@^2.2.6: +classnames@2.x, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -8518,7 +8519,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8971,6 +8972,14 @@ react-toastify@^5.3.2: prop-types "^15.7.2" react-transition-group "^2.6.1" +react-tooltip@^3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-3.11.1.tgz#7b4ce48ed26a46e996662b19a2afebbfd483513b" + integrity sha512-YCMVlEC2KuHIzOQhPplTK5jmBBwoL+PYJJdJKXj7M/h7oevupd/QSVq6z5U7/ehIGXyHsAqvwpdxexDfyQ0o3A== + dependencies: + classnames "^2.2.5" + prop-types "^15.6.0" + react-transition-group@^2.3.1, react-transition-group@^2.6.1: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" diff --git a/web/ASC.Web.Client/yarn.lock b/web/ASC.Web.Client/yarn.lock index 6d058f79d3..b8a9b043a8 100644 --- a/web/ASC.Web.Client/yarn.lock +++ b/web/ASC.Web.Client/yarn.lock @@ -1802,7 +1802,7 @@ asap@~2.0.6: integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= "asc-web-components@file:../../packages/asc-web-components": - version "1.0.116" + version "1.0.147" dependencies: moment "^2.24.0" prop-types "^15.7.2" @@ -1813,6 +1813,7 @@ asap@~2.0.6: react-dropzone "^10.1.8" react-text-mask "^5.4.3" react-toastify "^5.3.2" + react-tooltip "^3.11.1" react-virtualized-auto-sizer "^1.0.2" react-window "^1.8.5" reactstrap "^8.0.1" @@ -2619,7 +2620,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@2.x, classnames@^2.2.3, classnames@^2.2.6: +classnames@2.x, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -3736,11 +3737,6 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -email-addresses@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.0.3.tgz#fc3c6952f68da24239914e982c8a7783bc2ed96d" - integrity sha512-kUlSC06PVvvjlMRpNIl3kR1NRXLEe86VQ7N0bQeaCZb2g+InShCeHQp/JvyYNTugMnRN2NvJhHlc3q12MWbbpg== - emoji-regex@^7.0.1, emoji-regex@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -8516,7 +8512,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8964,6 +8960,14 @@ react-toastify@^5.3.2: prop-types "^15.7.2" react-transition-group "^2.6.1" +react-tooltip@^3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-3.11.1.tgz#7b4ce48ed26a46e996662b19a2afebbfd483513b" + integrity sha512-YCMVlEC2KuHIzOQhPplTK5jmBBwoL+PYJJdJKXj7M/h7oevupd/QSVq6z5U7/ehIGXyHsAqvwpdxexDfyQ0o3A== + dependencies: + classnames "^2.2.5" + prop-types "^15.6.0" + react-transition-group@^2.3.1, react-transition-group@^2.6.1: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" diff --git a/web/ASC.Web.Components/package.json b/web/ASC.Web.Components/package.json index 6e3d91a79d..295cf21e16 100644 --- a/web/ASC.Web.Components/package.json +++ b/web/ASC.Web.Components/package.json @@ -71,6 +71,7 @@ "enzyme-adapter-react-16": "^1.14.0", "eslint": "^6.3.0", "eslint-plugin-react": "^7.14.3", + "faker": "^4.1.0", "jest": "^24.8.0", "jest-enzyme": "^7.1.0", "jest-junit": "^8.0.0", @@ -114,6 +115,7 @@ "react-tooltip": "^3.11.1", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.8.5", + "react-window-infinite-loader": "^1.0.5", "reactstrap": "^8.0.1" }, "resolutions": { diff --git a/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector.stories.js b/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector.stories.js index 83334460c6..66a946fb53 100644 --- a/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector.stories.js +++ b/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector.stories.js @@ -1,3 +1,4 @@ +/* eslint-disable react/prop-types */ import React from "react"; import { storiesOf } from "@storybook/react"; import { action } from '@storybook/addon-actions'; @@ -6,8 +7,11 @@ import withReadme from "storybook-readme/with-readme"; import Readme from "./README.md"; import AdvancedSelector from "./"; import Section from "../../../.storybook/decorators/section"; -import { ArrayValue, BooleanValue } from "react-values"; +import { BooleanValue } from "react-values"; import Button from "../button"; +import { isEqual } from "lodash"; +import faker, { name } from "faker"; +import { slice } from "lodash/slice"; function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min)) + min; @@ -79,12 +83,28 @@ const groups = [ const sizes = ["compact", "full"]; const displayTypes = ['dropdown', 'aside']; -storiesOf("Components|AdvancedSelector", module) - .addDecorator(withKnobs) - .addDecorator(withReadme(Readme)) - .add("base", () => { - const optionsCount = number("Users count", 1000); - const options = Array.from({ length: optionsCount }, (v, index) => { + +class ExampleList extends React.Component { + constructor(props) { + super(props); + + const { total, isOpen } = props; + + this.state = { + isOpen: isOpen, + total: total, + options: [], + hasNextPage: false, + isNextPageLoading: false + } + + faker.seed(123); + this.persons = new Array(50) + .fill(true) + .map(() => ({ name: name.findName() })); + this.persons.sort((a, b) => a.name.localeCompare(b.name)); + + this.AllOptions = Array.from({ length: this.state.total }, (v, index) => { const additional_group = groups[getRandomInt(1, 6)]; groups[0].total++; additional_group.total++; @@ -95,6 +115,115 @@ storiesOf("Components|AdvancedSelector", module) }; }); + console.log(this.persons); + } + + loadNextPage = (startIndex, stopIndex) => { + this.setState({ isNextPageLoading: true }); + + const promise = new Promise((resolve) => { + setTimeout(() => { + this.setState({ + hasNextPage: this.AllOptions.length < 100, + isNextPageLoading: false + }); + + resolve(slice(this.AllOptions, startIndex, stopIndex - startIndex)); + }, 2500); + }); + + return promise; + }; + + componentDidUpdate(prevProps) { + const {total, options, isOpen} = this.props; + if(!isEqual(prevProps.options, options)) + { + this.setState({ + options: options + }); + } + + if(isOpen !== prevProps.isOpen) { + this.setState({ + isOpen: isOpen + }); + } + + if(total !== prevProps.total) { + this.setState({ + total: total, + option: [] + }); + } + } + + render() { + const { isOpen, options, total, hasNextPage, isNextPageLoading } = this.state; + return ( + { + action("onSearchChanged")(value); + /*set( + options.filter(option => { + return option.label.indexOf(value) > -1; + }) + );*/ + }} + //options={options} + total={total} + hasNextPage={hasNextPage} + isNextPageLoading={isNextPageLoading} + loadNextPage={this.loadNextPage} + groups={groups} + selectedGroups={[groups[0]]} + isMultiSelect={boolean("isMultiSelect", true)} + buttonLabel={text("buttonLabel", "Add members")} + selectAllLabel={text("selectAllLabel", "Select all")} + onSelect={selectedOptions => { + action("onSelect")(selectedOptions); + //toggle(); + }} + //onCancel={toggle} + onChangeGroup={group => { + /*set( + options.filter(option => { + return ( + option.groups && + option.groups.length > 0 && + option.groups.indexOf(group.key) > -1 + ); + }) + );*/ + }} + allowCreation={boolean("allowCreation", false)} + onAddNewClick={() => action("onSelect") } + isOpen={isOpen} + displayType={select("displayType", displayTypes, "dropdown")} + /> + ); + } + +} + +storiesOf("Components|AdvancedSelector", module) + .addDecorator(withKnobs) + .addDecorator(withReadme(Readme)) + .add("base", () => { + /*const optionsCount = number("Users count", 1000); + const options = Array.from({ length: optionsCount }, (v, index) => { + const additional_group = groups[getRandomInt(1, 6)]; + groups[0].total++; + additional_group.total++; + return { + key: `user${index}`, + groups: ["group-all", additional_group.key], + label: `User${index + 1} (All groups, ${additional_group.label})` + }; + });*/ + return (
(
+ + )}
diff --git a/web/ASC.Web.Components/src/components/advanced-selector/index.js b/web/ASC.Web.Components/src/components/advanced-selector/index.js index a823149b78..6d0821a813 100644 --- a/web/ASC.Web.Components/src/components/advanced-selector/index.js +++ b/web/ASC.Web.Components/src/components/advanced-selector/index.js @@ -1,574 +1,24 @@ import React from "react"; -import styled, { css } from "styled-components"; import PropTypes from "prop-types"; -import SearchInput from "../search-input"; -import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list"; -import { FixedSizeList } from "react-window"; -import Link from "../link"; -import Checkbox from "../checkbox"; -import Button from "../button"; -import { Icons } from "../icons"; -import ComboBox from "../combobox"; -import { Text } from "../text"; -import findIndex from "lodash/findIndex"; -import filter from "lodash/filter"; -import isEqual from "lodash/isEqual"; import DropDown from "../drop-down"; -import { handleAnyClick } from "../../utils/event"; -import isEmpty from "lodash/isEmpty"; import Aside from "../layout/sub-components/aside"; +import ADSelectorBody from "./sub-components/body"; const displayTypes = ["dropdown", "aside"]; -/* eslint-disable no-unused-vars */ -/* eslint-disable react/prop-types */ -const Container = ({ - value, - placeholder, - isMultiSelect, - size, - width, - maxHeight, - isDisabled, - onSelect, - onSearchChanged, - options, - selectedOptions, - buttonLabel, - selectAllLabel, - groups, - selectedGroups, - onChangeGroup, - isOpen, - displayType, - containerWidth, - containerHeight, - allowCreation, - onAddNewClick, - allowAnyClickClose, - ...props -}) =>
; -/* eslint-enable react/prop-types */ -/* eslint-enable no-unused-vars */ - -const StyledContainer = styled(Container)` - .dropdown-container { - - } - - .aside-container { - - } -`; - -const StyledBodyContainer = styled(Container)` - display: flex; - flex-direction: column; - - ${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")} - ${props => (props.containerHeight ? `height: ${props.containerHeight};` : "")} - - .data_container { - margin: 16px 16px -5px 16px; - - .head_container { - display: flex; - margin-bottom: ${props => (props.displayType === "dropdown" ? 8 : 16)}px; - - .options_searcher { - display: inline-block; - width: 100%; - - ${props => - props.displayType === "dropdown" && - props.size === "full" && - css` - margin-right: ${props => (props.allowCreation ? 8 : 16)}px; - `} - /*${props => - props.allowCreation - ? css` - width: 272px; - margin-right: 8px; - ` - : css` - width: ${props => (props.isDropDown ? "313px" : "100%")}; - `}*/ - } - - .add_new_btn { - ${props => - props.allowCreation && - css` - display: inline-block; - vertical-align: top; - height: 32px; - width: 36px; - margin-right: 16px; - line-height: 18px; - `} - } - - } - - .options_group_selector { - margin-bottom: 12px; - } - - .data_column_one { - ${props => - props.displayType === "dropdown" && - props.groups && - props.groups.length > 0 - ? css` - width: 50%; - display: inline-block; - ` - : ""} - - .options_list { - margin-top: 4px; - margin-left: -8px; - .option { - line-height: 32px; - padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px; - cursor: pointer; - - .option_checkbox { - /*margin-left: 8px;*/ - } - - .option_link { - padding-left: 8px; - } - - &:hover { - background-color: #f8f9f9; - } - } - } - } - - .data_column_two { - ${props => - props.displayType === "dropdown" && - props.groups && - props.groups.length > 0 - ? css` - width: 50%; - display: inline-block; - border-left: 1px solid #eceef1; - ` - : ""} - - .group_header { - font-weight: 600; - padding-left: 16px; - padding-bottom: 14px; - } - - .group_list { - margin-left: 8px; - - .option { - line-height: 32px; - padding-left: 8px; - cursor: pointer; - - .option_checkbox { - /*margin-left: 8px;*/ - } - - .option_link { - padding-left: 8px; - } - - &:hover { - background-color: #eceef1; - } - } - } - } - } - - .button_container { - border-top: 1px solid #eceef1; - display: flex; - - .add_members_btn { - margin: 16px; - } - } -`; - class AdvancedSelector extends React.Component { - constructor(props) { - super(props); - - this.ref = React.createRef(); - - const groups = this.convertGroups(this.props.groups); - const currentGroup = this.getCurrentGroup(groups); - - this.state = { - selectedOptions: this.props.selectedOptions || [], - selectedAll: this.props.selectedAll || false, - groups: groups, - currentGroup: currentGroup - }; - - if (props.isOpen) handleAnyClick(true, this.handleClick); - } - - handleClick = e => { - if ( - this.props.isOpen && - this.props.allowAnyClickClose && - this.ref && - this.ref.current && - !this.ref.current.contains(e.target) && - e && - e.target && - e.target.className && - typeof e.target.className.indexOf === "function" && - e.target.className.indexOf("option_checkbox") === -1 - ) { - this.props.onCancel && this.props.onCancel(); - } - }; - - componentWillUnmount() { - handleAnyClick(false, this.handleClick); - } - - shouldComponentUpdate(nextProps, nextState) { - return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState); - } - - componentDidUpdate(prevProps) { - if (this.props.isOpen !== prevProps.isOpen) { - handleAnyClick(this.props.isOpen, this.handleClick); - } - - if (this.props.allowAnyClickClose !== prevProps.allowAnyClickClose) { - handleAnyClick(this.props.allowAnyClickClose, this.handleClick); - } - - let newState = {}; - - if (!isEqual(this.props.selectedOptions, prevProps.selectedOptions)) { - newState = { selectedOptions: this.props.selectedOptions }; - } - - if (this.props.isMultiSelect !== prevProps.isMultiSelect) { - newState = Object.assign({}, newState, { - selectedOptions: [] - }); - } - - if (this.props.selectedAll !== prevProps.selectedAll) { - newState = Object.assign({}, newState, { - selectedAll: this.props.selectedAll - }); - } - - if (!isEqual(this.props.groups, prevProps.groups)) { - const groups = this.convertGroups(this.props.groups); - const currentGroup = this.getCurrentGroup(groups); - newState = Object.assign({}, newState, { - groups, - currentGroup - }); - } - - if (!isEmpty(newState)) { - this.setState({ ...this.state, ...newState }); - } - } - - convertGroups = groups => { - if (!groups) return []; - - const wrappedGroups = groups.map(this.convertGroup); - - return wrappedGroups; - }; - - convertGroup = group => { - return { - key: group.key, - label: `${group.label} (${group.total})`, - total: group.total - }; - }; - - getCurrentGroup = groups => { - const currentGroup = groups.length > 0 ? groups[0] : "No groups"; - return currentGroup; - }; - - onButtonClick = () => { - this.props.onSelect && - this.props.onSelect( - this.state.selectedAll ? this.props.options : this.state.selectedOptions - ); - }; - - onSelect = option => { - this.props.onSelect && this.props.onSelect(option); - }; - - onChange = (option, e) => { - const { selectedOptions } = this.state; - const newSelectedOptions = e.target.checked - ? [...selectedOptions, option] - : filter(selectedOptions, obj => obj.key !== option.key); - - //console.log("onChange", option, e.target.checked, newSelectedOptions); - - this.setState({ - selectedOptions: newSelectedOptions, - selectedAll: newSelectedOptions.length === this.props.options.length - }); - }; - - onSelectedAllChange = e => { - this.setState({ - selectedAll: e.target.checked, - selectedOptions: e.target.checked ? this.props.options : [] - }); - }; - - onCurrentGroupChange = group => { - this.setState({ - currentGroup: group - }); - - this.props.onChangeGroup && this.props.onChangeGroup(group); - }; - - renderRow = ({ data, index, style }) => { - const option = data[index]; - var isChecked = - this.state.selectedAll || - findIndex(this.state.selectedOptions, { key: option.key }) > -1; - - //console.log("renderRow", option, isChecked, this.state.selectedOptions); - return ( -
- {this.props.isMultiSelect ? ( - - ) : ( - - {option.label} - - )} -
- ); - }; - - renderBody = () => { - const { - value, - placeholder, - isDisabled, - onSearchChanged, - options, - isMultiSelect, - buttonLabel, - selectAllLabel, - size, - displayType, - onAddNewClick, - allowCreation - } = this.props; - - const { selectedOptions, selectedAll, currentGroup, groups } = this.state; - - /*const containerHeight = - size === "compact" ? (!groups || !groups.length ? 336 : 326) : 614; - const containerWidth = - size === "compact" ? (!groups || !groups.length ? 325 : 326) : isDropDown ? 690 : 326; - const listHeight = - size === "compact" - ? !groups || !groups.length - ? isMultiSelect - ? 176 - : 226 - : 120 - : 488; - const listWidth = isDropDown ? 320 : "100%";*/ - - let containerHeight; - let containerWidth; - let listHeight; - let listWidth; - const itemHeight = 32; - const hasGroups = groups && groups.length > 0; - - switch (size) { - case "compact": - containerHeight = hasGroups ? "326px" : "100%"; - containerWidth = "379px"; - listWidth = displayType === "dropdown" ? 356 : 356; - listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226; - break; - case "full": - default: - containerHeight = "100%"; - containerWidth = displayType === "dropdown" ? "690px" : "326px"; - listWidth = displayType === "dropdown" ? 320 : 302; - listHeight = 488; - break; - } - - return ( - -
-
-
-
- - {allowCreation && ( -
- {displayType === "aside" && groups && groups.length > 0 && ( - - )} - {isMultiSelect && !groups && !groups.length && ( - 0} - className="option_select_all_checkbox" - onChange={this.onSelectedAllChange} - /> - )} - - {this.renderRow.bind(this)} - -
- {displayType === "dropdown" && - size === "full" && - groups && - groups.length > 0 && ( -
- - Groups - - - {this.renderRow.bind(this)} - -
- )} -
- {isMultiSelect && ( -
-
- )} -
-
- ); - }; - render() { const { displayType, isOpen } = this.props; //console.log("AdvancedSelector render()"); return ( - - {displayType === "dropdown" ? ( - - {this.renderBody()} + displayType === "dropdown" + ? + - ) : ( -