mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 09:35:36 -05:00
Introduce vertical cards (#12125)
* Introduce vertical cards * Simplify and add a css type * Feedback * Update yarn.lock
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<div #cardDiv role="radio" *ngIf="label" [class]="getClass()" (click)="onCardClick()" [attr.aria-checked]="selected" (mouseover)="onCardHoverChanged($event)"
|
||||
(mouseout)="onCardHoverChanged($event)" tabIndex="0" [style.width]="width" [style.height]="height">
|
||||
<!-- The horizontal is for compatibility, we're going to get rid of this once card component can be removed -->
|
||||
<div #cardDiv role="radio" *ngIf="label" [class]="getClass() + ' horizontal'" (click)="onCardClick()"
|
||||
[attr.aria-checked]="selected" (mouseover)="onCardHoverChanged($event)" (mouseout)="onCardHoverChanged($event)"
|
||||
tabIndex="0" [style.width]="width" [style.height]="height">
|
||||
<ng-container *ngIf="isVerticalButton || isDetailsCard">
|
||||
<span *ngIf="hasStatus" class="card-status">
|
||||
<div class="status-content" [style.backgroundColor]="statusColor"></div>
|
||||
@@ -15,8 +17,10 @@
|
||||
<h4 class="card-label">{{label}}</h4>
|
||||
<div *ngIf="descriptions.length > 0" class="model-card-description-container">
|
||||
<div *ngFor="let desc of descriptions">
|
||||
<div *ngIf="desc.label; else separator" [style.font-weight]="desc.fontWeight" class="model-card-list-item-description">
|
||||
<span>{{desc.label}}</span><span class="model-card-list-item-description-value">{{desc.value}}</span>
|
||||
<div *ngIf="desc.label; else separator" [style.font-weight]="desc.fontWeight"
|
||||
class="model-card-list-item-description">
|
||||
<span>{{desc.label}}</span><span
|
||||
class="model-card-list-item-description-value">{{desc.value}}</span>
|
||||
</div>
|
||||
<ng-template #separator>
|
||||
<div style="height: 12px"></div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.model-card {
|
||||
.horizontal .model-card {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 90%;
|
||||
@@ -16,7 +16,7 @@
|
||||
border-color: rgb(214, 214, 214);
|
||||
}
|
||||
|
||||
.model-card .card-content {
|
||||
.horizontal .model-card .card-content {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
@@ -26,7 +26,8 @@
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.model-card .card-vertical-button {
|
||||
.horizontal .model-card .card-vertical-button,
|
||||
.horizontal .model-card .text-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -38,17 +39,17 @@
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.model-card .card-label {
|
||||
.horizontal .model-card .card-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.model-card .card-value {
|
||||
.horizontal .model-card .card-value {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.model-card .iconContainer {
|
||||
.horizontal .model-card .icon-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -60,7 +61,7 @@
|
||||
border-color: rgb(214, 214, 214);
|
||||
}
|
||||
|
||||
.model-card .cardIcon {
|
||||
.horizontal .model-card .icon {
|
||||
display: inline-block;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
@@ -72,7 +73,7 @@
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.model-card .card-status {
|
||||
.horizontal .model-card .card-status {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 5px;
|
||||
@@ -81,7 +82,7 @@
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.model-card .status-content {
|
||||
.horizontal .model-card .status-content {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
@@ -91,7 +92,8 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.model-card-list-item .selection-indicator-container, .model-card .selection-indicator-container {
|
||||
.horizontal .model-card-list-item .selection-indicator-container,
|
||||
.horizontal .model-card .selection-indicator-container {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
@@ -105,7 +107,8 @@
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.model-card-list-item .selection-indicator-container, .model-card .selection-indicator-container {
|
||||
.horizontal .model-card-list-item .selection-indicator-container,
|
||||
.horizontal .model-card .selection-indicator-container {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
width: 16px;
|
||||
@@ -117,17 +120,18 @@
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.model-card-list-item .selection-indicator-container {
|
||||
.horizontal .model-card-list-item .selection-indicator-container {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.model-card .selection-indicator-container {
|
||||
.horizontal .model-card .selection-indicator-container {
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.model-card-list-item .selection-indicator, .model-card .selection-indicator {
|
||||
.horizontal .model-card-list-item .selection-indicator,
|
||||
.horizontal .model-card .selection-indicator {
|
||||
margin: 4px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -135,26 +139,26 @@
|
||||
background-color: rgb(0, 120, 215);
|
||||
}
|
||||
|
||||
.model-card .model-table {
|
||||
.horizontal .model-card .model-table {
|
||||
border-spacing: 5px;
|
||||
}
|
||||
|
||||
.model-table .table-row {
|
||||
.horizontal .model-table .table-row {
|
||||
width: auto;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.model-table .table-cell {
|
||||
.horizontal .model-table .table-cell {
|
||||
vertical-align: top;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.model-table a {
|
||||
.horizontal .model-table a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.model-card-list-item {
|
||||
.horizontal .model-card-list-item {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -165,14 +169,14 @@
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.model-card-list-item .list-item-content {
|
||||
.horizontal .model-card-list-item .list-item-content {
|
||||
height: auto;
|
||||
padding: 5px 26px 5px 5px;
|
||||
min-height: 30px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.model-card-list-item .list-item-icon {
|
||||
.horizontal .model-card-list-item .list-item-icon {
|
||||
background-position: 2px 2px;
|
||||
padding-left: 22px;
|
||||
font-size: 15px;
|
||||
@@ -180,40 +184,40 @@
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.model-card-list-item .list-item-description {
|
||||
.horizontal .model-card-list-item .list-item-description {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.model-card-description-container {
|
||||
.horizontal .model-card-description-container {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
border-color: rgb(214, 214, 214);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.model-card-list-item-description {
|
||||
.horizontal .model-card-list-item-description {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.model-card-list-item-description-value {
|
||||
.horizontal .model-card-list-item-description-value {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.card-group {
|
||||
.horizontal .card-group {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.model-card-description-table {
|
||||
.horizontal .model-card-description-table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.model-card-description-label-column {
|
||||
.horizontal .model-card-description-label-column {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.model-card-description-value-column {
|
||||
.horizontal .model-card-description-value-column {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
!!!!NOTICE!!!!
|
||||
This CSS file is only to be used with vertical selector.
|
||||
If you want to reuse some of the stuff here, just copy/paste them.
|
||||
*/
|
||||
|
||||
.vertical .card-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vertical .model-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.vertical .model-card .model-card>* {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.vertical .model-card .selection-indicator-container {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
border-width: 1px;
|
||||
border-color: rgb(214, 214, 214);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.vertical .model-card .selection-indicator {
|
||||
border-radius: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: rgb(0, 120, 215);
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
/* Icon container */
|
||||
.vertical .model-card .icon-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* The icon itself */
|
||||
.vertical .model-card .icon-container .icon {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 50px;
|
||||
max-height: 50px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.vertical .model-card .text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.vertical .model-card .text-container .inner-text-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.vertical .model-card .text-value {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.vertical .model-card .link-value .codicon {
|
||||
padding-left: 5px;
|
||||
}
|
||||
@@ -1,31 +1,27 @@
|
||||
<div role="radiogroup" *ngIf="cards" class="card-group" style="flex-wrap:wrap" [style.height]="height"
|
||||
[style.width]="width" [attr.aria-label]="ariaLabel" (keydown)="onKeyDown($event)">
|
||||
<div #cardDiv role="radio" *ngFor="let card of cards" class="model-card" (click)="selectCard(card.id)"
|
||||
[attr.aria-checked]="isCardSelected(card.id)" [tabIndex]="getTabIndex(card.id)" [style.width]="cardWidth"
|
||||
[style.height]="cardHeight" (focus)="onCardFocus(card.id)" (blur)="onCardBlur(card.id)" style="flex:0 0 auto;">
|
||||
<span class="selection-indicator-container">
|
||||
<div *ngIf="isCardSelected(card.id)" class="selection-indicator"></div>
|
||||
</span>
|
||||
<div class="card-vertical-button">
|
||||
<div *ngIf="card.icon" class="iconContainer">
|
||||
<div [class]="getIconClass(card.id)" [style.width]="iconWidth" [style.height]="iconHeight"></div>
|
||||
<div role="radiogroup" *ngIf="cards" [class]="orientation + ' card-group'" class="card-group" style="flex-wrap:wrap"
|
||||
[style.height]="height" [style.width]="width" [attr.aria-label]="ariaLabel" (keydown)="onKeyDown($event)">
|
||||
<div #cardDiv role="radio" *ngFor="let card of cards" class="model-card" (click)="selectCard(card.id)"
|
||||
[attr.aria-checked]="isCardSelected(card.id)" [tabIndex]="getTabIndex(card.id)" [style.width]="cardWidth"
|
||||
[style.height]="cardHeight" (focus)="onCardFocus(card.id)" (blur)="onCardBlur(card.id)" style="flex:0 0 auto;">
|
||||
|
||||
<div class="selection-indicator-container">
|
||||
<div *ngIf="isCardSelected(card.id)" class="selection-indicator"></div>
|
||||
</div>
|
||||
<h4 class="card-label">{{card.label}}</h4>
|
||||
<div *ngIf="card.descriptions && card.descriptions.length > 0" class="model-card-description-container">
|
||||
<ng-container *ngFor="let desc of card.descriptions">
|
||||
<table class="model-card-description-table" [attr.aria-label]="desc.ariaLabel">
|
||||
<tr>
|
||||
<th class="model-card-description-label-column">{{desc.labelHeader}}</th>
|
||||
<th class="model-card-description-value-column" *ngIf="desc.valueHeader">
|
||||
{{desc.valueHeader}}</th>
|
||||
</tr>
|
||||
<tr *ngFor="let content of desc.contents">
|
||||
<td class="model-card-description-label-column">{{content.label}}</td>
|
||||
<td class="model-card-description-value-column" *ngIf="content.value">{{content.value}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="card.icon" class="icon-container">
|
||||
<div [class]="getIconClass(card.id)" [style.width]="iconWidth" [style.height]="iconHeight"> </div>
|
||||
</div>
|
||||
<div class="text-container">
|
||||
<div *ngFor="let description of card.descriptions" class="inner-text-content">
|
||||
<span class="text-value" [ngStyle]="description.textStyles">{{description.textValue}}</span>
|
||||
<a *ngIf="description.linkDisplayValue" class="link-value" href="#"
|
||||
(click)="onLinkClick($event, card.id, description)"
|
||||
[ngStyle]="description.linkStyles">
|
||||
{{description.linkDisplayValue}}
|
||||
<span *ngIf="description.displayLinkCodicon && description.linkDisplayValue"
|
||||
class="codicon codicon-link-external" [ngStyle]="description.linkCodiconStyles"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,13 +9,15 @@ import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUt
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
import 'vs/css!./media/card';
|
||||
import 'vs/css!./media/verticalCard';
|
||||
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
@Component({
|
||||
templateUrl: decodeURI(require.toUrl('./radioCardGroup.component.html'))
|
||||
|
||||
})
|
||||
export default class RadioCardGroup extends ComponentBase implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@@ -94,27 +96,36 @@ export default class RadioCardGroup extends ComponentBase implements IComponent,
|
||||
}
|
||||
|
||||
public get cards(): azdata.RadioCard[] {
|
||||
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, azdata.RadioCard[]>((props) => props.cards, []);
|
||||
return this.getSpecficProperties().cards ?? [];
|
||||
}
|
||||
|
||||
public get cardWidth(): string | undefined {
|
||||
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.cardWidth, undefined);
|
||||
return this.getSpecficProperties().cardWidth ?? undefined;
|
||||
}
|
||||
|
||||
public get cardHeight(): string | undefined {
|
||||
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.cardHeight, undefined);
|
||||
return this.getSpecficProperties().cardHeight ?? undefined;
|
||||
}
|
||||
|
||||
public get iconWidth(): string | undefined {
|
||||
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.iconWidth, undefined);
|
||||
return this.getSpecficProperties().iconWidth ?? undefined;
|
||||
}
|
||||
|
||||
public get iconHeight(): string | undefined {
|
||||
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.iconHeight, undefined);
|
||||
return this.getSpecficProperties().iconHeight ?? undefined;
|
||||
}
|
||||
|
||||
public get selectedCardId(): string | undefined {
|
||||
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.selectedCardId, undefined);
|
||||
return this.getSpecficProperties().selectedCardId ?? undefined;
|
||||
}
|
||||
|
||||
public get orientation(): string {
|
||||
const x = this.getSpecficProperties().orientation ?? 'horizontal';
|
||||
return x;
|
||||
}
|
||||
|
||||
private getSpecficProperties(): azdata.RadioCardGroupComponentProperties {
|
||||
return this.getProperties<azdata.RadioCardGroupComponentProperties>();
|
||||
}
|
||||
|
||||
public getIconClass(cardId: string): string {
|
||||
@@ -149,6 +160,18 @@ export default class RadioCardGroup extends ComponentBase implements IComponent,
|
||||
});
|
||||
}
|
||||
|
||||
public onLinkClick(event: Event, cardId: string, textContents: azdata.RadioCardDescription): void {
|
||||
event.stopPropagation();
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: {
|
||||
cardId,
|
||||
textContents: deepClone(textContents),
|
||||
card: deepClone(this.getCardById(cardId))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getCardElement(cardId: string): ElementRef {
|
||||
const card = this.getCardById(cardId);
|
||||
return this.cardElements.toArray()[this.cards.indexOf(card)];
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IComponentDescriptor, IComponent, IModelStore } from 'sql/platform/dash
|
||||
|
||||
export enum Orientation {
|
||||
Horizontal = 'horizontal',
|
||||
Vertical = 'vertial'
|
||||
Vertical = 'vertical'
|
||||
}
|
||||
|
||||
export interface ToolbarLayout {
|
||||
|
||||
Reference in New Issue
Block a user