From 7a20bdc82f3855e5a295b4a1b393a818cb6b062c Mon Sep 17 00:00:00 2001 From: Sergei Garin Date: Fri, 7 Jun 2024 11:10:11 +0300 Subject: [PATCH] Refactors of Dialogs, Popover styles, button sizes and Text (#10199) In this PR: 1. Now button has proper sized icons, size of the button is also alighned with the grid 2. Migrate ResizableTextContentEditableInput to Text component 3. Migrate Dialog to variants API 4. Did another attemt to make text more reusable. Now instead of using paddings, we use ::before and ::after to make text better aligned inside a grid --- .../AriaComponents/Button/Button.tsx | 124 ++++++++++++++---- .../AriaComponents/Button/ButtonGroup.tsx | 9 +- .../AriaComponents/Dialog/Dialog.tsx | 21 +-- .../AriaComponents/Dialog/Popover.tsx | 36 ++--- .../components/AriaComponents/Dialog/types.ts | 1 - .../ResizableContentEditableInput.tsx | 15 ++- .../Inputs/ResizableInput/variants.ts | 20 ++- .../components/AriaComponents/Text/Text.tsx | 14 +- .../AriaComponents/Tooltip/Tooltip.tsx | 4 +- .../src/components/ErrorBoundary.tsx | 38 +++++- .../lib/dashboard/src/components/Page.tsx | 8 +- .../lib/dashboard/src/components/Result.tsx | 7 +- .../lib/dashboard/src/layouts/Chat.tsx | 2 +- .../lib/dashboard/src/layouts/UserBar.tsx | 4 +- .../src/pages/dashboard/Dashboard.tsx | 2 +- .../lib/dashboard/src/utilities/error.ts | 17 +++ 16 files changed, 233 insertions(+), 89 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx index 295b123dc9..6f7750d8f3 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx @@ -10,14 +10,16 @@ import * as ariaComponents from '#/components/AriaComponents' import Spinner, * as spinnerModule from '#/components/Spinner' import SvgMask from '#/components/SvgMask' +import * as text from '../Text' + // ============== // === Button === // ============== /** Props for a {@link Button}. */ export type ButtonProps = - | (BaseButtonProps & Omit & PropsWithoutHref) - | (BaseButtonProps & Omit & PropsWithHref) + | (BaseButtonProps & Omit & PropsWithoutHref) + | (BaseButtonProps & Omit & PropsWithHref) /** * Props for a button with an href. @@ -54,7 +56,7 @@ export interface BaseButtonProps extends Omit Promise | void - + readonly children?: React.ReactNode readonly testId?: string readonly formnovalidate?: boolean @@ -65,20 +67,62 @@ export const BUTTON_STYLES = twv.tv({ variants: { isDisabled: { true: 'disabled:opacity-50 disabled:cursor-not-allowed' }, isFocused: { - true: 'focus:outline-none focus-visible:outline focus-visible:outline-primary', + true: 'focus:outline-none focus-visible:outline focus-visible:outline-primary focus-visible:outline-offset-2', }, loading: { true: { base: 'cursor-wait' } }, fullWidth: { true: 'w-full' }, size: { - custom: '', - hero: 'px-8 py-4 text-lg font-bold', - large: 'px-6 py-3 text-base font-bold', - medium: 'px-4 py-2 text-sm font-bold', - small: 'px-3 pt-1 pb-[5px] text-xs font-medium', - xsmall: 'px-2 pt-1 pb-[5px] text-xs font-medium', - xxsmall: 'px-1.5 pt-1 pb-[5px] text-xs font-medium', + custom: { base: '', extraClickZone: 'after:inset-[-12px]' }, + hero: { base: 'px-8 py-4 text-lg font-bold', content: 'gap-[0.75em]' }, + large: { + base: 'px-[11px] py-[5px]', + content: 'gap-2', + text: text.TEXT_STYLE({ + variant: 'body', + color: 'custom', + weight: 'bold', + }), + extraClickZone: 'after:inset-[-6px]', + }, + medium: { + base: 'px-[9px] py-[3px]', + text: text.TEXT_STYLE({ + variant: 'body', + color: 'custom', + weight: 'bold', + }), + content: 'gap-2', + extraClickZone: 'after:inset-[-8px]', + }, + small: { + base: 'px-[7px] py-[1px]', + content: 'gap-1', + text: text.TEXT_STYLE({ + variant: 'body', + color: 'custom', + }), + extraClickZone: 'after:inset-[-10px]', + }, + xsmall: { + base: 'px-[5px] py-[1px]', + content: 'gap-1', + text: text.TEXT_STYLE({ + variant: 'body', + color: 'custom', + }), + extraClickZone: 'after:inset-[-12px]', + }, + xxsmall: { + base: 'px-[3px] py-[0px]', + content: 'gap-0.5', + text: text.TEXT_STYLE({ + variant: 'body', + color: 'custom', + }), + extraClickZone: 'after:inset-[-12px]', + }, }, - iconOnly: { true: '' }, + iconOnly: { true: { base: '', icon: 'w-full h-full' } }, rounded: { full: 'rounded-full', large: 'rounded-lg', @@ -91,17 +135,23 @@ export const BUTTON_STYLES = twv.tv({ }, variant: { custom: 'focus-visible:outline-offset-2', - link: 'inline-flex px-0 py-0 rounded-sm text-primary/50 underline hover:text-primary focus-visible:outline-offset-0', - primary: 'bg-primary text-white hover:bg-primary/70 focus-visible:outline-offset-2', - tertiary: 'bg-share text-white hover:bg-share/90 focus-visible:outline-offset-2', - cancel: 'bg-selected-frame opacity-80 hover:opacity-100 focus-visible:outline-offset-2', - delete: 'bg-delete text-white focus-visible:outline-offset-2', + link: { + base: 'inline-flex px-0 py-0 rounded-sm text-primary/50 underline hover:text-primary border-none', + icon: 'h-[1.25cap] mt-0.5', + }, + primary: 'bg-primary text-white hover:bg-primary/70', + tertiary: 'bg-share text-white hover:bg-share/90', + cancel: 'bg-white/50 hover:bg-white', + delete: + 'bg-danger/80 hover:bg-danger text-white focus-visible:outline-danger focus-visible:bg-danger', icon: { - base: 'opacity-70 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-offset-0', + base: 'opacity-80 hover:opacity-100 focus-visible:opacity-100', wrapper: 'w-full h-full', content: 'w-full h-full', - icon: 'w-fit h-fit', + extraClickZone: 'w-full h-full', }, + ghost: + 'opacity-80 hover:opacity-100 hover:bg-white focus-visible:opacity-100 focus-visible:bg-white', submit: 'bg-invite text-white opacity-80 hover:opacity-100 focus-visible:outline-offset-2', outline: 'border-primary/40 text-primary hover:border-primary focus-visible:outline-offset-2', }, @@ -114,11 +164,12 @@ export const BUTTON_STYLES = twv.tv({ }, }, slots: { - extraClickZone: 'flex relative after:inset-[-12px] after:absolute', + extraClickZone: 'flex relative after:absolute after:cursor-pointer', wrapper: 'relative block', loader: 'absolute inset-0 flex items-center justify-center', content: 'flex items-center gap-[0.5em]', - icon: 'h-[1.5em] flex-none', + text: '', + icon: 'h-[2cap] flex-none aspect-square', }, defaultVariants: { loading: false, @@ -130,12 +181,28 @@ export const BUTTON_STYLES = twv.tv({ showIconOnHover: false, }, compoundVariants: [ - { variant: 'icon', size: 'xxsmall', class: 'p-0.5 rounded-full', iconOnly: true }, - { variant: 'icon', size: 'xsmall', class: 'p-1 rounded-full', iconOnly: true }, - { variant: 'icon', size: 'small', class: 'p-1 rounded-full', iconOnly: true }, - { variant: 'icon', size: 'medium', class: 'p-2 rounded-full', iconOnly: true }, - { variant: 'icon', size: 'large', class: 'p-3 rounded-full', iconOnly: true }, - { variant: 'icon', size: 'hero', class: 'p-4 rounded-full', iconOnly: true }, + { variant: 'icon', isFocused: true, iconOnly: true, class: 'focus-visible:outline-offset-0' }, + { + variant: 'link', + isFocused: true, + class: 'focus-visible:outline-offset-1', + }, + { + variant: 'icon', + size: 'xxsmall', + class: 'aspect-square rounded-full w-4 p-0', + iconOnly: true, + }, + { + variant: 'icon', + size: 'xsmall', + class: 'aspect-square p-0 rounded-full w-5', + iconOnly: true, + }, + { size: 'small', class: 'aspect-square p-0 rounded-full w-6', iconOnly: true }, + { size: 'medium', class: 'aspect-square p-0 rounded-full w-8', iconOnly: true }, + { size: 'large', class: 'aspect-square p-0 rounded-full w-10', iconOnly: true }, + { size: 'hero', class: 'aspect-square p-0 rounded-full w-16', iconOnly: true }, { variant: 'link', size: 'xxsmall', class: 'font-medium' }, { variant: 'link', size: 'xsmall', class: 'font-medium' }, { variant: 'link', size: 'small', class: 'font-medium' }, @@ -230,6 +297,7 @@ export const Button = React.forwardRef(function Button( loader, extraClickZone, icon: iconClasses, + text: textClasses, } = BUTTON_STYLES({ isDisabled, loading: isLoading, @@ -260,7 +328,7 @@ export const Button = React.forwardRef(function Button( return ( <> {iconComponent} - <>{children} + {children} ) } diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/ButtonGroup.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/ButtonGroup.tsx index 4ab98c0651..4fa270b448 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/ButtonGroup.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/ButtonGroup.tsx @@ -14,12 +14,14 @@ const STYLES = twv.tv({ base: 'flex w-full flex-1 shrink-0', variants: { wrap: { true: 'flex-wrap' }, - direction: { column: 'flex-col justify-center', row: 'flex-row items-center' }, + direction: { column: 'flex-col', row: 'flex-row' }, gap: { custom: '', large: 'gap-3.5', medium: 'gap-2', small: 'gap-1.5', + xsmall: 'gap-1', + xxsmall: 'gap-0.5', none: 'gap-0', }, align: { @@ -31,6 +33,11 @@ const STYLES = twv.tv({ evenly: 'justify-evenly', }, }, + compoundVariants: [ + { direction: 'column', align: 'start', class: 'items-start' }, + { direction: 'column', align: 'center', class: 'items-center' }, + { direction: 'column', align: 'end', class: 'items-end' }, + ], }) /** diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx index 69306ef01f..088506984c 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx @@ -2,7 +2,6 @@ * Can be used to display alerts, confirmations, or other content. */ import * as React from 'react' -import clsx from 'clsx' import * as twv from 'tailwind-variants' import * as aria from '#/components/aria' @@ -48,10 +47,14 @@ const DIALOG_STYLES = twv.tv({ modal: 'w-full max-w-md min-h-[100px] max-h-[90vh]', fullscreen: 'w-full h-full max-w-full max-h-full bg-clip-border', }, + hideCloseButton: { true: { closeButton: 'hidden' } }, }, slots: { header: - 'sticky grid grid-cols-[1fr_auto_1fr] items-center border-b border-primary/10 px-3.5 py-2 text-primary', + 'sticky grid grid-cols-[1fr_auto_1fr] items-center border-b border-primary/10 px-3.5 pt-[3px] pb-0.5', + closeButton: 'col-start-1 col-end-1 mr-auto', + heading: 'col-start-2 col-end-2 my-0', + content: 'relative flex-auto overflow-y-auto p-3.5', }, }) @@ -83,7 +86,7 @@ export function Dialog(props: DialogProps) { const root = portal.useStrictPortalContext() const shouldRenderTitle = typeof title === 'string' - const dialogSlots = DIALOG_STYLES({ className, type, rounded }) + const dialogSlots = DIALOG_STYLES({ className, type, rounded, hideCloseButton }) utlities.useInteractOutside({ ref: dialogRef, @@ -151,23 +154,21 @@ export function Dialog(props: DialogProps) { {shouldRenderTitle && ( - {title} - + )} -
+
}> {typeof children === 'function' ? children(opts) : children} diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Popover.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Popover.tsx index 9e026784fd..2b335cc298 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Popover.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Popover.tsx @@ -31,7 +31,7 @@ export interface PopoverProps export const POPOVER_STYLES = twv.tv({ extend: variants.DIALOG_BACKGROUND, - base: 'shadow-2xl w-full', + base: 'shadow-md w-full overflow-clip', variants: { isEntering: { true: 'animate-in fade-in placement-bottom:slide-in-from-top-1 placement-top:slide-in-from-bottom-1 placement-left:slide-in-from-right-1 placement-right:slide-in-from-left-1 ease-out duration-200', @@ -40,11 +40,11 @@ export const POPOVER_STYLES = twv.tv({ true: 'animate-out fade-out placement-bottom:slide-out-to-top-1 placement-top:slide-out-to-bottom-1 placement-left:slide-out-to-right-1 placement-right:slide-out-to-left-1 ease-in duration-150', }, size: { - xsmall: { base: 'max-w-xs', content: 'p-2.5' }, - small: { base: 'max-w-sm', content: 'p-3.5' }, - medium: { base: 'max-w-md', content: 'p-3.5' }, - large: { base: 'max-w-lg', content: 'px-4 py-4' }, - hero: { base: 'max-w-xl', content: 'px-6 py-5' }, + xsmall: { base: 'max-w-xs', dialog: 'p-2.5' }, + small: { base: 'max-w-sm', dialog: 'p-3.5' }, + medium: { base: 'max-w-md', dialog: 'p-3.5' }, + large: { base: 'max-w-lg', dialog: 'px-4 py-4' }, + hero: { base: 'max-w-xl', dialog: 'px-6 py-5' }, }, rounded: { none: '', @@ -57,7 +57,7 @@ export const POPOVER_STYLES = twv.tv({ }, }, slots: { - content: 'flex-auto overflow-y-auto', + content: 'flex-auto overflow-y-auto max-h-[inherit]', }, defaultVariants: { rounded: 'xxlarge', size: 'small' }, }) @@ -106,20 +106,22 @@ export function Popover(props: PopoverProps) { > {opts => ( - + {({ close }) => { closeRef.current = close return ( -
- - - }> - {typeof children === 'function' ? children({ ...opts, close }) : children} - - - -
+ + + }> + {typeof children === 'function' ? children({ ...opts, close }) : children} + + + ) }}
diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts index f7bcbb63fd..7f367817b9 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts @@ -7,7 +7,6 @@ export interface DialogProps extends aria.DialogProps { * @default 'modal' */ readonly title?: string readonly isDismissable?: boolean - readonly hideCloseButton?: boolean readonly onOpenChange?: (isOpen: boolean) => void readonly isKeyboardDismissDisabled?: boolean readonly modalProps?: Pick diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx index b256328f4d..f2c226e651 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx @@ -8,6 +8,7 @@ import * as twv from 'tailwind-variants' import * as eventCallbackHooks from '#/hooks/eventCallbackHooks' import * as aria from '#/components/aria' +import * as ariaComponents from '#/components/AriaComponents' import * as mergeRefs from '#/utilities/mergeRefs' @@ -16,7 +17,7 @@ import * as varants from './variants' const CONTENT_EDITABLE_STYLES = twv.tv({ extend: varants.INPUT_STYLES, base: '', - slots: { placeholder: 'text-primary/25 absolute inset-0 pointer-events-none' }, + slots: { placeholder: 'opacity-50 absolute inset-0 pointer-events-none' }, }) /** @@ -106,22 +107,22 @@ export const ResizableContentEditableInput = React.forwardRef( }} /> - + {placeholder} - +
{description != null && ( - + {description} - + )}
{errorMessage != null && ( - + {errorMessage} - + )} ) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/variants.ts b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/variants.ts index f4360fd5d3..0291a5a2c3 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/variants.ts +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Inputs/ResizableInput/variants.ts @@ -6,15 +6,23 @@ import * as twv from 'tailwind-variants' +import * as text from '../../Text' + export const INPUT_STYLES = twv.tv({ - base: 'w-full overflow-hidden block cursor-text rounded-md border-2 border-primary/10 bg-transparent px-1.5 pb-1 pt-2 focus-within:border-primary/50 transition-colors duration-200', + base: 'w-full overflow-hidden block cursor-text rounded-md border-2 border-primary/10 bg-transparent px-1.5 pb-1 pt-1.5 focus-within:border-primary/50 transition-colors duration-200', variants: { isInvalid: { true: 'border-red-500/70 focus-within:border-red-500' } }, slots: { - inputContainer: 'block max-h-32 min-h-5 text-sm font-normal relative overflow-auto', - description: 'mt-1 block text-xs text-primary/40 select-none pointer-events-none', - error: 'block text-xs text-red-500', + inputContainer: text.TEXT_STYLE({ + className: 'block max-h-32 min-h-6 text-sm font-medium relative overflow-auto', + variant: 'body', + }), + description: 'block select-none pointer-events-none opacity-80', + error: 'block', textArea: 'block h-auto w-full max-h-full resize-none bg-transparent', - resizableSpan: - 'pointer-events-none invisible absolute block max-h-32 min-h-10 overflow-y-auto break-all text-sm', + resizableSpan: text.TEXT_STYLE({ + className: + 'pointer-events-none invisible absolute block max-h-32 min-h-10 overflow-y-auto break-all', + variant: 'body', + }), }, }) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Text/Text.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Text/Text.tsx index 888f3bddc1..6e6c1ef008 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Text/Text.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Text/Text.tsx @@ -24,7 +24,7 @@ export interface TextProps } export const TEXT_STYLE = twv.tv({ - base: 'inline-block', + base: 'inline-block flex-col before:block after:block before:flex-none after:flex-none before:w-full after:w-full', variants: { color: { custom: '', @@ -39,9 +39,9 @@ export const TEXT_STYLE = twv.tv({ // leading should always be after the text size to make sure it is not stripped by twMerge variant: { custom: '', - body: 'text-xs leading-[20px] pt-[1px] pb-[3px]', - h1: 'text-xl leading-[29px] pt-[2px] pb-[5px]', - subtitle: 'text-[13.5px] leading-[20px] pt-[1px] pb-[3px]', + body: 'text-xs leading-[20px] before:h-[1px] after:h-[3px]', + h1: 'text-xl leading-[29px] before:h-0.5 after:h-[5px]', + subtitle: 'text-[13.5px] leading-[20px] before:h-[1px] after:h-[3px]', }, weight: { custom: '', @@ -102,17 +102,17 @@ export const TEXT_STYLE = twv.tv({ { variant: 'h1', disableLineHeightCompensation: true, - class: 'pt-[unset] pb-[unset]', + class: 'before:h-[unset] after:h-[unset]', }, { variant: 'body', disableLineHeightCompensation: true, - class: 'pt-[unset] pb-[unset]', + class: 'before:h-[unset] after:h-[unset]', }, { variant: 'subtitle', disableLineHeightCompensation: true, - class: 'pt-[unset] pb-[unset]', + class: 'before:h-[unset] after:h-[unset]', }, ], }) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Tooltip/Tooltip.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Tooltip/Tooltip.tsx index d6538a40b0..9ff06363bf 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Tooltip/Tooltip.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Tooltip/Tooltip.tsx @@ -4,6 +4,8 @@ import * as twv from 'tailwind-variants' import * as aria from '#/components/aria' import * as portal from '#/components/Portal' +import * as text from '../Text' + // ================= // === Constants === // ================= @@ -18,7 +20,7 @@ export const TOOLTIP_STYLES = twv.tv({ }, size: { custom: '', - medium: 'text-xs leading-[25px] px-2 py-1', + medium: text.TEXT_STYLE({ className: 'px-2 py-1', color: 'custom', balance: true }), }, rounded: { custom: '', diff --git a/app/ide-desktop/lib/dashboard/src/components/ErrorBoundary.tsx b/app/ide-desktop/lib/dashboard/src/components/ErrorBoundary.tsx index b3bfc046fe..c157d766ef 100644 --- a/app/ide-desktop/lib/dashboard/src/components/ErrorBoundary.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/ErrorBoundary.tsx @@ -9,11 +9,15 @@ import * as sentry from '@sentry/react' import * as reactQuery from '@tanstack/react-query' import * as errorBoundary from 'react-error-boundary' +import * as detect from 'enso-common/src/detect' + import * as textProvider from '#/providers/TextProvider' import * as ariaComponents from '#/components/AriaComponents' import * as result from '#/components/Result' +import * as errorUtils from '#/utilities/error' + /** * Props for the ErrorBoundary component */ @@ -58,14 +62,23 @@ export function ErrorBoundary(props: ErrorBoundaryProps) { ) } +/** + * Props for the DefaultFallbackComponent + */ +export interface FallBackProps extends errorBoundary.FallbackProps { + readonly error: unknown +} + /** * Default fallback component to show when there is an error */ -function DefaultFallbackComponent(props: errorBoundary.FallbackProps): React.JSX.Element { - const { resetErrorBoundary } = props +function DefaultFallbackComponent(props: FallBackProps): React.JSX.Element { + const { resetErrorBoundary, error } = props const { getText } = textProvider.useText() + const stack = errorUtils.tryGetStack(error) + return ( + {detect.IS_DEV_MODE && stack != null && ( + + + {stack} + + + )} + - + {getText('tryAgain')} diff --git a/app/ide-desktop/lib/dashboard/src/components/Page.tsx b/app/ide-desktop/lib/dashboard/src/components/Page.tsx index e393475720..808a7dea60 100644 --- a/app/ide-desktop/lib/dashboard/src/components/Page.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/Page.tsx @@ -18,11 +18,12 @@ import Portal from '#/components/Portal' /** Props for a {@link Page}. */ export interface PageProps extends Readonly { readonly hideInfoBar?: true + readonly hideChat?: true } /** A page. */ export default function Page(props: PageProps) { - const { hideInfoBar = false, children } = props + const { hideInfoBar = false, children, hideChat = false } = props const [isHelpChatOpen, setIsHelpChatOpen] = React.useState(false) const { unsetModal } = modalProvider.useSetModal() const session = authProvider.useUserSession() @@ -52,7 +53,10 @@ export default function Page(props: PageProps) { )} {/* `session.accessToken` MUST be present in order for the `Chat` component to work. */} - {!hideInfoBar && session?.accessToken != null && process.env.ENSO_CLOUD_CHAT_URL != null ? ( + {!hideInfoBar && + !hideChat && + session?.accessToken != null && + process.env.ENSO_CLOUD_CHAT_URL != null ? ( + {title} ) : ( title )} - + {subtitle} diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Chat.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Chat.tsx index e24f79f483..1573dc0d0d 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Chat.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Chat.tsx @@ -683,7 +683,7 @@ export default function Chat(props: ChatProps) { return reactDom.createPortal(
- + {getText('invite')} @@ -96,7 +96,7 @@ export default function UserBar(props: UserBarProps) { {getText('upgrade')} diff --git a/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx b/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx index 8535dde59a..8fd54f9cd4 100644 --- a/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx +++ b/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx @@ -474,7 +474,7 @@ export default function Dashboard(props: DashboardProps) { }, [page, setPage]) return ( - +
(error: MustNotBeKnown): string | null { : null } +/** + * Extracts the `stack` property of a value if it is a string. Intended to be used on {@link Error}s. + */ +export function tryGetStack( + error: MustNotBeKnown, + // eslint-disable-next-line no-restricted-syntax + defaultMessage: DefaultMessage = null as DefaultMessage +): DefaultMessage | string { + const unknownError: unknown = error + return unknownError != null && + typeof unknownError === 'object' && + 'stack' in unknownError && + typeof unknownError.stack === 'string' + ? unknownError.stack + : defaultMessage +} + /** Like {@link tryGetMessage} but return the string representation of the value if it is not an * {@link Error}. */ export function getMessageOrToString(error: MustNotBeKnown) {