addons/components/ErrorNotice.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {ReactNode} from 'react';
b69ab319
b69ab3110import React, {Component, useState} from 'react';
b69ab3111import {Icon} from './Icon';
b69ab3112import type {Placement} from './Tooltip';
b69ab3113import {Tooltip} from './Tooltip';
b69ab3114
b69ab3115import './error-notice.css';
b69ab3116
b69ab3117export function ErrorNotice({
b69ab3118 title,
b69ab3119 description,
b69ab3120 details,
b69ab3121 error,
b69ab3122 buttons,
b69ab3123 startExpanded,
b69ab3124}: {
b69ab3125 title: React.ReactNode;
b69ab3126 description?: React.ReactNode;
b69ab3127 /** Hidden details shown when expanded */
b69ab3128 details?: React.ReactNode;
b69ab3129 error?: Error;
b69ab3130 buttons?: Array<React.ReactNode>;
b69ab3131 startExpanded?: boolean;
b69ab3132}) {
b69ab3133 const [isExpanded, setIsExpanded] = useState(startExpanded === true);
b69ab3134 return (
b69ab3135 <div className="error-notice" onClick={() => setIsExpanded(e => !e)}>
b69ab3136 <div className="error-notice-left">
b69ab3137 <div className="error-notice-arrow">
b69ab3138 <Icon icon={isExpanded ? 'triangle-down' : 'triangle-right'} />
b69ab3139 </div>
b69ab3140 <div className="error-notice-content">
b69ab3141 <span className="error-notice-title">{title}</span>
b69ab3142 <span className="error-notice-byline">{description ?? error?.message}</span>
b69ab3143 {isExpanded ? (
b69ab3144 <div
b69ab3145 className="error-notice-details"
b69ab3146 onClick={e => {
b69ab3147 // don't close the notice when clicking/text selecting the details
b69ab3148 e.stopPropagation();
b69ab3149 }}>
b69ab3150 {details}
b69ab3151 {error != null && (
b69ab3152 <span className="error-notice-stack-trace">{error.stack ?? error.message}</span>
b69ab3153 )}
b69ab3154 </div>
b69ab3155 ) : null}
b69ab3156 </div>
b69ab3157 </div>
b69ab3158 {buttons ? <div className="error-notice-buttons">{buttons}</div> : null}
b69ab3159 </div>
b69ab3160 );
b69ab3161}
b69ab3162
b69ab3163type Props = {
b69ab3164 children: React.ReactNode;
b69ab3165};
b69ab3166
b69ab3167type State = {error: Error | null};
b69ab3168
b69ab3169export class ErrorBoundary extends Component<Props, State> {
b69ab3170 constructor(props: Props) {
b69ab3171 super(props);
b69ab3172 this.state = {error: null};
b69ab3173 }
b69ab3174
b69ab3175 static getDerivedStateFromError(error: Error) {
b69ab3176 return {error};
b69ab3177 }
b69ab3178
b69ab3179 render() {
b69ab3180 if (this.state.error != null) {
b69ab3181 return <ErrorNotice title="Something went wrong" error={this.state.error} />;
b69ab3182 }
b69ab3183
b69ab3184 return this.props.children;
b69ab3185 }
b69ab3186}
b69ab3187
b69ab3188/**
b69ab3189 * One-line error message, which shows the full ErrorNotice in a tooltip on hover.
b69ab3190 */
b69ab3191export function InlineErrorBadge({
b69ab3192 children,
b69ab3193 error,
b69ab3194 title,
b69ab3195 placement,
b69ab3196}: {
b69ab3197 children: ReactNode;
b69ab3198 error: Error;
b69ab3199 title?: ReactNode;
b69ab31100 placement?: Placement;
b69ab31101}) {
b69ab31102 return (
b69ab31103 <div className="inline-error-badge">
b69ab31104 <Tooltip component={TooltipErrorDetails(error, title)} placement={placement}>
b69ab31105 <Icon icon="error" slot="start" />
b69ab31106 <span>{children}</span>
b69ab31107 </Tooltip>
b69ab31108 </div>
b69ab31109 );
b69ab31110}
b69ab31111
b69ab31112function TooltipErrorDetails(error: Error, title?: ReactNode) {
b69ab31113 return function Component() {
b69ab31114 return (
b69ab31115 <div className="inline-error-tooltip">
b69ab31116 <ErrorNotice title={title ?? error.message} error={error} startExpanded />
b69ab31117 </div>
b69ab31118 );
b69ab31119 };
b69ab31120}