170 lines
3.4 KiB
JavaScript
170 lines
3.4 KiB
JavaScript
/**
|
|
* External dependencies
|
|
*/
|
|
import React, { createRef, Component } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { __ } from '@wordpress/i18n';
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
import { clampLines } from './utils';
|
|
|
|
/**
|
|
* Show text based content, limited to a number of lines, with a read more link.
|
|
*
|
|
* Based on https://github.com/zoltantothcom/react-clamp-lines.
|
|
*/
|
|
class ReadMore extends Component {
|
|
constructor( props ) {
|
|
super( ...arguments );
|
|
|
|
this.state = {
|
|
/**
|
|
* This is true when read more has been pressed and the full review is shown.
|
|
*/
|
|
isExpanded: false,
|
|
/**
|
|
* True if we are clamping content. False if the review is short. Null during init.
|
|
*/
|
|
clampEnabled: null,
|
|
/**
|
|
* Content is passed in via children.
|
|
*/
|
|
content: props.children,
|
|
/**
|
|
* Summary content generated from content HTML.
|
|
*/
|
|
summary: '.',
|
|
};
|
|
|
|
this.reviewSummary = createRef();
|
|
this.reviewContent = createRef();
|
|
this.getButton = this.getButton.bind( this );
|
|
this.onClick = this.onClick.bind( this );
|
|
}
|
|
|
|
componentDidMount() {
|
|
if ( this.props.children ) {
|
|
const { maxLines, ellipsis } = this.props;
|
|
|
|
const lineHeight = this.reviewSummary.current.clientHeight + 1;
|
|
const reviewHeight = this.reviewContent.current.clientHeight + 1;
|
|
const maxHeight = lineHeight * maxLines + 1;
|
|
const clampEnabled = reviewHeight > maxHeight;
|
|
|
|
this.setState( {
|
|
clampEnabled,
|
|
} );
|
|
|
|
if ( clampEnabled ) {
|
|
this.setState( {
|
|
summary: clampLines(
|
|
this.reviewContent.current.innerHTML,
|
|
this.reviewSummary.current,
|
|
maxHeight,
|
|
ellipsis
|
|
),
|
|
} );
|
|
}
|
|
}
|
|
}
|
|
|
|
getButton() {
|
|
const { isExpanded } = this.state;
|
|
const { className, lessText, moreText } = this.props;
|
|
|
|
const buttonText = isExpanded ? lessText : moreText;
|
|
|
|
if ( ! buttonText ) {
|
|
return;
|
|
}
|
|
|
|
return (
|
|
<a
|
|
href="#more"
|
|
className={ className + '__read_more' }
|
|
onClick={ this.onClick }
|
|
aria-expanded={ ! isExpanded }
|
|
role="button"
|
|
>
|
|
{ buttonText }
|
|
</a>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handles the click event for the read more/less button.
|
|
*
|
|
* @param {Object} e event
|
|
*/
|
|
onClick( e ) {
|
|
e.preventDefault();
|
|
|
|
const { isExpanded } = this.state;
|
|
|
|
this.setState( {
|
|
isExpanded: ! isExpanded,
|
|
} );
|
|
}
|
|
|
|
render() {
|
|
const { className } = this.props;
|
|
const { content, summary, clampEnabled, isExpanded } = this.state;
|
|
|
|
if ( ! content ) {
|
|
return null;
|
|
}
|
|
|
|
if ( clampEnabled === false ) {
|
|
return (
|
|
<div className={ className }>
|
|
<div ref={ this.reviewContent }>{ content }</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={ className }>
|
|
{ ( ! isExpanded || clampEnabled === null ) && (
|
|
<div
|
|
ref={ this.reviewSummary }
|
|
aria-hidden={ isExpanded }
|
|
dangerouslySetInnerHTML={ {
|
|
__html: summary,
|
|
} }
|
|
/>
|
|
) }
|
|
{ ( isExpanded || clampEnabled === null ) && (
|
|
<div
|
|
ref={ this.reviewContent }
|
|
aria-hidden={ ! isExpanded }
|
|
>
|
|
{ content }
|
|
</div>
|
|
) }
|
|
{ this.getButton() }
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
ReadMore.propTypes = {
|
|
children: PropTypes.node.isRequired,
|
|
maxLines: PropTypes.number,
|
|
ellipsis: PropTypes.string,
|
|
moreText: PropTypes.string,
|
|
lessText: PropTypes.string,
|
|
className: PropTypes.string,
|
|
};
|
|
|
|
ReadMore.defaultProps = {
|
|
maxLines: 3,
|
|
ellipsis: '…',
|
|
moreText: __( 'Read more', 'woocommerce' ),
|
|
lessText: __( 'Read less', 'woocommerce' ),
|
|
className: 'read-more-content',
|
|
};
|
|
|
|
export default ReadMore;
|