diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index b0648f2b0f..277d230982 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -1661,4 +1661,27 @@ declare module 'azdata' { */ graphFileType: string; } + + /** + * Component to display text with an icon representing the severity + */ + export interface InfoBoxComponent extends Component, InfoBoxComponentProperties { + /** + * An event called when the InfoBox is clicked + */ + onDidClick: vscode.Event; + } + + export interface InfoBoxComponentProperties { + /** + * Sets whether the infobox is clickable or not. This will display a right arrow at the end of infobox text. + * Default value is false. + */ + isClickable?: boolean | undefined; + + /** + * Sets the ariaLabel for the right arrow button that shows up in clickable infoboxes + */ + clickableButtonAriaLabel?: string; + } } diff --git a/src/sql/base/browser/ui/infoBox/infoBox.ts b/src/sql/base/browser/ui/infoBox/infoBox.ts index 72f94ca9f0..99f29b6ff6 100644 --- a/src/sql/base/browser/ui/infoBox/infoBox.ts +++ b/src/sql/base/browser/ui/infoBox/infoBox.ts @@ -4,10 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/infoBox'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { alert, status } from 'vs/base/browser/ui/aria/aria'; import { IThemable } from 'vs/base/common/styler'; import { Color } from 'vs/base/common/color'; +import * as DOM from 'vs/base/browser/dom'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Codicon } from 'vs/base/common/codicons'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; export interface IInfoBoxStyles { informationBackground?: Color; @@ -22,16 +27,25 @@ export interface InfoBoxOptions { text: string; style: InfoBoxStyle; announceText?: boolean; + isClickable?: boolean; + clickableButtonAriaLabel?: string; } export class InfoBox extends Disposable implements IThemable { private _imageElement: HTMLDivElement; private _textElement: HTMLDivElement; private _infoBoxElement: HTMLDivElement; + private _clickableIndicator: HTMLDivElement; private _text = ''; private _infoBoxStyle: InfoBoxStyle = 'information'; private _styles: IInfoBoxStyles; private _announceText: boolean = false; + private _isClickable: boolean = false; + private _clickableButtonAriaLabel: string; + + private _clickListenersDisposableStore = new DisposableStore(); + private _onDidClick: Emitter = this._register(new Emitter()); + get onDidClick(): Event { return this._onDidClick.event; } constructor(container: HTMLElement, options?: InfoBoxOptions) { super(); @@ -43,10 +57,16 @@ export class InfoBox extends Disposable implements IThemable { container.appendChild(this._infoBoxElement); this._infoBoxElement.appendChild(this._imageElement); this._infoBoxElement.appendChild(this._textElement); + this._clickableIndicator = DOM.$('a'); + this._clickableIndicator.classList.add('infobox-clickable-arrow', ...Codicon.arrowRight.classNamesArray); + this._infoBoxElement.appendChild(this._clickableIndicator); + if (options) { this.infoBoxStyle = options.style; this.text = options.text; this._announceText = (options.announceText === true); + this.isClickable = (options.isClickable === true); + this.clickableButtonAriaLabel = options.clickableButtonAriaLabel; } } @@ -96,6 +116,63 @@ export class InfoBox extends Disposable implements IThemable { } } + public get isClickable(): boolean { + return this._isClickable; + } + + public set isClickable(v: boolean) { + if (this._isClickable === v) { + return; + } + this._isClickable = v; + if (this._isClickable) { + this._clickableIndicator.style.display = ''; + this._clickableIndicator.tabIndex = 0; + this._infoBoxElement.style.cursor = 'pointer'; + this._infoBoxElement.setAttribute('role', 'button'); + this._textElement.style.maxWidth = 'calc(100% - 75px)'; + this.registerClickListeners(); + } else { + this._clickableIndicator.style.display = 'none'; + this._clickableIndicator.tabIndex = -1; + this._infoBoxElement.style.cursor = 'default'; + this._infoBoxElement.removeAttribute('role'); + this._textElement.style.maxWidth = ''; + this.unregisterClickListeners(); + } + } + + private registerClickListeners() { + this._clickListenersDisposableStore.add(DOM.addDisposableListener(this._infoBoxElement, DOM.EventType.CLICK, e => { + if (this._isClickable) { + this._onDidClick.fire(); + } + })); + + this._clickListenersDisposableStore.add(DOM.addDisposableListener(this._infoBoxElement, DOM.EventType.KEY_PRESS, e => { + const event = new StandardKeyboardEvent(e); + if (this._isClickable && (event.equals(KeyCode.Enter) || !event.equals(KeyCode.Space))) { + this._onDidClick.fire(); + DOM.EventHelper.stop(e); + return; + } + })); + } + + private unregisterClickListeners() { + this._clickListenersDisposableStore.clear(); + } + + public get clickableButtonAriaLabel(): string { + return this._clickableButtonAriaLabel; + } + + public set clickableButtonAriaLabel(v: string) { + this._clickableButtonAriaLabel = v; + this._clickableIndicator.ariaLabel = this._clickableButtonAriaLabel; + this._clickableIndicator.title = this._clickableButtonAriaLabel; + } + private updateStyle(): void { if (this._styles) { let backgroundColor: Color; diff --git a/src/sql/base/browser/ui/infoBox/media/infoBox.css b/src/sql/base/browser/ui/infoBox/media/infoBox.css index 75dd1d127d..4c26e1c53d 100644 --- a/src/sql/base/browser/ui/infoBox/media/infoBox.css +++ b/src/sql/base/browser/ui/infoBox/media/infoBox.css @@ -8,7 +8,6 @@ flex-direction: row; width: 100%; height: 100%; - overflow: scroll; } .infobox-image { @@ -38,7 +37,12 @@ } .infobox-text { - flex: 1 1 auto; - padding: 10px 0; + flex: 1 1 fit-content; + padding: 10px 0px 0px 0px; user-select: text; + overflow: scroll; +} + +.infobox-clickable-arrow { + padding: 10px 0px 0px 0px; } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 19dd99c1d0..9376a83aa2 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -2085,6 +2085,7 @@ class InfoBoxComponentWrapper extends ComponentWrapper implements azdata.InfoBox constructor(proxy: MainThreadModelViewShape, handle: number, id: string, logService: ILogService) { super(proxy, handle, ModelComponentTypes.InfoBox, id, logService); this.properties = {}; + this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get style(): azdata.InfoBoxStyle { @@ -2110,6 +2111,27 @@ class InfoBoxComponentWrapper extends ComponentWrapper implements azdata.InfoBox public set announceText(v: boolean) { this.setProperty('announceText', v); } + + public get isClickable(): boolean { + return this.properties['isClickable']; + } + + public set isClickable(v: boolean) { + this.setProperty('isClickable', v); + } + + public get clickableButtonAriaLabel(): string { + return this.properties['clickableButtonAriaLabel']; + } + + public set clickableButtonAriaLabel(v: string) { + this.setProperty('clickableButtonAriaLabel', v); + } + + public get onDidClick(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidClick); + return emitter && emitter.event; + } } class SliderComponentWrapper extends ComponentWrapper implements azdata.SliderComponent { diff --git a/src/sql/workbench/browser/modelComponents/infoBox.component.ts b/src/sql/workbench/browser/modelComponents/infoBox.component.ts index b9b2397518..04ebd0b468 100644 --- a/src/sql/workbench/browser/modelComponents/infoBox.component.ts +++ b/src/sql/workbench/browser/modelComponents/infoBox.component.ts @@ -8,7 +8,7 @@ import { } from '@angular/core'; import * as azdata from 'azdata'; -import { IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; +import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; import { ILogService } from 'vs/platform/log/common/log'; import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase'; import { InfoBox, InfoBoxStyle } from 'sql/base/browser/ui/infoBox/infoBox'; @@ -41,6 +41,12 @@ export default class InfoBoxComponent extends ComponentBase { + this.fireEvent({ + eventType: ComponentEventType.onDidClick, + args: e + }); + }); this.updateInfoBox(); } } @@ -65,6 +71,8 @@ export default class InfoBoxComponent extends ComponentBase((props) => props.announceText, false); } + + public get isClickable(): boolean { + return this.getPropertyOrDefault((props) => props.isClickable, false); + } + + public get clickableButtonAriaLabel(): string { + return this.getPropertyOrDefault((props) => props.clickableButtonAriaLabel, ''); + } }