Skip to content

Commit 65ef1a7

Browse files
authored
feat: Create an ErrorView that can be used to display errors (#1965)
- Added to the StyleGuide as well - Displays errors in a textarea, with a copy button and an expand button
1 parent c6bcc15 commit 65ef1a7

9 files changed

Lines changed: 238 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/* eslint-disable react/jsx-props-no-spreading */
2+
/* eslint no-alert: "off" */
3+
import React, { CSSProperties } from 'react';
4+
import { ErrorView } from '@deephaven/components';
5+
import { sampleSectionIdAndClasses } from './utils';
6+
7+
function ErrorViews(): React.ReactElement {
8+
const columnStyle: CSSProperties = {
9+
maxHeight: 500,
10+
display: 'flex',
11+
flexDirection: 'column',
12+
maxWidth: 400,
13+
};
14+
15+
const shortErrorMessage = 'This is a short error message';
16+
const midErrorMessage = 'Mid length error message\n'.repeat(10);
17+
const longErrorMessage = 'Really long error message\n'.repeat(100);
18+
19+
const midErrorType = 'MidError';
20+
const longErrorType = 'SuperLongErrorMessageType';
21+
22+
return (
23+
<div {...sampleSectionIdAndClasses('error-views')}>
24+
<h2 className="ui-title" title="Display error messages easily">
25+
Error Views
26+
</h2>
27+
<h3>Expandable</h3>
28+
<div className="row" style={{ maxHeight: 500 }}>
29+
<div className="col" style={columnStyle}>
30+
<ErrorView message={shortErrorMessage} />
31+
</div>
32+
<div className="col" style={columnStyle}>
33+
<ErrorView message={midErrorMessage} type={midErrorType} />
34+
</div>
35+
<div className="col" style={columnStyle}>
36+
<ErrorView message={longErrorMessage} type={longErrorType} />
37+
</div>
38+
</div>
39+
<h3>Always expanded</h3>
40+
<div className="row" style={{ maxHeight: 500 }}>
41+
<div className="col" style={columnStyle}>
42+
<ErrorView message={shortErrorMessage} isExpanded />
43+
</div>
44+
<div className="col" style={columnStyle}>
45+
<ErrorView message={midErrorMessage} type={midErrorType} isExpanded />
46+
</div>
47+
<div className="col" style={columnStyle}>
48+
<ErrorView
49+
message={longErrorMessage}
50+
type={longErrorType}
51+
isExpanded
52+
/>
53+
</div>
54+
</div>
55+
</div>
56+
);
57+
}
58+
59+
export default ErrorViews;

packages/code-studio/src/styleguide/StyleGuide.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { RandomAreaPlotAnimation } from './RandomAreaPlotAnimation';
3737
import SpectrumComparison from './SpectrumComparison';
3838
import Pickers from './Pickers';
3939
import ListViews from './ListViews';
40+
import ErrorViews from './ErrorViews';
4041

4142
const stickyProps = {
4243
position: 'sticky',
@@ -133,6 +134,7 @@ function StyleGuide(): React.ReactElement {
133134

134135
<SampleMenuCategory data-menu-category="Spectrum Comparison" />
135136
<SpectrumComparison />
137+
<ErrorViews />
136138
</div>
137139
</div>
138140
);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
@import '../scss/custom.scss';
2+
3+
.error-view {
4+
position: relative;
5+
color: $danger;
6+
border-radius: $border-radius;
7+
background-color: negative-opacity($exception-transparency);
8+
display: flex;
9+
flex-direction: column;
10+
flex-grow: 0;
11+
font-family: $font-family-monospace;
12+
transition: all $transition ease-in-out;
13+
max-height: 150px;
14+
15+
&.expanded {
16+
max-height: 100%;
17+
}
18+
19+
.error-view-header {
20+
display: flex;
21+
flex-direction: row;
22+
justify-content: space-between;
23+
text-wrap: nowrap;
24+
width: 100%;
25+
26+
.error-view-header-text {
27+
display: flex;
28+
flex-direction: row;
29+
align-items: center;
30+
padding-left: $spacer;
31+
padding-right: $spacer;
32+
font-weight: bold;
33+
flex-shrink: 1;
34+
overflow: hidden;
35+
white-space: nowrap;
36+
37+
span {
38+
flex-shrink: 1;
39+
overflow: hidden;
40+
white-space: nowrap;
41+
text-overflow: ellipsis;
42+
padding-left: $spacer-1;
43+
}
44+
}
45+
46+
.error-view-buttons {
47+
display: flex;
48+
flex-direction: row;
49+
gap: 1px;
50+
overflow: hidden;
51+
border-radius: 0 $border-radius 0 0;
52+
flex-shrink: 0;
53+
54+
.btn-danger {
55+
border-radius: 0;
56+
color: var(--dh-color-contrast-dark);
57+
opacity: 0.8;
58+
padding: $spacer-1;
59+
&:active {
60+
color: var(--dh-color-contrast-dark);
61+
}
62+
}
63+
64+
.error-view-copy-button {
65+
min-width: 3rem;
66+
}
67+
}
68+
}
69+
70+
.error-view-text {
71+
width: 100%;
72+
padding: $spacer;
73+
margin-bottom: 0;
74+
color: $danger;
75+
background-color: transparent;
76+
border: 0;
77+
resize: none;
78+
outline: none;
79+
white-space: pre;
80+
flex-grow: 1;
81+
overflow: auto;
82+
}
83+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
2+
import classNames from 'classnames';
3+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4+
import { vsDiffAdded, vsDiffRemoved, vsWarning } from '@deephaven/icons';
5+
import {
6+
useDebouncedCallback,
7+
useResizeObserver,
8+
} from '@deephaven/react-hooks';
9+
import CopyButton from './CopyButton';
10+
import Button from './Button';
11+
import './ErrorView.scss';
12+
13+
export type ErrorViewerProps = {
14+
/** The message to display in the error view */
15+
message: string;
16+
17+
/** Set to true if you want the error view to display expanded. Will not show the Show More/Less buttons if true. Defaults to false. */
18+
isExpanded?: boolean;
19+
20+
/** The type of error message to display in the header. Defaults to Error. */
21+
type?: string;
22+
};
23+
24+
/**
25+
* Component that displays an error message in a textarea so user can scroll and a copy button.
26+
*/
27+
function ErrorView({
28+
message,
29+
isExpanded: isExpandedProp = false,
30+
type = 'Error',
31+
}: ErrorViewerProps): JSX.Element {
32+
const [isExpandable, setIsExpandable] = useState(false);
33+
const [isExpanded, setIsExpanded] = useState(false);
34+
const viewRef = useRef<HTMLDivElement>(null);
35+
const textRef = useRef<HTMLPreElement>(null);
36+
37+
const handleResize = useCallback(() => {
38+
if (isExpanded || isExpandedProp || textRef.current == null) {
39+
return;
40+
}
41+
const newIsExpandable =
42+
textRef.current.scrollHeight > textRef.current.clientHeight;
43+
setIsExpandable(newIsExpandable);
44+
}, [isExpanded, isExpandedProp]);
45+
46+
const debouncedHandleResize = useDebouncedCallback(handleResize, 100);
47+
48+
useResizeObserver(viewRef.current, debouncedHandleResize);
49+
50+
useLayoutEffect(debouncedHandleResize, [debouncedHandleResize]);
51+
52+
return (
53+
<div
54+
className={classNames('error-view', {
55+
expanded: isExpanded || isExpandedProp,
56+
})}
57+
ref={viewRef}
58+
>
59+
<div className="error-view-header">
60+
<div className="error-view-header-text">
61+
<FontAwesomeIcon icon={vsWarning} />
62+
<span>{type}</span>
63+
</div>
64+
<div className="error-view-buttons">
65+
<CopyButton
66+
kind="danger"
67+
className="error-view-copy-button"
68+
tooltip="Copy exception contents"
69+
copy={`${type}: ${message}`.trim()}
70+
/>
71+
{(isExpandable || isExpanded) && !isExpandedProp && (
72+
<Button
73+
kind="danger"
74+
className="error-view-expand-button"
75+
onClick={() => {
76+
setIsExpanded(!isExpanded);
77+
}}
78+
icon={isExpanded ? vsDiffRemoved : vsDiffAdded}
79+
>
80+
{isExpanded ? 'Show Less' : 'Show More'}
81+
</Button>
82+
)}
83+
</div>
84+
</div>
85+
<pre className="error-view-text" ref={textRef}>
86+
{message}
87+
</pre>
88+
</div>
89+
);
90+
}
91+
92+
export default ErrorView;

packages/components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from './DraggableItemList';
2323
export { default as DragUtils } from './DragUtils';
2424
export { default as EditableItemList } from './EditableItemList';
2525
export * from './ErrorBoundary';
26+
export { default as ErrorView } from './ErrorView';
2627
export { default as HierarchicalCheckboxMenu } from './HierarchicalCheckboxMenu';
2728
export * from './HierarchicalCheckboxMenu';
2829
export * from './ItemList';

tests/styleguide.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const sampleSectionIds: string[] = [
4545
'sample-section-spectrum-forms',
4646
'sample-section-spectrum-overlays',
4747
'sample-section-spectrum-well',
48+
'sample-section-error-views',
4849
];
4950
const buttonSectionIds: string[] = [
5051
'sample-section-buttons-regular',
88.2 KB
Loading
150 KB
Loading
81.1 KB
Loading

0 commit comments

Comments
 (0)