@@ -5,21 +5,48 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'preact/hooks';
55
66import { observeElementSize } from '../utils/observe-element-size' ;
77
8- type InlineControlsProps = {
8+ type InlineControlExcerptProps = {
9+ /**
10+ * The excerpt provides internal controls to expand and collapse
11+ * the content.
12+ */
13+ inlineControl : true ;
14+
15+ /**
16+ * Text on inline control when clicking it will expand the content.
17+ * Defaults to 'More'.
18+ */
19+ inlineControlExpandText ?: string ;
20+
21+ /**
22+ * Text on inline control when clicking it will collapse the content.
23+ * Defaults to 'Less'.
24+ */
25+ inlineControlCollapseText ?: string ;
26+
27+ /** Additional styles to pass to the inline controls element. */
28+ inlineControlStyle ?: JSX . CSSProperties ;
29+ /** Additional CSS classes to pass to the inline controls element. */
30+ inlineControlClasses ?: string | string [ ] ;
31+ } ;
32+
33+ type InlineControlProps = InlineControlExcerptProps & {
934 isCollapsed : boolean ;
1035 setCollapsed : ( collapsed : boolean ) => void ;
11- linkStyle : JSX . CSSProperties ;
1236} ;
1337
1438/**
1539 * An optional toggle link at the bottom of an excerpt which controls whether
1640 * it is expanded or collapsed.
1741 */
18- function InlineControls ( {
42+ function InlineControl ( {
1943 isCollapsed,
2044 setCollapsed,
21- linkStyle,
22- } : InlineControlsProps ) {
45+ inlineControlExpandText = 'More' ,
46+ inlineControlCollapseText = 'Less' ,
47+ inlineControlStyle,
48+ inlineControlClasses,
49+ } : InlineControlProps ) {
2350 return (
2451 < div
2552 className = { classnames (
@@ -42,40 +69,30 @@ function InlineControls({
4269 onClick = { ( ) => setCollapsed ( ! isCollapsed ) }
4370 expanded = { ! isCollapsed }
4471 title = "Toggle visibility of full excerpt text"
45- style = { linkStyle }
72+ style = { inlineControlStyle }
73+ classes = { inlineControlClasses }
4674 underline = "always"
4775 inline
4876 >
49- { isCollapsed ? 'More' : 'Less' }
77+ { isCollapsed ? inlineControlExpandText : inlineControlCollapseText }
5078 </ LinkButton >
5179 </ div >
5280 </ div >
5381 ) ;
5482}
5583
56- const noop = ( ) => { } ;
57-
58- export type ExcerptProps = {
59- children ?: ComponentChildren ;
60-
84+ type ExternalControlExcerptProps = {
6185 /**
62- * If `true`, the excerpt provides internal controls to expand and collapse
63- * the content. If `false`, the caller sets the collapsed state via the
64- * `collapse` prop. When using inline controls, the excerpt is initially
65- * collapsed.
86+ * The caller is responsible for providing their own collapse/expand control,
87+ * in combination with `collapsed` and `onToggleCollapsed` props.
6688 */
67- inlineControls ?: boolean ;
89+ inlineControl : false ;
90+ } ;
6891
69- /**
70- * If the content should be truncated if its height exceeds
71- * `collapsedHeight + overflowThreshold`. This prop is only used if
72- * `inlineControls` is false.
73- */
74- collapse ?: boolean ;
92+ export type ExcerptProps = {
93+ children ?: ComponentChildren ;
7594
76- /**
77- * Maximum height of the container, in pixels, when it is collapsed.
78- */
95+ /** Maximum height of the container, in pixels, when it is collapsed. */
7996 collapsedHeight : number ;
8097
8198 /**
@@ -85,44 +102,72 @@ export type ExcerptProps = {
85102 overflowThreshold ?: number ;
86103
87104 /**
88- * Called when the content height exceeds or falls below
105+ * Whether a shadow is drawn at the bottom of collapsed content, to hint that
106+ * content is being hidden.
107+ *
108+ * The shadow area can be clicked to expand the container so the content is
109+ * fully visible.
110+ *
111+ * Defaults to `false` for excerpts with inline control, and `true` for
112+ * excerpts with external control.
113+ */
114+ shadow ?: boolean ;
115+
116+ /**
117+ * If the content should be truncated if its height exceeds
89118 * `collapsedHeight + overflowThreshold`.
119+ *
120+ * Use this prop in combination with `onToggleCollapsed` to make this excerpt
121+ * a controlled component.
122+ *
123+ * Defaults to `true`.
90124 */
91- onCollapsibleChanged ?: ( isCollapsible : boolean ) => void ;
125+ collapsed ?: boolean ;
92126
93127 /**
94- * When `inlineControls` is `false`, this function is called when the user
95- * requests to expand the content by clicking a zone at the bottom of the
96- * container.
128+ * If this function is provided, it is called when the user requests to expand
129+ * the content by clicking the shadowed zone at the bottom of the container
130+ * (if `shadow` is `true`) or the inline control (if `inlineControl` is `true`)
97131 */
98132 onToggleCollapsed ?: ( collapsed : boolean ) => void ;
99133
100134 /**
101- * Additional styles to pass to the inline controls element.
102- * Ignored if inlineControls is `false `.
135+ * Called when the content height exceeds or falls below
136+ * `collapsedHeight + overflowThreshold `.
103137 */
104- inlineControlsLinkStyle ?: JSX . CSSProperties ;
105- } ;
138+ onCollapsibleChanged ?: ( isCollapsible : boolean ) => void ;
139+ } & ( InlineControlExcerptProps | ExternalControlExcerptProps ) ;
106140
107141/**
108142 * A container which truncates its content when they exceed a specified height.
109143 *
110144 * The collapsed state of the container can be handled either via internal
111- * controls (if `inlineControls` is `true`) or by the caller using the
112- * `collapse` prop .
145+ * controls (if `inlineControls` is `true`) or by the caller using a custom
146+ * control .
113147 */
114148export default function Excerpt ( {
115149 children,
116- collapse = false ,
150+ collapsed = true ,
117151 collapsedHeight,
118- inlineControls = true ,
119- onCollapsibleChanged = noop ,
120- onToggleCollapsed = noop ,
152+ onCollapsibleChanged,
153+ onToggleCollapsed,
121154 overflowThreshold = 0 ,
122- inlineControlsLinkStyle = { } ,
155+ shadow,
156+ ...rest
123157} : ExcerptProps ) {
158+ const inlineControl = rest . inlineControl ;
159+ const withShadow = shadow ?? ! inlineControl ;
124160 const [ collapsedByInlineControls , setCollapsedByInlineControls ] =
125- useState ( true ) ;
161+ useState ( collapsed ) ;
162+ const setCollapsed = useCallback (
163+ ( collapsed : boolean ) => {
164+ if ( inlineControl ) {
165+ setCollapsedByInlineControls ( collapsed ) ;
166+ }
167+ onToggleCollapsed ?.( collapsed ) ;
168+ } ,
169+ [ inlineControl , onToggleCollapsed ] ,
170+ ) ;
126171
127172 const contentElement = useRef < HTMLDivElement | null > ( null ) ;
128173
@@ -138,7 +183,7 @@ export default function Excerpt({
138183 // prettier-ignore
139184 const isCollapsible =
140185 newContentHeight > ( collapsedHeight + overflowThreshold ) ;
141- onCollapsibleChanged ( isCollapsible ) ;
186+ onCollapsibleChanged ?. ( isCollapsible ) ;
142187 } , [ collapsedHeight , onCollapsibleChanged , overflowThreshold ] ) ;
143188
144189 useLayoutEffect ( ( ) => {
@@ -154,19 +199,14 @@ export default function Excerpt({
154199 // expanding/collapsing the content.
155200 // prettier-ignore
156201 const isOverflowing = contentHeight > ( collapsedHeight + overflowThreshold ) ;
157- const isCollapsed = inlineControls ? collapsedByInlineControls : collapse ;
202+ const isCollapsed = inlineControl ? collapsedByInlineControls : collapsed ;
158203 const isExpandable = isOverflowing && isCollapsed ;
159204
160205 const contentStyle : Record < string , number > = { } ;
161206 if ( contentHeight !== 0 ) {
162207 contentStyle [ 'max-height' ] = isExpandable ? collapsedHeight : contentHeight ;
163208 }
164209
165- const setCollapsed = ( collapsed : boolean ) =>
166- inlineControls
167- ? setCollapsedByInlineControls ( collapsed )
168- : onToggleCollapsed ( collapsed ) ;
169-
170210 return (
171211 < div
172212 data-testid = "excerpt-container"
@@ -200,23 +240,23 @@ export default function Excerpt({
200240 'transition-[opacity] duration-150 ease-linear' ,
201241 'absolute w-full bottom-0 h-touch-minimum' ,
202242 {
203- // For expandable excerpts not using inlineControls , style this
204- // element with a shadow-like gradient
243+ // For expandable excerpts with shadow , style this element with a
244+ // shadow-like gradient
205245 'bg-gradient-to-b from-white/0 via-95% via-black/10 to-100% to-black/15' :
206- ! inlineControls && isExpandable ,
207- 'bg-none' : inlineControls ,
208- // Don't make this shadow visible OR clickable if there's nothing
246+ withShadow && isExpandable ,
247+ 'bg-none' : ! withShadow ,
248+ // Don't make the shadow visible OR clickable if there's nothing
209249 // to do here (the excerpt isn't expandable)
210250 'opacity-0 pointer-events-none' : ! isExpandable ,
211251 } ,
212252 ) }
213253 title = "Show the full excerpt"
214254 />
215- { isOverflowing && inlineControls && (
216- < InlineControls
255+ { isOverflowing && inlineControl && (
256+ < InlineControl
217257 isCollapsed = { collapsedByInlineControls }
218258 setCollapsed = { setCollapsed }
219- linkStyle = { inlineControlsLinkStyle }
259+ { ... rest }
220260 />
221261 ) }
222262 </ div >
0 commit comments