Merge pull request #9 from ONLYOFFICE/feature/layout-scrolling

Feature/layout scrolling
This commit is contained in:
Alexey Safronov 2019-11-20 11:50:09 +03:00 committed by GitHub
commit 1b0a6bb9a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 183 additions and 87 deletions

View File

@ -39,7 +39,7 @@ class GroupAction extends React.Component {
<I18nextProvider i18n={i18n}>
{group || !match.params.groupId
? <PageLayout
withBodyScroll={false}
withBodyScroll={true}
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}

View File

@ -39,6 +39,7 @@ import {
deleteUser
} from "../../../../../store/services/api";
import { isMobileOnly } from "react-device-detect";
import isEqual from "lodash/isEqual";
class SectionBodyContent extends React.PureComponent {
constructor(props) {
@ -461,6 +462,19 @@ class SectionBodyContent extends React.PureComponent {
});
};
needForUpdate = (currentProps, nextProps) => {
if (currentProps.checked !== nextProps.checked) {
return true;
}
if (currentProps.status !== nextProps.status) {
return true;
}
if (!isEqual(currentProps.data, nextProps.data)) {
return true;
}
return false;
};
render() {
console.log("Home SectionBodyContent render()");
const { users, viewer, selection, history, settings, t } = this.props;
@ -468,7 +482,7 @@ class SectionBodyContent extends React.PureComponent {
return users.length > 0 ? (
<>
<RowContainer>
<RowContainer useReactWindow={false}>
{users.map(user => {
const contextOptions = this.getUserContextOptions(user, viewer);
const contextOptionsProps = !contextOptions.length
@ -494,6 +508,7 @@ class SectionBodyContent extends React.PureComponent {
onSelect={this.onContentRowSelect}
{...checkedProps}
{...contextOptionsProps}
needForUpdate={this.needForUpdate}
>
<UserContent
user={user}

View File

@ -116,10 +116,12 @@ const SectionPagingContent = ({
console.log("SectionPagingContent render", filter);
return (
return filter.total < filter.pageCount ? (
<></>
) : (
<Paging
previousLabel={t('PreviousPage')}
nextLabel={t('NextPage')}
previousLabel={t("PreviousPage")}
nextLabel={t("NextPage")}
pageItems={pageItems}
onSelectPage={onChangePage}
countItems={countItems}

View File

@ -52,7 +52,7 @@ class PureHome extends React.Component {
if (headerVisible || selected === "close") {
newState.isHeaderVisible = headerVisible;
if (selected === "close") {
setSelected({ selected: "none" });
setSelected("none");
}
}
@ -111,7 +111,7 @@ class PureHome extends React.Component {
fontColor={"#999"}
/>
<PageLayout
withBodyScroll={false}
withBodyScroll={true}
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}

View File

@ -14,6 +14,7 @@ import {
PAGE_COUNT,
EmployeeStatus
} from "../../helpers/constants";
import unionBy from 'lodash/unionBy';
export const SET_GROUPS = "SET_GROUPS";
export const SET_USERS = "SET_USERS";
@ -190,11 +191,14 @@ function fetchPeopleByFilter(dispatch, filter) {
}
export function updateUserStatus(status, userIds) {
return dispatch => {
return (dispatch, getState) => {
return api.updateUserStatus(status, userIds).then(users => {
users.forEach(user => {
dispatch(setUser(user));
});
const { people } = getState();
const { users: currentUsers } = people;
const newUsers = unionBy(users, currentUsers, "id");
dispatch(setUsers(newUsers));
});
};
}

View File

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

View File

@ -45,8 +45,8 @@ class PageLayout extends React.PureComponent {
isArticleAvailable = isArticleHeaderAvailable || isArticleMainButtonAvailable || isArticleBodyAvailable,
isSectionHeaderAvailable = !!props.sectionHeaderContent,
isSectionFilterAvailable = !!props.sectionFilterContent,
isSectionBodyAvailable = !!props.sectionBodyContent,
isSectionPagingAvailable = !!props.sectionPagingContent,
isSectionBodyAvailable = !!props.sectionBodyContent || isSectionFilterAvailable || isSectionPagingAvailable,
isSectionAvailable = isSectionHeaderAvailable || isSectionFilterAvailable || isSectionBodyAvailable || isSectionPagingAvailable || isArticleAvailable,
isBackdropAvailable = isArticleAvailable;
@ -154,17 +154,19 @@ class PageLayout extends React.PureComponent {
{this.state.isSectionHeaderAvailable && (
<SectionHeader>{this.state.sectionHeaderContent}</SectionHeader>
)}
{this.state.isSectionFilterAvailable && (
<SectionFilter>{this.state.sectionFilterContent}</SectionFilter>
)}
{this.state.isSectionBodyAvailable && (
<SectionBody withScroll={this.props.withBodyScroll}>
<SectionBody withScroll={this.props.withBodyScroll} pinned={this.state.isArticlePinned}>
{this.state.isSectionFilterAvailable && (
<SectionFilter>{this.state.sectionFilterContent}</SectionFilter>
)}
{this.state.sectionBodyContent}
{this.state.isSectionPagingAvailable && (
<SectionPaging>{this.state.sectionPagingContent}</SectionPaging>
)}
</SectionBody>
)}
{this.state.isSectionPagingAvailable && (
<SectionPaging>{this.state.sectionPagingContent}</SectionPaging>
)}
{this.state.isArticleAvailable && (
<SectionToggler
visible={!this.state.isArticlePinned}

View File

@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import Scrollbar from "../../scrollbar";
import { tablet } from "../../../utils/device";
const StyledSectionBody = styled.div`
margin: 16px 0;
@ -10,16 +11,31 @@ const StyledSectionBody = styled.div`
height: 100%;
`;
const StyledSpacer = styled.div`
display: none;
min-height: 64px;
@media ${tablet} {
display: ${props => (props.pinned ? "none" : "block")};
}
`;
const SectionBody = React.memo(props => {
//console.log("PageLayout SectionBody render");
const { children, withScroll } = props;
const { children, withScroll, pinned } = props;
return (
<StyledSectionBody>
{withScroll ? (
<Scrollbar stype="mediumBlack">{children}</Scrollbar>
<Scrollbar stype="mediumBlack">
{children}
<StyledSpacer pinned={pinned}/>
</Scrollbar>
) : (
<>{children}</>
<>
{children}
<StyledSpacer pinned={pinned}/>
</>
)}
</StyledSectionBody>
);
@ -29,6 +45,7 @@ SectionBody.displayName = "SectionBody";
SectionBody.propTypes = {
withScroll: PropTypes.bool,
pinned: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
@ -37,7 +54,8 @@ SectionBody.propTypes = {
};
SectionBody.defaultProps = {
withScroll: true
withScroll: true,
pinned: false
};
export default SectionBody;

View File

@ -2,7 +2,7 @@ import React from "react";
import styled from "styled-components";
const StyledSectionFilter = styled.div`
margin: 16px 0 0;
margin: 0 0 16px;
`;
const SectionFilter = React.memo(props => {

View File

@ -4,6 +4,7 @@ import styled from "styled-components";
const StyledSectionHeader = styled.div`
border-bottom: 1px solid #eceef1;
height: 56px;
margin-right: 16px;
`;
const SectionHeader = React.memo(props => {

View File

@ -2,7 +2,7 @@ import React from "react";
import styled from "styled-components";
const StyledSectionPaging = styled.div`
margin: 0 0 16px;
margin: 16px 0 0;
`;
const SectionPaging = React.memo(props => {

View File

@ -6,6 +6,8 @@ import { Icons } from "../../icons";
const StyledSectionToggler = styled.div`
height: 64px;
position: fixed;
bottom: 0;
display: none;
@media ${tablet} {
@ -19,6 +21,7 @@ const StyledSectionToggler = styled.div`
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.13);
border-radius: 48px;
cursor: pointer;
background: #fff;
}
`;

View File

@ -2,7 +2,7 @@ import React from "react";
import styled from "styled-components";
const StyledSection = styled.section`
padding: 0 16px;
padding: 0 0 0 16px;
flex-grow: 1;
display: flex;
flex-direction: column;

View File

@ -18,7 +18,8 @@ Container for rows
#### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | -------- | :------: | ------ | ------- | --------------------------------------------------------------- |
| `manualHeight` | `string` | - | | - | Allows you to set fixed block height for Row |
| `itemHeight` | `number` | - | | 50 | Height of one Row element. Required for scroll to work properly |
| Props | Type | Required | Values | Default | Description |
| ---------------- | -------- | :------: | ------ | ------- | --------------------------------------------------------------- |
| `manualHeight` | `string` | - | | - | Allows you to set fixed block height for Row |
| `itemHeight` | `number` | - | | 50 | Height of one Row element. Required for scroll to work properly |
| `useReactWindow` | `bool` | - | | true | Use react-window for efficiently rendering large lists |

View File

@ -8,7 +8,9 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import ContextMenu from '../context-menu';
const StyledRowContainer = styled.div`
height: ${props => props.manualHeight ? props.manualHeight : '100%'};
height: ${props => props.useReactWindow ? props.manualHeight ? props.manualHeight : '100%' : 'auto'};
margin: 16px 0;
position: relative;
`;
class RowContainer extends React.PureComponent {
@ -49,7 +51,7 @@ class RowContainer extends React.PureComponent {
}, areEqual);
render() {
const { manualHeight, itemHeight, children } = this.props;
const { manualHeight, itemHeight, children, useReactWindow } = this.props;
const renderList = ({ height, width }) => (
<List
@ -66,11 +68,17 @@ class RowContainer extends React.PureComponent {
);
return (
<StyledRowContainer id='rowContainer' manualHeight={manualHeight}>
<AutoSizer>
{renderList}
</AutoSizer>
<ContextMenu targetAreaId='rowContainer' options={this.state.contextOptions} />
<StyledRowContainer id="rowContainer" manualHeight={manualHeight} useReactWindow={useReactWindow}>
{ useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
) : (
children.map((item, index) => (
<div key={index} onContextMenu={this.onRowContextClick.bind(this, item.props.contextOptions)}>
{item}
</div>
))
)}
<ContextMenu targetAreaId="rowContainer" options={this.state.contextOptions} />
</StyledRowContainer>
);
}
@ -79,11 +87,13 @@ class RowContainer extends React.PureComponent {
RowContainer.propTypes = {
itemHeight: PropTypes.number,
manualHeight: PropTypes.string,
children: PropTypes.any.isRequired
children: PropTypes.any.isRequired,
useReactWindow: PropTypes.bool
};
RowContainer.defaultProps = {
itemHeight: 50,
useReactWindow: true
};
export default RowContainer;

View File

@ -1,5 +1,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import withReadme from 'storybook-readme/with-readme';
import Readme from './README.md';
import Section from '../../../.storybook/decorators/section';
import RowContainer from '.';
import Row from '../row';
@ -45,6 +47,7 @@ const fillFakeData = (n) => {
const fakeData = fillFakeData(20);
storiesOf('Components|RowContainer', module)
.addDecorator(withReadme(Readme))
.add('base', () => {
return (
<Section>
@ -71,7 +74,7 @@ storiesOf('Components|RowContainer', module)
</>
{user.isHead
? <Link containerWidth='120px' type='page' title='Head of department' fontSize={12} color={sideInfoColor} >Head of department</Link>
: <div containerWidth='120px'></div>
: <div></div>
}
<Link containerWidth='160px' type='action' title={user.department} fontSize={12} color={sideInfoColor} >{user.department}</Link>
<Link type='page' title={user.mobilePhone} fontSize={12} color={sideInfoColor} >{user.mobilePhone}</Link>

View File

@ -29,4 +29,5 @@ Displays content as row.
| `element` | `element` | - | | ` ` | Required to host some component. It has a fixed order of location, if the Checkbox component is specified, then it follows, otherwise it occupies the first position. If there is no value, the occupied space is distributed among the other child elements. |
| `contextOptions` | `array` | - | | ` ` | Required to host the ContextMenuButton component. It is always located near the right border of the container, regardless of the contents of the child elements. If there is no value, the occupied space is distributed among the other child elements. |
| `data` | `object` | - | | ` ` | Current row item information. |
| `onSelect` | `function` | - | | ` ` | Event when selecting row element. Returns data value. |
| `onSelect` | `function` | - | | ` ` | Event when selecting row element. Returns data value. |
| `needForUpdate` | `function` | - | | ` ` | Custom shouldComponentUpdate function |

View File

@ -1,17 +1,17 @@
import React from 'react'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import Checkbox from '../checkbox'
import ContextMenuButton from '../context-menu-button'
import { tablet } from '../../utils/device';
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import isEqual from "lodash/isEqual";
import Checkbox from "../checkbox";
import ContextMenuButton from "../context-menu-button";
import { tablet } from "../../utils/device";
const StyledRow = styled.div`
cursor: default;
min-height: 50px;
width: 100%;
border-bottom: 1px solid #ECEEF1;
border-bottom: 1px solid #eceef1;
display: flex;
flex-direction: row;
@ -55,38 +55,64 @@ const StyledOptionButton = styled.div`
`;
// eslint-disable-next-line react/display-name
const Row = props => {
const changeCheckbox = (e) => {
props.onSelect && props.onSelect(e.target.checked, props.data);
};
const getOptions = () => props.contextOptions;
//console.log("Row render");
const { checked, element, children, contextOptions } = props;
class Row extends React.Component {
shouldComponentUpdate(nextProps) {
if (this.props.needForUpdate) {
return this.props.needForUpdate(this.props, nextProps);
}
return !isEqual(this.props, nextProps);
}
return (
<StyledRow {...props}>
{Object.prototype.hasOwnProperty.call(props, 'checked') &&
<StyledCheckbox>
<Checkbox isChecked={checked} onChange={changeCheckbox} />
</StyledCheckbox>
}
{Object.prototype.hasOwnProperty.call(props, 'element') &&
<StyledElement>
{element}
</StyledElement>
}
<StyledContent>
{children}
</StyledContent>
<StyledOptionButton>
{Object.prototype.hasOwnProperty.call(props, 'contextOptions') && contextOptions.length > 0 &&
<ContextMenuButton directionX='right' getData={getOptions} />
}
</StyledOptionButton>
</StyledRow>
);
};
render() {
//console.log("Row render");
const {
checked,
element,
children,
data,
contextOptions,
onSelect
} = this.props;
const renderCheckbox = Object.prototype.hasOwnProperty.call(
this.props,
"checked"
);
const renderElement = Object.prototype.hasOwnProperty.call(
this.props,
"element"
);
const renderContext =
Object.prototype.hasOwnProperty.call(this.props, "contextOptions") &&
contextOptions.length > 0;
const changeCheckbox = e => {
onSelect && onSelect(e.target.checked, data);
};
const getOptions = () => contextOptions;
return (
<StyledRow {...this.props}>
{renderCheckbox && (
<StyledCheckbox>
<Checkbox isChecked={checked} onChange={changeCheckbox} />
</StyledCheckbox>
)}
{renderElement && <StyledElement>{element}</StyledElement>}
<StyledContent>{children}</StyledContent>
<StyledOptionButton>
{renderContext && (
<ContextMenuButton directionX="right" getData={getOptions} />
)}
</StyledOptionButton>
</StyledRow>
);
}
}
Row.propTypes = {
checked: PropTypes.bool,
@ -94,7 +120,8 @@ Row.propTypes = {
children: PropTypes.element,
data: PropTypes.object,
contextOptions: PropTypes.array,
onSelect: PropTypes.func
onSelect: PropTypes.func,
needForUpdate: PropTypes.func
};
export default Row;
export default Row;

View File

@ -7,19 +7,23 @@ const Scrollbar = React.forwardRef((props, ref) => {
const scrollbarType = {
smallWhite: {
thumbV: { backgroundColor: 'rgba(256, 256, 256, 0.2)', width: '2px', marginLeft: '2px', borderRadius: 'inherit' },
thumbH: { backgroundColor: 'rgba(256, 256, 256, 0.2)', height: '2px', marginTop: '2px', borderRadius: 'inherit' }
thumbH: { backgroundColor: 'rgba(256, 256, 256, 0.2)', height: '2px', marginTop: '2px', borderRadius: 'inherit' },
view: {}
},
smallBlack: {
thumbV: { backgroundColor: 'rgba(0, 0, 0, 0.1)', width: '2px', marginLeft: '2px', borderRadius: 'inherit' },
thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '2px', marginTop: '2px', borderRadius: 'inherit' }
thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '2px', marginTop: '2px', borderRadius: 'inherit' },
view: {}
},
mediumBlack: {
thumbV: { backgroundColor: 'rgba(0, 0, 0, 0.1)', width: '8px', borderRadius: 'inherit' },
thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '8px', borderRadius: 'inherit' }
thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '8px', borderRadius: 'inherit' },
view: {paddingRight: '16px'}
},
preMediumBlack: {
thumbV: { backgroundColor: 'rgba(0, 0, 0, 0.1)', width: '5px', borderRadius: 'inherit', cursor: 'default' },
thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '5px', borderRadius: 'inherit', cursor: 'default' }
thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '5px', borderRadius: 'inherit', cursor: 'default' },
view: {}
},
};
@ -27,6 +31,7 @@ const Scrollbar = React.forwardRef((props, ref) => {
const thumbV = stype ? stype.thumbV : {};
const thumbH = stype ? stype.thumbH : {};
const view = stype ? stype.view : {};
const renderNavThumbVertical = ({ style, ...props }) => (
<div {...props} style={{ ...style, ...thumbV }} />
@ -36,8 +41,12 @@ const Scrollbar = React.forwardRef((props, ref) => {
<div {...props} style={{ ...style, ...thumbH }} />
);
const renderView = ({ style, ...props }) => (
<div {...props} style={{ ...style, ...view}} />
);
return (
<Scrollbars renderThumbVertical={renderNavThumbVertical} renderThumbHorizontal={renderNavThumbHorizontal} {...props} ref={ref} />
<Scrollbars renderView={renderView} renderThumbVertical={renderNavThumbVertical} renderThumbHorizontal={renderNavThumbHorizontal} {...props} ref={ref} />
);
});