diff --git a/packages/code-studio/src/styleguide/ErrorViews.tsx b/packages/code-studio/src/styleguide/ErrorViews.tsx new file mode 100644 index 000000000..08ec59fc0 --- /dev/null +++ b/packages/code-studio/src/styleguide/ErrorViews.tsx @@ -0,0 +1,59 @@ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint no-alert: "off" */ +import React, { CSSProperties } from 'react'; +import { ErrorView } from '@deephaven/components'; +import { sampleSectionIdAndClasses } from './utils'; + +function ErrorViews(): React.ReactElement { + const columnStyle: CSSProperties = { + maxHeight: 500, + display: 'flex', + flexDirection: 'column', + maxWidth: 400, + }; + + const shortErrorMessage = 'This is a short error message'; + const midErrorMessage = 'Mid length error message\n'.repeat(10); + const longErrorMessage = 'Really long error message\n'.repeat(100); + + const midErrorType = 'MidError'; + const longErrorType = 'SuperLongErrorMessageType'; + + return ( +
+

+ Error Views +

+

Expandable

+
+
+ +
+
+ +
+
+ +
+
+

Always expanded

+
+
+ +
+
+ +
+
+ +
+
+
+ ); +} + +export default ErrorViews; diff --git a/packages/code-studio/src/styleguide/StyleGuide.tsx b/packages/code-studio/src/styleguide/StyleGuide.tsx index 72c368977..d7c33e1ae 100644 --- a/packages/code-studio/src/styleguide/StyleGuide.tsx +++ b/packages/code-studio/src/styleguide/StyleGuide.tsx @@ -37,6 +37,7 @@ import { RandomAreaPlotAnimation } from './RandomAreaPlotAnimation'; import SpectrumComparison from './SpectrumComparison'; import Pickers from './Pickers'; import ListViews from './ListViews'; +import ErrorViews from './ErrorViews'; const stickyProps = { position: 'sticky', @@ -133,6 +134,7 @@ function StyleGuide(): React.ReactElement { + ); diff --git a/packages/components/src/ErrorView.scss b/packages/components/src/ErrorView.scss new file mode 100644 index 000000000..c0c290d69 --- /dev/null +++ b/packages/components/src/ErrorView.scss @@ -0,0 +1,83 @@ +@import '../scss/custom.scss'; + +.error-view { + position: relative; + color: $danger; + border-radius: $border-radius; + background-color: negative-opacity($exception-transparency); + display: flex; + flex-direction: column; + flex-grow: 0; + font-family: $font-family-monospace; + transition: all $transition ease-in-out; + max-height: 150px; + + &.expanded { + max-height: 100%; + } + + .error-view-header { + display: flex; + flex-direction: row; + justify-content: space-between; + text-wrap: nowrap; + width: 100%; + + .error-view-header-text { + display: flex; + flex-direction: row; + align-items: center; + padding-left: $spacer; + padding-right: $spacer; + font-weight: bold; + flex-shrink: 1; + overflow: hidden; + white-space: nowrap; + + span { + flex-shrink: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding-left: $spacer-1; + } + } + + .error-view-buttons { + display: flex; + flex-direction: row; + gap: 1px; + overflow: hidden; + border-radius: 0 $border-radius 0 0; + flex-shrink: 0; + + .btn-danger { + border-radius: 0; + color: var(--dh-color-contrast-dark); + opacity: 0.8; + padding: $spacer-1; + &:active { + color: var(--dh-color-contrast-dark); + } + } + + .error-view-copy-button { + min-width: 3rem; + } + } + } + + .error-view-text { + width: 100%; + padding: $spacer; + margin-bottom: 0; + color: $danger; + background-color: transparent; + border: 0; + resize: none; + outline: none; + white-space: pre; + flex-grow: 1; + overflow: auto; + } +} diff --git a/packages/components/src/ErrorView.tsx b/packages/components/src/ErrorView.tsx new file mode 100644 index 000000000..d537de166 --- /dev/null +++ b/packages/components/src/ErrorView.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; +import classNames from 'classnames'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { vsDiffAdded, vsDiffRemoved, vsWarning } from '@deephaven/icons'; +import { + useDebouncedCallback, + useResizeObserver, +} from '@deephaven/react-hooks'; +import CopyButton from './CopyButton'; +import Button from './Button'; +import './ErrorView.scss'; + +export type ErrorViewerProps = { + /** The message to display in the error view */ + message: string; + + /** 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. */ + isExpanded?: boolean; + + /** The type of error message to display in the header. Defaults to Error. */ + type?: string; +}; + +/** + * Component that displays an error message in a textarea so user can scroll and a copy button. + */ +function ErrorView({ + message, + isExpanded: isExpandedProp = false, + type = 'Error', +}: ErrorViewerProps): JSX.Element { + const [isExpandable, setIsExpandable] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + const viewRef = useRef(null); + const textRef = useRef(null); + + const handleResize = useCallback(() => { + if (isExpanded || isExpandedProp || textRef.current == null) { + return; + } + const newIsExpandable = + textRef.current.scrollHeight > textRef.current.clientHeight; + setIsExpandable(newIsExpandable); + }, [isExpanded, isExpandedProp]); + + const debouncedHandleResize = useDebouncedCallback(handleResize, 100); + + useResizeObserver(viewRef.current, debouncedHandleResize); + + useLayoutEffect(debouncedHandleResize, [debouncedHandleResize]); + + return ( +
+
+
+ + {type} +
+
+ + {(isExpandable || isExpanded) && !isExpandedProp && ( + + )} +
+
+
+        {message}
+      
+
+ ); +} + +export default ErrorView; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 35f9513f3..00147bf7a 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -23,6 +23,7 @@ export * from './DraggableItemList'; export { default as DragUtils } from './DragUtils'; export { default as EditableItemList } from './EditableItemList'; export * from './ErrorBoundary'; +export { default as ErrorView } from './ErrorView'; export { default as HierarchicalCheckboxMenu } from './HierarchicalCheckboxMenu'; export * from './HierarchicalCheckboxMenu'; export * from './ItemList'; diff --git a/tests/styleguide.spec.ts b/tests/styleguide.spec.ts index 5c15fe69e..e0eee5247 100644 --- a/tests/styleguide.spec.ts +++ b/tests/styleguide.spec.ts @@ -45,6 +45,7 @@ const sampleSectionIds: string[] = [ 'sample-section-spectrum-forms', 'sample-section-spectrum-overlays', 'sample-section-spectrum-well', + 'sample-section-error-views', ]; const buttonSectionIds: string[] = [ 'sample-section-buttons-regular', diff --git a/tests/styleguide.spec.ts-snapshots/error-views-chromium-linux.png b/tests/styleguide.spec.ts-snapshots/error-views-chromium-linux.png new file mode 100644 index 000000000..c726cbddf Binary files /dev/null and b/tests/styleguide.spec.ts-snapshots/error-views-chromium-linux.png differ diff --git a/tests/styleguide.spec.ts-snapshots/error-views-firefox-linux.png b/tests/styleguide.spec.ts-snapshots/error-views-firefox-linux.png new file mode 100644 index 000000000..cd847c32e Binary files /dev/null and b/tests/styleguide.spec.ts-snapshots/error-views-firefox-linux.png differ diff --git a/tests/styleguide.spec.ts-snapshots/error-views-webkit-linux.png b/tests/styleguide.spec.ts-snapshots/error-views-webkit-linux.png new file mode 100644 index 000000000..441af5ec6 Binary files /dev/null and b/tests/styleguide.spec.ts-snapshots/error-views-webkit-linux.png differ