feat(ClampedText): N-line clamp with fade and Show more toggle - #398
feat(ClampedText): N-line clamp with fade and Show more toggle#398ebellamy-bh wants to merge 3 commits into
Conversation
Ported from waggleline's free-text field renderer: clamps to 2-8 lines with a surface-matched fade gradient, skips the toggle under a character threshold so short values stay flat, and exposes toggle labels for i18n. Hardcoded grays/cyan become semantic tokens.
There was a problem hiding this comment.
Pull request overview
Adds a new ClampedText component to the design system to collapse long free-text fields to a configurable number of lines with a fade edge and a “Show more / Show less” disclosure.
Changes:
- Introduces
ClampedTextcomponent implementation, Storybook stories, and unit tests. - Exports
ClampedTextfrom the package public API (src/index.ts) and adds a tsup entrypoint for component build output.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tsup.config.ts | Adds ClampedText to tsup entrypoints so it’s built/published. |
| src/index.ts | Re-exports ClampedText from the package root. |
| src/components/ClampedText/index.ts | Barrel export for ClampedText and its props. |
| src/components/ClampedText/ClampedText.tsx | Implements the N-line clamp UI with fade + toggle. |
| src/components/ClampedText/ClampedText.test.tsx | Adds unit tests covering clamp/toggle behavior and props like threshold/labels/fade. |
| src/components/ClampedText/ClampedText.stories.tsx | Adds Storybook stories demonstrating card/page usage and short-text behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Deploying ui with
|
| Latest commit: |
28f471d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://61fe54be.ui-6d0.pages.dev |
| Branch Preview URL: | https://feat-clamped-text.ui-6d0.pages.dev |
… lines 7, TW3 clamp support - The short-text branch now spreads ...props and attaches the forwarded ref (component retyped to HTMLElement since it renders span or div) - lines union gains 7 so the documented 2–8 range is real - Preset twins extend theme.lineClamp with 7/8 and safelist all clamp + fade classes so Tailwind 3 consumers generate them (TW4 accepts any integer bare value)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/components/ClampedText/ClampedText.tsx:70
- This component renders a
<span>whentext.length <= thresholdbut a<div>when clamped. That means the DOM structure and default display semantics change at runtime based on text length (and can produce invalid HTML if used inside phrasing-only contexts like<p>). Consider rendering a consistent root element (e.g., always aspanwithblockclass when clamped, or anasprop).
if (text.length <= threshold) {
return (
<span
ref={ref as React.ForwardedRef<HTMLSpanElement>}
className={cn('break-words whitespace-pre-wrap', className)}
{...props}
src/components/ClampedText/ClampedText.tsx:103
- The toggle sets
aria-expanded, but it doesn’t identify what region it controls (noaria-controls). Several disclosure-style components in this codebase wirearia-controlsto the controlled region id (e.g.,CollapsibleTrigger), which improves screen reader context. Consider adding a stable id to the clamped text region and referencing it from the button.
<button
type="button"
aria-expanded={open}
onClick={(e) => {
…ists, story options include 7
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/components/ClampedText/ClampedText.tsx:6
ClampedTextPropsinheritschildrenfromReact.HTMLAttributes<HTMLElement>, but the component ignoreschildrenand always renderstext. Allowingchildrenhere makes the API misleading and can silently drop user-provided content; it also makes it easier to accidentally pass props that conflict with rendered children.
Consider omitting children from the inherited attributes, consistent with other data-driven components (e.g. AddressProps uses Omit<...,'children'>).
export interface ClampedTextProps extends React.HTMLAttributes<HTMLElement> {
Ports waggleline's free-text clamp into the design system — long messages, notes, and descriptions collapse to N lines instead of pushing a detail panel's scroll a mile down.
What it does
aria-expanded)thresholdcharacter count (default 280) it renders inline with no togglefadeClassNamematches the fade to the surface behind the text (from-carddefault,from-backgroundfor page surfaces)Screenshots
A long call note clamped to four lines inside a card:
Provenance
Port of
waggleline/app/imports/ui/components/ClampedText.tsx(used by its form-details renderer for 2,000-character free-text fields); hardcoded gray/cyan → semantic tokens, staticline-clamp-*map kept literal for Tailwind scanning.Testing
typecheck/lint/format/rtl:scanclean; combined batch suite 651/651