Skip to content
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

feat: add compound components #20

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 39 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ React component that uses the css line clamping to truncate given text in specif
- 😱 It works both with plain text and HTML/Components
- 💥 Integrated show more/less behaviour
- 👂 Listens to text and lines changes and responds accordingly
- ⚙️ Easy-to-use component API
- 🌳 Tiny size, only 1.4kb

- ⚙️ Easy-to-use Composable component API
- 🌳 Tiny size
## Installation

```shell
npm install react-multiline-clamp
```

### The library gives you three composable components to use inside the Clamp component as children

- Clamp.Text
- Clamp.ShowMore
- Clamp.ShowLess

<br />

You can compose this three components inside the Clamp in the order you desire, this way you can put the show more/less elements above the text for example.

<br />

### Basic example

```jsx
Expand All @@ -27,7 +38,9 @@ import Clamp from 'react-multiline-clamp';
const MyComponent = () => {
return (
<Clamp withTooltip lines={2}>
<p>Multiline text</p>
<Clamp.Text>
<p>Multiline text</p>
</Clamp.Text>
</Clamp>
);
};
Expand All @@ -40,39 +53,35 @@ import Clamp from 'react-multiline-clamp';

const MyComponent = () => {
return (
<Clamp
lines={2}
maxLines={6}
withToggle
showMoreElement={({ toggle }) => (
<button type="button" onClick={toggle}>
<Clamp lines={2} maxLines={6} withToggle>
<Clamp.Text>
<p>Multiline text</p>
</Clamp.Text>

<Clamp.ShowMore>
<button type="button" onClick={() => console.log("Custom event")}>
Show more
</button>
)}
showLessElement={({ toggle }) => (
<span type="button" onClick={toggle}>
menossssss
</span>
)}
>
<p>Multiline text</p>
</Clamp>
</Clamp.ShowMore>

<Clamp.ShowLess />
</Clamp>
);
};
```

## API

| Name | Type | Default | Description |
| :-------------: | :---------------------: | :---------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------: |
| children | Element | | The expected element to which the ellipsis would be applied. It could be plain text or any HTML/Component |
| lines | Number | 2 | The number of lines we want the text to be truncated to |
| maxLines | Number | 8 | The maximum number of lines we want to show after clicking on showMore button |
| withTooltip | Boolean | true | Indicates if we want the text to have a tooltip title |
| withToggle | Boolean | false | Indicates if we want to have the show more/less actions |
| showMoreElement | Element | <button type="button">More</button> | Element that triggers the show more action |
| showLessElement | Element | <button type="button">Less</button> | Element that triggers the show less action |
| onShowMore | (isExpanded) => Boolean | () => {} | A callback function that gets calls every time we click on the show more/less buttons. It returns whether the text is expanded or not (Boolean) |
| Name | Type | Default | Description |
| :-------------: | :---------------------: | :---------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------: |
| children | Element | | The expected element to which the ellipsis would be applied. It could be plain text or any HTML/Component |
| lines | Number | 2 | The number of lines we want the text to be truncated to |
| maxLines | Number | 8 | The maximum number of lines we want to show after clicking on showMore button |
| withTooltip | Boolean | true | Indicates if we want the text to have a tooltip title |
| withToggle | Boolean | false | Indicates if we want to have the show more/less actions |
| Clamp.Text | Element | | Component to put the text you want to apply the ellipsis to |
| Clamp.ShowMore | Element | button | Component render the show more element |
| Clamp.ShowLess | Element | button | Component render the show less element |
| onShowMore | (isExpanded) => Boolean | () => {} | A callback function that gets calls every time we click on the show more/less buttons. It returns whether the text is expanded or not (Boolean) |

#### [See browser support](https://caniuse.com/#feat=mdn-css_properties_-webkit-line-clamp)

Expand Down
73 changes: 30 additions & 43 deletions src/Example.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import Clamp from './lib';
import React, { useState } from "react";
import Clamp from "./lib";

const initialText = `Lorem ipsum dolor sit amet consectetur adipisicing elit.
Illum perspiciatis ea consequuntur ipsum deleniti placeat
Expand All @@ -11,49 +11,36 @@ dignissimos dolor facere officia delectus corrupti
perferendis laboriosam deserunt nobis, suscipit autem atque?`;

function Example() {
const [text, setText] = useState(initialText);
const [lines, setLines] = useState(2);
const [text, setText] = useState(initialText);
const [lines, setLines] = useState(2);

return (
<div>
<h1>Multiline clamp example</h1>
<div>
<textarea
value={text}
cols={4}
rows={4}
onChange={ev => setText(ev.target.value)}
style={{ width: '100%', height: '150px' }}
/>
</div>
<div>
<input
type="text"
value={lines}
onChange={ev => setLines(ev.target.value)}
/>
</div>
return (
<div>
<h1>Multiline clamp example</h1>
<div>
<textarea
value={text}
cols={4}
rows={4}
onChange={(ev) => setText(ev.target.value)}
style={{ width: "100%", height: "150px" }}
/>
</div>
<div>
<input
type="text"
value={lines}
onChange={(ev) => setLines(ev.target.value)}
/>
</div>

<Clamp
showMoreElement={({ toggle }) => (
<button type="button" onClick={toggle}>
Show more
</button>
)}
showLessElement={({ toggle }) => (
<span type="button" onClick={toggle}>
menossssss
</span>
)}
lines={lines}
maxLines={8}
withToggle
onShowMore={show => console.log(show)}
>
<p>{text}</p>
</Clamp>
</div>
);
<Clamp lines={lines} maxLines={8}>
<Clamp.Text>
<p>{text}</p>
</Clamp.Text>
</Clamp>
</div>
);
}

export default Example;
173 changes: 98 additions & 75 deletions src/lib/clamp.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,104 @@
import React, { useState, Fragment, useEffect } from 'react';
import isCssEllipsisApplied from './utils/isCssEllipsisApplied';
import TruncatedElement from './truncatedElement';

const defaultShowMoreElement = ({ toggle }) => (
<button type="button" onClick={toggle}>
More
</button>
);

const defaultShowLessElement = ({ toggle }) => (
<button type="button" onClick={toggle}>
Less
</button>
);
import React, { useState, useEffect, useContext, createContext } from "react";
import PropTypes from "prop-types";
import isCssEllipsisApplied from "./utils/isCssEllipsisApplied";
import TruncatedText from "./text";
import ShowMore from "./showMore";
import ShowLess from "./showLess";

const ClampContext = createContext();

export const useClampContext = () => {
const context = useContext(ClampContext);

if (!context) {
throw new Error(
`Clamp show more/less components cannot be rendered outside the Clamp component`
);
}

return context;
};

const Clamp = ({
children,
lines = 2,
maxLines = 8,
withTooltip = true,
withToggle = false,
showMoreElement = defaultShowMoreElement,
showLessElement = defaultShowLessElement,
onShowMore = () => {}
children,
lines,
maxLines,
withTooltip,
withToggle,
onShowMore
}) => {
const [sLines, setLines] = useState(lines);
const [isExpanded, setIsExpanded] = useState(false);
const [showMore, setShowMore] = useState(false);

const handleToggleShowMore = show => {
const newLines = show ? maxLines : lines;

setShowMore(showMore => !showMore);
setIsExpanded(isExpanded => !isExpanded);
setLines(newLines);

onShowMore(show);
};

const handleConfigElement = elem => {
if (!elem) return;

if (isCssEllipsisApplied(elem)) {
if (withTooltip) {
const title = elem.textContent;
elem.setAttribute('title', title);
}

if (withToggle && !showMore && !isExpanded) {
setShowMore(true);
}
} else {
elem.removeAttribute('title');
setShowMore(false);
}
};

useEffect(() => {
if (lines) {
setLines(lines);
}
}, [lines]);

return (
<Fragment>
<TruncatedElement lines={sLines} getRef={handleConfigElement}>
{children}
</TruncatedElement>

{showMore &&
!isExpanded &&
showMoreElement({ toggle: () => handleToggleShowMore(true) })}

{isExpanded &&
showLessElement({ toggle: () => handleToggleShowMore(false) })}
</Fragment>
);
const [sLines, setLines] = useState(lines);
const [isExpanded, setIsExpanded] = useState(false);
const [showMore, setShowMore] = useState(false);

const handleToggleShowMore = () => {
const newLines = showMore ? maxLines : lines;

setShowMore(!showMore);
setIsExpanded(!isExpanded);
setLines(newLines);

onShowMore(showMore);
};

const handleConfigElement = (elem) => {
if (!elem) return;

if (isCssEllipsisApplied(elem)) {
if (withTooltip) {
const title = elem.textContent;
elem.setAttribute("title", title);
}

if (withToggle && !showMore && !isExpanded) {
setShowMore(true);
}
} else {
elem.removeAttribute("title");
setShowMore(false);
}
};

useEffect(() => {
if (lines) {
setLines(lines);
}
}, [lines]);

return (
<ClampContext.Provider
value={{
toggle: handleToggleShowMore,
showMore,
isExpanded,
lines: sLines,
configElement: handleConfigElement
}}
>
{children}
</ClampContext.Provider>
);
};

Clamp.propTypes = {
children: PropTypes.element.isRequired,
lines: PropTypes.number,
maxLines: PropTypes.number,
withTooltip: PropTypes.bool,
withToggle: PropTypes.bool,
onShowMore: PropTypes.func
};

Clamp.defaultProps = {
lines: 2,
maxLines: 8,
withTooltip: true,
withToggle: false,
onShowMore: () => {}
};

Clamp.Text = TruncatedText;
Clamp.ShowMore = ShowMore;
Clamp.ShowLess = ShowLess;

export default Clamp;
25 changes: 25 additions & 0 deletions src/lib/showLess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import PropTypes from "prop-types";
import { useClampContext } from "./clamp";
import cloneReactElement from "./utils/cloneReactElement";
import orchestrateClickEvents from "./utils/orchestrateClickEvents";

const ShowLess = ({ children }) => {
const { toggle, isExpanded } = useClampContext();

return isExpanded
? cloneReactElement(children, {
onClick: () => orchestrateClickEvents(children.props.onClick, toggle)
})
: null;
};

ShowLess.propTypes = {
children: PropTypes.element
};

ShowLess.defaultProps = {
children: <button type="button">Less</button>
};

export default ShowLess;
Loading