-
Notifications
You must be signed in to change notification settings - Fork 100
[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
Merged
adamstankiewicz
merged 17 commits into
openedx:master
from
raccoongang:Peter_Kulko/development-Truncate-text-component
Aug 19, 2022
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
a12f0d4
docs: development Truncate component
peterkulko d60d381
refactor: added snapshot test and props description
peterkulko 2e6c79b
refactor: code refactoring
peterkulko 24c85f6
refactor: added tests coverage
peterkulko 1552b60
refactor: added tests coverage
peterkulko e1d0b38
refactor: added tests coverage for clampLines, createCopyElement func…
peterkulko 4d9fb56
refactor: writing tests for calculateEllipsisElementHeight, calculate…
peterkulko 74d0e17
refactor: refactoring snapshots
peterkulko 6ebe95d
refactor: remove snapshot tests
peterkulko 3500382
refactor: code refactoring
peterkulko c7f78e4
docs: mock scrollHeight
peterkulko 57d8a80
docs: added resize calcucation
peterkulko 427b2cf
refactor: renamed const
peterkulko a91ef05
refactor: refactoring after review
peterkulko f674abd
refactor: fixed ellipsis
peterkulko ed2d8c7
feat: added onTruncate props and example usage
peterkulko 2d249fd
refactor: refactoring after review
peterkulko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| --- | ||
| 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}> | ||
| 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> | ||
| ``` | ||
|
|
||
| ### With the onTruncate | ||
|
|
||
| ```jsx live | ||
| <Truncate lines={2} onTruncate={() => console.log('onTruncate')}> | ||
| 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> | ||
| ``` | ||
|
|
||
| ### Example usage in Card | ||
|
|
||
| ```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={{ minWidth: 100 }}>Action 1</Button> | ||
| </Card.Footer> | ||
| </Card> | ||
| ) | ||
| } | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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___'); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| 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); | ||
| }); | ||
| it('render with onTruncate', () => { | ||
| const mockFn = jest.fn(); | ||
| wrapper.setProps({ onTruncate: mockFn }); | ||
| expect(mockFn.mock.calls.length).toBe(1); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import React, { | ||
| useLayoutEffect, 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, onTruncate, | ||
| }) => { | ||
| const [truncateText, setTruncateText] = useState(''); | ||
| const textContainer = useRef(); | ||
| const { width } = useWindowSize(); | ||
|
|
||
| useLayoutEffect(() => { | ||
| const newTruncateText = truncateLines(children, textContainer.current, { | ||
| ellipsis, | ||
| whiteSpace, | ||
| lines, | ||
| }); | ||
| setTruncateText(newTruncateText); | ||
| if (onTruncate) { | ||
| onTruncate(truncateText); | ||
| } | ||
| }, [children, ellipsis, lines, onTruncate, truncateText, 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]), | ||
| /** Adds the whitespace from before the ellipsis. */ | ||
| whiteSpace: PropTypes.bool, | ||
| /** Custom html element for truncated text. */ | ||
| elementType: PropTypes.string, | ||
| /** Specifies class name to append to the base element. */ | ||
| className: PropTypes.string, | ||
| /** Callback fired when a text truncating */ | ||
| onTruncate: PropTypes.func, | ||
| }; | ||
|
|
||
| Truncate.defaultProps = { | ||
| lines: DEFAULT_TRUNCATE_LINES, | ||
| ellipsis: DEFAULT_TRUNCATE_ELLIPSIS, | ||
| whiteSpace: false, | ||
| elementType: DEFAULT_TRUNCATE_ELEMENT_TYPE, | ||
| className: undefined, | ||
| onTruncate: undefined, | ||
| }; | ||
|
|
||
| export default Truncate; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| 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); | ||
| }; | ||
|
|
||
| // This function truncates the original line of text by adding ellipsis, | ||
| // arbitrating the user's expected number of show lines, HTML element type, | ||
| // and whitespaces between the text and the ellipsis. | ||
| const truncateLines = (text, element, { lines, whiteSpace, ellipsis }) => { | ||
|
peterkulko marked this conversation as 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, | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PKulkoRaccoonGang Even though
lines={2}in the "Basic Usage" example on the docs site, I'm seeing it with like 2.5 lines on initial render, until I click "Show code example"; then, it reverts to 2 lines as expected:Any ideas what might be causing this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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.