Repository navigation
[BD-46] feat: development Truncate component #1472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
a12f0d4
d60d381
2e6c79b
24c85f6
1552b60
e1d0b38
4d9fb56
74d0e17
6ebe95d
3500382
c7f78e4
57d8a80
427b2cf
a91ef05
f674abd
ed2d8c7
2d249fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| --- | ||
| title: 'Truncate' | ||
| type: 'component' | ||
| components: | ||
| - Truncate | ||
| status: 'New' | ||
| designStatus: 'Done' | ||
| devStatus: 'Done' | ||
| --- | ||
|
|
||
| A Truncate component can help you crop multiline text. There will be three dots at the end of the text. | ||
|
|
||
| ### Basic Usage | ||
|
|
||
| ```jsx live | ||
| <Truncate lines={2}> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This behavior is caused by the width of the container, which is different for each theme. When using truncation by the consumer, there will be no such problems. |
||
| Learners, course teams, researchers, developers: the edX community includes groups with a range of reasons | ||
| for using the platform and objectives to accomplish. To help members of each group learn about what edX | ||
| offers, reach goals, and solve problems, edX provides a variety of information resources. | ||
| Learners, course teams, researchers, developers: the edX community includes groups with a range of reasons | ||
| for using the platform and objectives to accomplish. To help members of each group learn about what edX | ||
| offers, reach goals, and solve problems, edX provides a variety of information resources. | ||
| </Truncate> | ||
| ``` | ||
|
|
||
| ### With the custom ellipsis | ||
|
|
||
| ```jsx live | ||
| <Truncate lines={2} ellipsis="🎉🎉🎉" whiteSpace> | ||
| Learners, course teams, researchers, developers: the edX community includes groups with a range of reasons | ||
| for using the platform and objectives to accomplish. To help members of each group learn about what edX | ||
| offers, reach goals, and solve problems, edX provides a variety of information resources. | ||
| </Truncate> | ||
| ``` | ||
|
|
||
| ### Usage in component properties | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe rename this to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed |
||
|
|
||
| ```jsx live | ||
| () => { | ||
| const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth }); | ||
|
|
||
| return ( | ||
| <Card style={{ width: isExtraSmall ? "100%" : "18rem" }} isClickable> | ||
| <Card.ImageCap | ||
| src="https://source.unsplash.com/360x200/?nature,flower" | ||
| srcAlt="Card image" | ||
| /> | ||
| <Card.Header | ||
| title={ | ||
| <Truncate lines={2}> | ||
| Using Enhanced Capabilities In Your Course | ||
| </Truncate>} | ||
| /> | ||
| <Card.Section> | ||
| <Truncate lines={4}> | ||
| Learners, course teams, researchers, developers: the edX community includes groups with a range of reasons | ||
| for using the platform and objectives to accomplish. To help members of each group learn about what edX | ||
| offers, reach goals, and solve problems, edX provides a variety of information resources. | ||
| </Truncate> | ||
| </Card.Section> | ||
| <Card.Footer | ||
| textElement={ | ||
| <Truncate lines={2}> | ||
| Using Enhanced Capabilities In Your Course | ||
| </Truncate>} | ||
| > | ||
| <Button style={{ width: '100%' }}>Action 1</Button> | ||
| </Card.Footer> | ||
| </Card> | ||
| ) | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { constructString, cropText, truncateLines } from './utils'; | ||
|
|
||
| describe('utils', () => { | ||
| describe('cropText', () => { | ||
| it('return the contents of the sliced text', () => { | ||
| const text = 'Learners, course teams, researchers, developers'; | ||
| const cropDecrement = 0.70; | ||
| const croppedText = cropText(text, cropDecrement); | ||
| expect(croppedText).toEqual('Learners, course teams, research'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('constructString', () => { | ||
| it('return new string text after constructed', () => { | ||
| const string = 'Learners, course teams, researchers, developers'; | ||
| const whiteSpace = true; | ||
| const ellipsis = '...'; | ||
| const finalString = constructString(string, whiteSpace, ellipsis); | ||
| expect(finalString).toEqual('Learners, course teams, researchers, developers ...'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('truncateLines', () => { | ||
| it('returned truncate lines', () => { | ||
| const element = document.createElement('div'); | ||
| jest.spyOn(document, 'createElement').mockReturnValue({ | ||
| parentNode: { | ||
| removeChild: () => {}, | ||
| }, | ||
| scrollHeight: 220, | ||
| setAttribute: () => {}, | ||
| set innerHTML(val) { | ||
| this.scrollHeight -= 60; | ||
| }, | ||
| }); | ||
|
|
||
| const text = 'Learners, course teams, researchers, developers: the edX community includes groups with ' | ||
| + 'a range of reasons for using the platform and objectives to accomplish.'; | ||
| const lines = 2; | ||
| const whiteSpace = false; | ||
| const ellipsis = '___'; | ||
| expect(truncateLines(text, element, { | ||
| lines, | ||
| whiteSpace, | ||
| ellipsis, | ||
| })).toEqual('Learners, course teams, researchers, developers: the edX community includes groups with a range of ' | ||
| + 'reasons for using the platform and objectives to accompl___'); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import React from 'react'; | ||
| import { mount } from 'enzyme'; | ||
| import Truncate from './index'; | ||
|
|
||
| describe('<Truncate />', () => { | ||
| const wrapper = mount( | ||
| <Truncate> | ||
| Learners, course teams, researchers, developers. | ||
| </Truncate>, | ||
| ); | ||
| it('render with className', () => { | ||
| wrapper.setProps({ className: 'pgn__truncate' }); | ||
| expect(wrapper.hasClass('pgn__truncate')).toEqual(true); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import React, { | ||
| useEffect, useRef, useState, | ||
| } from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import { truncateLines } from './utils'; | ||
| import { useWindowSize } from '../index'; | ||
|
|
||
| const DEFAULT_TRUNCATE_LINES = 1; | ||
| const DEFAULT_TRUNCATE_ELLIPSIS = '...'; | ||
| const DEFAULT_TRUNCATE_ELEMENT_TYPE = 'div'; | ||
|
|
||
| const Truncate = ({ | ||
| children, lines, ellipsis, elementType, className, whiteSpace, | ||
| }) => { | ||
| const [truncateText, setTruncateText] = useState(''); | ||
| const textContainer = useRef(); | ||
| const { width } = useWindowSize(); | ||
|
|
||
| useEffect(() => { | ||
|
peterkulko marked this conversation as resolved.
Resolved
|
||
| const newTruncateText = truncateLines(children, textContainer.current, { | ||
| ellipsis, | ||
| whiteSpace, | ||
| lines, | ||
| }); | ||
| setTruncateText(newTruncateText); | ||
| }, [children, ellipsis, lines, whiteSpace, width]); | ||
|
|
||
| return React.createElement(elementType, { | ||
| ref: textContainer, | ||
| className, | ||
| }, truncateText); | ||
| }; | ||
|
|
||
| Truncate.propTypes = { | ||
| /** The expected text to which the ellipsis would be applied. */ | ||
| children: PropTypes.string.isRequired, | ||
| /** The number of lines the text to be truncated to. */ | ||
| lines: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||
| /** Text content for the ellipsis - will appear after the truncated lines. */ | ||
| ellipsis: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.node]), | ||
| /** The whitespace from before the ellipsis. */ | ||
|
peterkulko marked this conversation as resolved.
Resolved
|
||
| whiteSpace: PropTypes.bool, | ||
| /** Custom html element for truncated text. */ | ||
| elementType: PropTypes.string, | ||
| /** Specifies class name to append to the base element. */ | ||
| className: PropTypes.string, | ||
| }; | ||
|
|
||
| Truncate.defaultProps = { | ||
| lines: DEFAULT_TRUNCATE_LINES, | ||
| ellipsis: DEFAULT_TRUNCATE_ELLIPSIS, | ||
| whiteSpace: false, | ||
| elementType: DEFAULT_TRUNCATE_ELEMENT_TYPE, | ||
| className: null, | ||
|
peterkulko marked this conversation as resolved.
Resolved
|
||
| }; | ||
|
|
||
| export default Truncate; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| const LINE_HEIGHT_VALUE = 40; | ||
| const CROP_DECREMENT_STEP = 0.01; | ||
|
|
||
| const createCopyElement = (element) => { | ||
| const newElement = document.createElement(element.tagName); | ||
| newElement.setAttribute( | ||
| 'style', | ||
| `line-height: ${LINE_HEIGHT_VALUE}px; display: inline-block;`, | ||
| ); | ||
| return newElement; | ||
| }; | ||
|
|
||
| const constructString = (text, whiteSpace, ellipsis) => { | ||
| const spacer = whiteSpace ? ' ' : ''; | ||
| return `${text.trim()}${spacer}${ellipsis}`; | ||
| }; | ||
|
|
||
| const cropText = (text, cropDecrement) => { | ||
| const sliceIndex = Math.floor(text.length * cropDecrement); | ||
| return text.slice(0, sliceIndex); | ||
| }; | ||
|
|
||
| const truncateLines = (text, element, { lines, whiteSpace, ellipsis }) => { | ||
|
peterkulko marked this conversation as resolved.
Resolved
|
||
| const visibilityArea = LINE_HEIGHT_VALUE * Number(lines); | ||
| const newElement = createCopyElement(element); | ||
| let truncateText = text; | ||
| let cropDecrement = 1; | ||
|
|
||
| element.append(newElement); | ||
| newElement.innerHTML = constructString(text, whiteSpace, ellipsis); | ||
|
|
||
| let newElementTextHeight = newElement.scrollHeight; | ||
|
|
||
| if (visibilityArea > newElementTextHeight) { | ||
| newElement.parentNode.removeChild(newElement); | ||
| return truncateText; | ||
| } | ||
|
|
||
| while (newElementTextHeight > visibilityArea) { | ||
| cropDecrement -= CROP_DECREMENT_STEP; | ||
| truncateText = cropText(text, cropDecrement); | ||
| newElement.innerHTML = constructString(truncateText, whiteSpace, ellipsis); | ||
| newElementTextHeight = newElement.scrollHeight; | ||
| } | ||
|
|
||
| newElement.parentNode.removeChild(newElement); | ||
| return constructString(truncateText, whiteSpace, ellipsis); | ||
| }; | ||
|
|
||
| module.exports = { | ||
| cropText, | ||
| truncateLines, | ||
| constructString, | ||
| createCopyElement, | ||
| }; | ||


Uh oh!
There was an error while loading. Please reload this page.