ReadMore
Text cut after N lines with a Read more / Show less button, left out when the text fits.
Demo
Quillo renders a page on the server, then resumes its components in the browser from the state the server sent, so a page shows at once and reacts as soon as it is touched. What someone typed and clicked before the script came is kept and done then. A component that runs by itself loads as soon as the page is idle; the rest wait until they are reached, so a page with a hundred of them costs no more than one. Pages with the same components share one bundle, whichever of them each showed.
A second paragraph, hidden until the button is pressed.
Folded.
Get it
It needs theme.qwe, as every component does, and line_clamp.qwe; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/line_clamp.qwe$ curl -O https://quillolang.org/components/ui/read_more.qweimport ui/theme.qwe use theme_cssuse ui/read_more.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
let read_more_demo_text = "Quillo renders a page on the server, then resumes its components in the browser from the state the server sent, so a page shows at once and reacts as soon as it is touched. What someone typed and clicked before the script came is kept and done then. A component that runs by itself loads as soon as the page is idle; the rest wait until they are reached, so a page with a hundred of them costs no more than one. Pages with the same components share one bundle, whichever of them each showed."component ReadMoreDemoLive() { state open = false <div.demo-stack> <ReadMore lines={2} id="read-more-demo-live" more="See all" less="See fewer" on_toggle={(o) => { open = o }}> {read_more_demo_text} </ReadMore> <p.demo-note>{open ? "Unfolded." : "Folded."}</p> </div>}view ReadMoreDemo() = <div.demo-stack.demo-narrow> <ReadMore lines={3} id="read-more-demo-three"> <p>{read_more_demo_text}</p> <p>A second paragraph, hidden until the button is pressed.</p> </ReadMore> <ReadMoreDemoLive/> <ReadMore lines={3} id="read-more-demo-short">Short enough to fit, so there is no button.</ReadMore> </div>Source
// ReadMore: text cut after a few lines, with a button that shows the rest and// folds it again. The button is left out when the text fits as it is.// A component; it needs line_clamp.qwe (it clamps with LineClamp).//// <ReadMore lines={3}><p>{post.body}</p></ReadMore>// <ReadMore lines={2} more="See all" less="See fewer" open={true}>{long_text}</ReadMore>//// children: the text or HTML. lines: how many show when folded (3). more ("Read more")// and less ("Show less"): the button's labels. open: starts unfolded.// id: the text's id, which the button's aria-controls names ("ui-read-more").// on_toggle(open): inside one of your components, called when it is folded or unfolded.import ui/line_clamp.qwe use LineClampimport ui/theme.qwe use ui_clslet read_more_css = css`.ui-read-more { display: grid; gap: 6px; justify-items: start; font: 15px/1.5 var(--ui-font); color: var(--ui-fg); }.ui-read-more-btn { display: inline-flex; align-items: center; gap: 4px; margin: 0; padding: 0; border: 0; background: none; border-radius: 3px; color: var(--ui-accent); font: 500 14px/1.4 var(--ui-font); cursor: pointer; }.ui-read-more-btn:hover { text-decoration: underline; text-underline-offset: 2px; }.ui-read-more-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-read-more-btn svg { width: 12px; height: 12px; transition: transform .15s; }.ui-read-more-open .ui-read-more-btn svg { transform: rotate(180deg); }@media (prefers-reduced-motion: reduce) { .ui-read-more-btn svg { transition: none; } }`component ReadMore(lines: int = 3, more: str = "Read more", less: str = "Show less", open: bool = false, id: str = "ui-read-more", on_toggle = null, class: str? = null, children) { style global read_more_css state shown = open state needed = true // false once the browser finds the text fits in its lines <div class={ui_cls(["ui-read-more", shown && "ui-read-more-open", class])} @mount={(el) => { let t = el.querySelector(".ui-read-more-text") if !shown && t != null && t.scrollHeight <= t.clientHeight + 1 { needed = false } }}> <LineClamp lines={shown ? 0 : lines} id={id} class="ui-read-more-text">{children}</LineClamp> {needed && <button.ui-read-more-btn type="button" aria-expanded={shown ? "true" : "false"} aria-controls={id} @click={() => { shown = !shown on_toggle && on_toggle(shown) }}> {shown ? less : more} <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6l4.5 4.5L12.5 6"/></svg> </button>} </div>}Related
Thread 0 posts
On ReadMore: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.