Skip to content

Instantly share code, notes, and snippets.

@peteygao
Created November 24, 2023 08:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peteygao/d184ab05fffb24832a0234297b3029ed to your computer and use it in GitHub Desktop.
Save peteygao/d184ab05fffb24832a0234297b3029ed to your computer and use it in GitHub Desktop.
Expandable Text web component (hide text behind a "Read more" button)
class ExpandableText extends HTMLElement {
private shadow: ShadowRoot;
private container: HTMLDivElement;
private readMoreBtn: HTMLSpanElement;
private expanded: boolean;
private containerLargerThanText: boolean;
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.expanded = false;
this.containerLargerThanText = false;
// Create elements
this.container = document.createElement('div');
this.container.id = 'container';
this.readMoreBtn = document.createElement('span');
this.readMoreBtn.id = 'readMoreBtn';
this.readMoreBtn.textContent = 'Read more';
this.readMoreBtn.style.display = 'none';
this.readMoreBtn.addEventListener('click', () => {
this.expanded = true;
this.render();
});
// Add styles
const style: HTMLStyleElement = document.createElement('style');
style.textContent = `
div {
white-space: normal;
}
#container {
overflow: hidden;
white-space: normal;
line-height: 1.25rem;
width: 100%;
display: block;
transition: max-height 0.3s ease-out;
}
.transparentGradient {
-webkit-mask-image: linear-gradient(180deg, #000 60%, transparent);
mask-image: linear-gradient(180deg, #000 60%, transparent);
}
#readMoreBtn {
margin-top: 12px;
display: none;
cursor: pointer;
color: blue;
text-decoration: underline;
}
`;
// Append elements to shadow DOM
this.shadow.appendChild(style);
this.shadow.appendChild(this.container);
this.shadow.appendChild(this.readMoreBtn);
// Initial setup
window.addEventListener('resize', this.render.bind(this));
}
connectedCallback(): void {
this.render();
}
static get observedAttributes(): string[] {
return ['text', 'height'];
}
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
this.render();
}
private render(): void {
const text: string = this.getAttribute('text') || '';
const height: string = this.getAttribute('height') || '100'; // Default height
this.container.textContent = text;
this.containerLargerThanText = this.container.scrollHeight <= this.container.clientHeight + 2;
this.readMoreBtn.style.display = this.containerLargerThanText || this.expanded ? 'none' : 'block';
// Apply styles dynamically
this.container.style.maxHeight = this.expanded ? `${this.container.scrollHeight}px` : `${height}px`;
if (this.expanded || this.containerLargerThanText) {
this.container.classList.remove('transparentGradient');
} else {
this.container.classList.add('transparentGradient');
}
}
}
customElements.define('expandable-text', ExpandableText);
@peteygao
Copy link
Author

This was created to be used with Elm and elm-ui, which is why there's the weird div { white-space: normal } override.

In action, it looks like this:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment