[dev-overlay] expandable error message container (#76297)

When the error message exceeds 200px in height, we want to collapse the
part above 200px, and show a button to expand it.

[CleanShot 2025-02-20 at 16.46.16.mp4 <span
class="graphite__hidden">(uploaded via Graphite)</span> <img
class="graphite__hidden"
src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/rKSEEwxbNzdFs9t0yyxN/ad657511-2c09-4180-85db-be7325f45d2f.mp4"
/>](https://app.graphite.dev/media/video/rKSEEwxbNzdFs9t0yyxN/ad657511-2c09-4180-85db-be7325f45d2f.mp4)

Closes NDX-743
This commit is contained in:
Jude Gao
2025-02-20 17:29:20 -05:00
committed by GitHub
parent d55c00d842
commit cb7bcf3ccc
2 changed files with 84 additions and 6 deletions
@@ -34,6 +34,15 @@ export const LongString: Story = {
},
}
export const SuperLongString: Story = {
args: {
errorMessage:
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Praesentium molestiae, perspiciatis earum labore repudiandae aliquid atque soluta ullam. Quidem provident non at. Reprehenderit similique harum recusandae id est dolore temporibus!\n'.repeat(
30
),
},
}
export const ReactNode: Story = {
args: {
errorMessage: (
@@ -1,3 +1,4 @@
import { useState, useRef, useLayoutEffect } from 'react'
import { noop as css } from '../../../helpers/noop-template'
export type ErrorMessageType = React.ReactNode
@@ -7,17 +8,47 @@ type ErrorMessageProps = {
}
export function ErrorMessage({ errorMessage }: ErrorMessageProps) {
const [isExpanded, setIsExpanded] = useState(false)
const [shouldTruncate, setShouldTruncate] = useState(false)
const messageRef = useRef<HTMLParagraphElement>(null)
useLayoutEffect(() => {
if (messageRef.current) {
setShouldTruncate(messageRef.current.scrollHeight > 200)
}
}, [errorMessage])
return (
<p
id="nextjs__container_errors_desc"
className="nextjs__container_errors_desc"
>
{errorMessage}
</p>
<div className="nextjs__container_errors_wrapper">
<p
ref={messageRef}
id="nextjs__container_errors_desc"
className={`nextjs__container_errors_desc ${shouldTruncate && !isExpanded ? 'truncated' : ''}`}
>
{errorMessage}
</p>
{shouldTruncate && !isExpanded && (
<>
<div className="nextjs__container_errors_gradient_overlay" />
<button
onClick={() => setIsExpanded(true)}
className="nextjs__container_errors_expand_button"
aria-expanded={isExpanded}
aria-controls="nextjs__container_errors_desc"
>
Show More
</button>
</>
)}
</div>
)
}
export const styles = css`
.nextjs__container_errors_wrapper {
position: relative;
}
.nextjs__container_errors_desc {
margin: 0;
margin-left: var(--size-1);
@@ -29,4 +60,42 @@ export const styles = css`
overflow-wrap: break-word;
white-space: pre-wrap;
}
.nextjs__container_errors_desc.truncated {
max-height: 200px;
overflow: hidden;
}
.nextjs__container_errors_gradient_overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 85px;
background: linear-gradient(
180deg,
rgba(250, 250, 250, 0) 0%,
var(--color-background-200) 100%
);
}
.nextjs__container_errors_expand_button {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
padding: 6px 8px;
background: var(--color-background-100);
border: 1px solid var(--color-gray-alpha-400);
border-radius: 999px;
box-shadow:
0px 2px 2px var(--color-gray-alpha-100),
0px 8px 8px -8px var(--color-gray-alpha-100);
font-size: 13px;
cursor: pointer;
color: var(--color-gray-900);
font-weight: 500;
}
`