Introduce vertical cards (#12125)

* Introduce vertical cards

* Simplify and add a css type

* Feedback

* Update yarn.lock
This commit is contained in:
Amir Omidi
2020-09-04 15:15:58 -07:00
committed by GitHub
parent c2320831f7
commit 59bb827d2e
12 changed files with 255 additions and 85 deletions

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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)];

View File

@@ -14,7 +14,7 @@ import { IComponentDescriptor, IComponent, IModelStore } from 'sql/platform/dash
export enum Orientation {
Horizontal = 'horizontal',
Vertical = 'vertial'
Vertical = 'vertical'
}
export interface ToolbarLayout {