initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,169 @@
/**
* 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: '&hellip;',
moreText: __( 'Read more', 'woocommerce' ),
lessText: __( 'Read less', 'woocommerce' ),
className: 'read-more-content',
};
export default ReadMore;

View File

@ -0,0 +1,53 @@
/**
* Internal dependencies
*/
import ReadMore from '../';
export default {
title: 'WooCommerce Blocks/@base-components/ReadMore',
component: ReadMore,
};
export const Default = () => (
<ReadMore maxLines={ 2 }>
<h1>
No! Alderaan is peaceful. We have no weapons. You can&apos;t
possibly
</h1>
<p>
As you wish. But with the blast shield down, I can&apos;t even see!
How am I supposed to fight? Look, I ain&apos;t in this for your
revolution, and I&apos;m not in it for you, Princess. I expect to be
well paid. I&apos;m in it for the money.
</p>
<p>
You mean it controls your actions?
<strong>
{ ' ' }
She must have hidden the plans in the escape pod.
</strong>{ ' ' }
<em>
Send a detachment down to retrieve them, and see to it
personally, Commander.
</em>
There&apos;ll be no one to stop us this time!
</p>
<h2>Escape is not his plan. I must face him, alone.</h2>
<ol>
<li>Partially, but it also obeys your commands.</li>
<li>
Leave that to me. Send a distress signal, and inform the Senate
that all on board were killed.
</li>
<li>
A tremor in the Force. The last time I felt it was in the
presence of my old master.
</li>
</ol>
<aside>
<a href="http://fillerama.io">
Content from http://fillerama.io &quot;Star Wars&quot;
</a>
</aside>
</ReadMore>
);

View File

@ -0,0 +1,34 @@
/**
* Internal dependencies
*/
import { truncateHtml } from '../utils';
const shortContent =
'<p>Lorem ipsum dolor sit amet, <strong>consectetur.</strong>.</p>';
const longContent =
'<p>Lorem ipsum dolor sit amet, <strong>consectetur adipiscing elit. Nullam a condimentum diam.</strong> Donec finibus enim eros, et lobortis magna varius quis. Nulla lacinia tellus ac neque aliquet, in porttitor metus interdum. Maecenas vestibulum nisi et auctor vestibulum. Maecenas vehicula, lacus et pellentesque tempor, orci nulla mattis purus, id porttitor augue magna et metus. Aenean hendrerit aliquet massa ac convallis. Mauris vestibulum neque in condimentum porttitor. Donec viverra, orci a accumsan vehicula, dui massa lobortis lorem, et cursus est purus pulvinar elit. Vestibulum vitae tincidunt ex, ut vulputate nisi.</p>' +
'<p>Morbi tristique iaculis felis, sed porta urna tincidunt vitae. Etiam nisl sem, eleifend non varius quis, placerat a arcu. Donec consectetur nunc at orci fringilla pulvinar. Nam hendrerit tellus in est aliquet varius id in diam. Donec eu ullamcorper ante. Ut ultricies, felis vel sodales aliquet, nibh massa vestibulum ipsum, sed dignissim mi nunc eget lacus. Curabitur mattis placerat magna a aliquam. Nullam diam elit, cursus nec erat ullamcorper, tempor eleifend mauris. Nunc placerat nunc ut enim ornare tempus. Fusce porta molestie ante eget faucibus. Fusce eu lectus sit amet diam auctor lacinia et in diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris eu lacus lobortis, faucibus est vel, pulvinar odio. Duis feugiat tortor quis dui euismod varius.</p>';
describe( 'ReadMore Component', () => {
describe( 'Test the truncateHtml function', () => {
it( 'Truncate long HTML content to length of 10', () => {
const truncatedContent = truncateHtml( longContent, 10 );
expect( truncatedContent ).toEqual( '<p>Lorem ipsum...</p>' );
} );
it( 'Truncate long HTML content, but avoid cutting off HTML tags.', () => {
const truncatedContent = truncateHtml( longContent, 40 );
expect( truncatedContent ).toEqual(
'<p>Lorem ipsum dolor sit amet, <strong>consectetur...</strong></p>'
);
} );
it( 'No need to truncate short HTML content.', () => {
const truncatedContent = truncateHtml( shortContent, 100 );
expect( truncatedContent ).toEqual(
'<p>Lorem ipsum dolor sit amet, <strong>consectetur.</strong>.</p>'
);
} );
} );
} );

View File

@ -0,0 +1,87 @@
/**
* External dependencies
*/
import trimHtml from 'trim-html';
/**
* Truncate some HTML content to a given length.
*
* @param {string} html HTML that will be truncated.
* @param {number} length Length to truncate the string to.
* @param {string} ellipsis Character to append to truncated content.
*/
export const truncateHtml = ( html, length, ellipsis = '...' ) => {
const trimmed = trimHtml( html, {
suffix: ellipsis,
limit: length,
} );
return trimmed.html;
};
/**
* Clamp lines calculates the height of a line of text and then limits it to the
* value of the lines prop. Content is updated once limited.
*
* @param {string} originalContent Content to be clamped.
* @param {Object} targetElement Element which will contain the clamped content.
* @param {number} maxHeight Max height of the clamped content.
* @param {string} ellipsis Character to append to clamped content.
* @return {string} clamped content
*/
export const clampLines = (
originalContent,
targetElement,
maxHeight,
ellipsis
) => {
const length = calculateLength( originalContent, targetElement, maxHeight );
return truncateHtml( originalContent, length - ellipsis.length, ellipsis );
};
/**
* Calculate how long the content can be based on the maximum number of lines allowed, and client height.
*
* @param {string} originalContent Content to be clamped.
* @param {Object} targetElement Element which will contain the clamped content.
* @param {number} maxHeight Max height of the clamped content.
*/
const calculateLength = ( originalContent, targetElement, maxHeight ) => {
let markers = {
start: 0,
middle: 0,
end: originalContent.length,
};
while ( markers.start <= markers.end ) {
markers.middle = Math.floor( ( markers.start + markers.end ) / 2 );
// We set the innerHTML directly in the DOM here so we can reliably check the clientHeight later in moveMarkers.
targetElement.innerHTML = truncateHtml(
originalContent,
markers.middle
);
markers = moveMarkers( markers, targetElement.clientHeight, maxHeight );
}
return markers.middle;
};
/**
* Move string markers. Used by calculateLength.
*
* @param {Object} markers Markers for clamped content.
* @param {number} currentHeight Current height of clamped content.
* @param {number} maxHeight Max height of the clamped content.
*/
const moveMarkers = ( markers, currentHeight, maxHeight ) => {
if ( currentHeight <= maxHeight ) {
markers.start = markers.middle + 1;
} else {
markers.end = markers.middle - 1;
}
return markers;
};