From bb8f58f6cbe8be68442a7479ed94862a8ed5688f Mon Sep 17 00:00:00 2001 From: Vadim Marenkov <2dangel84@gmail.com> Date: Wed, 26 Sep 2018 11:49:49 +0700 Subject: [PATCH 1/6] feat(cards): Added cards --- commitlint.config.js | 1 + package.json | 1 + src/lib-dev/cards/module.ts | 42 ++++++ src/lib-dev/cards/styles.scss | 26 ++++ src/lib-dev/cards/template.html | 78 +++++++++++ src/lib/cards/README.md | 0 src/lib/cards/_cards-theme.scss | 99 ++++++++++++++ src/lib/cards/cards.component.html | 15 ++ src/lib/cards/cards.component.scss | 80 +++++++++++ src/lib/cards/cards.component.spec.ts | 114 ++++++++++++++++ src/lib/cards/cards.component.ts | 128 ++++++++++++++++++ src/lib/cards/cards.md | 13 ++ src/lib/cards/cards.module.ts | 30 ++++ src/lib/cards/index.ts | 1 + src/lib/cards/public-api.ts | 2 + src/lib/cards/tsconfig.build.json | 13 ++ src/lib/core/theming/_all-theme.scss | 2 + src/lib/core/theming/_palette.scss | 44 +++--- src/lib/core/theming/prebuilt/dark-theme.scss | 4 +- 19 files changed, 669 insertions(+), 24 deletions(-) create mode 100644 src/lib-dev/cards/module.ts create mode 100644 src/lib-dev/cards/styles.scss create mode 100644 src/lib-dev/cards/template.html create mode 100644 src/lib/cards/README.md create mode 100644 src/lib/cards/_cards-theme.scss create mode 100644 src/lib/cards/cards.component.html create mode 100644 src/lib/cards/cards.component.scss create mode 100644 src/lib/cards/cards.component.spec.ts create mode 100644 src/lib/cards/cards.component.ts create mode 100644 src/lib/cards/cards.md create mode 100644 src/lib/cards/cards.module.ts create mode 100644 src/lib/cards/index.ts create mode 100644 src/lib/cards/public-api.ts create mode 100644 src/lib/cards/tsconfig.build.json diff --git a/commitlint.config.js b/commitlint.config.js index 21774476e..1251eca41 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -9,6 +9,7 @@ module.exports = { 'build', 'button', 'cdk', + 'cards', 'checkbox', 'chore', 'common', diff --git a/package.json b/package.json index c48df5fee..962de1c8a 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "server-dev": "webpack-dev-server --config tools/webpack/webpack.config.js", "server-dev:badge": "npm run server-dev -- --env.component badge", "server-dev:button": "npm run server-dev -- --env.component button", + "server-dev:cards": "npm run server-dev -- --env.component cards", "server-dev:checkbox": "npm run server-dev -- --env.component checkbox", "server-dev:icon": "npm run server-dev -- --env.component icon", "server-dev:input": "npm run server-dev -- --env.component input", diff --git a/src/lib-dev/cards/module.ts b/src/lib-dev/cards/module.ts new file mode 100644 index 000000000..55ca1e006 --- /dev/null +++ b/src/lib-dev/cards/module.ts @@ -0,0 +1,42 @@ +import { Component, NgModule, ViewEncapsulation } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { McCardsModule, Status } from '../../lib/cards'; + + +@Component({ + selector: 'app', + template: require('./template.html'), + styleUrls: ['./styles.scss'], + encapsulation: ViewEncapsulation.None +}) +export class CardsDemoComponent { + state = Status; + + s1 = false; + s2 = false; + s3 = false; + s4 = false; +} + + +@NgModule({ + declarations: [ + CardsDemoComponent + ], + imports: [ + BrowserModule, + McCardsModule + ], + bootstrap: [ + CardsDemoComponent + ] +}) +export class CardsDemoModule {} + +platformBrowserDynamic() + .bootstrapModule(CardsDemoModule) + // tslint:disable-next-line + .catch((error) => console.error(error)); + diff --git a/src/lib-dev/cards/styles.scss b/src/lib-dev/cards/styles.scss new file mode 100644 index 000000000..b12d858f8 --- /dev/null +++ b/src/lib-dev/cards/styles.scss @@ -0,0 +1,26 @@ +@import '~@ptsecurity/mosaic-icons/dist/styles/mc-icons'; + +// Светлая +@import '../../lib/core/theming/prebuilt/default-theme'; + +//// Темная +//@import '../../lib/core/theming/prebuilt/dark-theme'; +// +//html { +// background: mc-color($mc-grey, 800); +// color: white; +//} + +mc-card { + margin-right: 10px; +} + +.line { + margin-bottom: 20px; + display: flex; + flex-direction: row; +} + +.mc-upload-to-cloud_24 { + font-size: 32px; +} diff --git a/src/lib-dev/cards/template.html b/src/lib-dev/cards/template.html new file mode 100644 index 000000000..b261432ef --- /dev/null +++ b/src/lib-dev/cards/template.html @@ -0,0 +1,78 @@ +
+

Обычные

+
+ + Content + + + + + Content 2 + + + + Content 3 + + + + Content 4 + +
+

Белые

+
+ + Content + + + + + Content 2 + + + + Content 3 + + + + Content 4 + +
+

Неинетерактивные

+
+ + Content + + + + + Content 2 + + + + Content 3 + + + + Content 4 + +
+

Дополнительный контент

+
+ + + Content + + + + Content + + + + + Content + + + Content + +
+
diff --git a/src/lib/cards/README.md b/src/lib/cards/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/src/lib/cards/_cards-theme.scss b/src/lib/cards/_cards-theme.scss new file mode 100644 index 000000000..19fcce4f8 --- /dev/null +++ b/src/lib/cards/_cards-theme.scss @@ -0,0 +1,99 @@ +@import '../core/theming/theming'; +@import '../core/theming/palette'; + +@mixin left-marker($color) { + $gray100: mc-color($mc-grey, 100); + box-shadow: inset -1px 0 0 0 $gray100, inset 0 1px 0 0 $gray100, inset 0 -1px 0 0 $gray100, inset 4px 0 0 0 mc-color($color, 400); +} + +@mixin left-marker-dark($color) { + $gray600: mc-color($mc-grey, 600); + box-shadow: inset -1px 0 0 0 $gray600, inset 0 1px 0 0 $gray600, inset 0 -1px 0 0 $gray600, inset 4px 0 0 0 mc-color($color, 600); +} + +@mixin normal-state($color, $isDark) { + @if $isDark == true { + background-color: mc-color($color, 700); + @include left-marker-dark($color); + } @else { + background-color: mc-color($color, 60); + @include left-marker($color); + } +} + +@mixin selected-state($border-color, $back-color, $isDark) { + @if $isDark == true { + background-color: mc-color($back-color, 700); + box-shadow: inset 4px 0 0 0 mc-color($border-color, 600); + } @else { + background-color: mc-color($back-color, 100); + box-shadow: inset 4px 0 0 0 mc-color($border-color, 400); + } +} + +@mixin mc-cards-theme($theme) { + $isDark: map-get($theme, is-dark); + $info: map-get($theme, primary); + $error: map-get($theme, warn); + $warning: $mc-yellow; + $success: $mc-green; + $background-info: map-get($theme, background); + $foreground-info: map-get($theme, foreground); + $background-color: map-get($background-info, background); + $foreground-color: map-get($foreground-info, base); + + $hover-color: map-get($background-info, hover); + $focus-color: mc-color($mc-blue, 400); + + .mc-card { + background-color: mc-color($mc-grey, 60); + color: $foreground-color; + + &__info { + @include normal-state($info, $isDark); + + &.mc-card__selected { + @include selected-state($info, $info, $isDark); + } + } + + &__warning { + @include normal-state($warning, $isDark); + + &.mc-card__selected { + @include selected-state($warning, $info, $isDark); + } + } + + &__success { + @include normal-state($success, $isDark); + + &.mc-card__selected { + @include selected-state($success, $info, $isDark); + } + } + + &__error { + @include normal-state($error, $isDark); + + &.mc-card__selected { + @include selected-state($error, $info, $isDark); + } + } + + &__white { + background-color: $background-color; + } + + .mc-cards-hover-overlay { + background: $hover-color; + } + + &:focus, + &.cdk-focused { + .mc-cards-focus-overlay { + border: solid 1px $focus-color; + } + } + } +} diff --git a/src/lib/cards/cards.component.html b/src/lib/cards/cards.component.html new file mode 100644 index 000000000..c156cf6cd --- /dev/null +++ b/src/lib/cards/cards.component.html @@ -0,0 +1,15 @@ +
+
+ +
+
+ +
+
+ +
+
+
+
diff --git a/src/lib/cards/cards.component.scss b/src/lib/cards/cards.component.scss new file mode 100644 index 000000000..28a16b160 --- /dev/null +++ b/src/lib/cards/cards.component.scss @@ -0,0 +1,80 @@ +@import 'cards-theme'; +@import '../core/styles/common/layout'; + +.mc-card { + position: relative; + box-sizing: border-box; + display: flex; + flex-direction: column; + cursor: pointer; + + .mc-cards-wrapper { + flex: auto; + display: flex; + flex-direction: row; + + %content-base { + display: flex; + flex-direction: column; + position: relative; + } + + .content-left { + @extend %content-base; + + flex: none; + margin: 16px 12px; + + &.has-content { + margin-left: 20px; + } + } + + .main-content { + @extend %content-base; + flex: auto; + margin: 12px 0; + } + + .content-right { + @extend %content-base; + flex: none; + margin: 8px 16px 8px 8px; + + &.has-content { + margin-right: 8px; + } + } + } + + &:focus { + outline: none; + } + + &.cdk-focused { + z-index: 1; + } + + .mc-cards-focus-overlay { + @include mc-fill; + pointer-events:none; + } + + .mc-cards-hover-overlay { + @include mc-fill; + display: none !important; + pointer-events:none; + } + + &.mc-card__hover { + .mc-cards-hover-overlay { + display: block !important; + } + } + + &.mc-readonly { + cursor: auto; + } +} + + diff --git a/src/lib/cards/cards.component.spec.ts b/src/lib/cards/cards.component.spec.ts new file mode 100644 index 000000000..236c21bf1 --- /dev/null +++ b/src/lib/cards/cards.component.spec.ts @@ -0,0 +1,114 @@ +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { McCard, McCardsModule, Status } from './index'; + + +describe('MсCards', () => { + let fixture: ComponentFixture; + let cardDebugElement: DebugElement; + let cardNativeElement: HTMLElement; + let cardInstance: McCard; + let testComponent: TestApp; + let wraperElement: HTMLInputElement; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [McCardsModule], + declarations: [TestApp] + }); + + TestBed.compileComponents(); + + fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + + cardDebugElement = fixture.debugElement.query(By.directive(McCard)); + cardNativeElement = cardDebugElement.nativeElement; + cardInstance = cardDebugElement.componentInstance; + testComponent = fixture.debugElement.componentInstance; + wraperElement = cardNativeElement.querySelector('.mc-cards-wrapper'); + })); + + it('should apply class based on status', () => { + + testComponent.status = Status.Info; + fixture.detectChanges(); + expect(wraperElement.classList.contains('mc-card__info')).toBe(true); + + testComponent.status = Status.Error; + fixture.detectChanges(); + expect(wraperElement.classList.contains('mc-card__error')).toBe(true); + + testComponent.status = Status.Warning; + fixture.detectChanges(); + expect(wraperElement.classList.contains('mc-card__warning')).toBe(true); + + testComponent.status = Status.Success; + fixture.detectChanges(); + expect(wraperElement.classList.contains('mc-card__success')).toBe(true); + }); + + it('should apply class for "white mode"', () => { + + testComponent.mode = 'white'; + fixture.detectChanges(); + expect(wraperElement.classList.contains('mc-card__white')).toBe(true); + }); + + it('should add class on selected', () => { + testComponent.selected = true; + fixture.detectChanges(); + + expect(wraperElement.classList.contains('mc-card__selected')).toBe(true); + }); + + it('should handle a click on the card', () => { + wraperElement.click(); + + expect(testComponent.clickCount).toBe(1); + }); + + it('should not interact if readonly', () => { + testComponent.readonly = true; + fixture.detectChanges(); + + wraperElement.click(); + + expect(testComponent.clickCount).toBe(0); + }); + + it('should remove tabindex if readonly', () => { + expect(cardNativeElement.getAttribute('tabIndex')).toBe('0'); + + testComponent.readonly = true; + fixture.detectChanges(); + expect(cardNativeElement.getAttribute('tabIndex')).toBe(null); + }); +}); + + +@Component({ + selector: 'test-app', + template: ` + + + ` +}) +class TestApp { + status: Status = Status.Info; + readonly = false; + selected = false; + mode: 'color' | 'white' = 'color'; + + clickCount: number = 0; + + increment() { + this.clickCount++; + } +} diff --git a/src/lib/cards/cards.component.ts b/src/lib/cards/cards.component.ts new file mode 100644 index 000000000..2a803f32b --- /dev/null +++ b/src/lib/cards/cards.component.ts @@ -0,0 +1,128 @@ +import { + Output, + ChangeDetectionStrategy, + Component, + ElementRef, Input, + OnDestroy, EventEmitter, + ViewEncapsulation, HostBinding, Directive, ContentChild +} from '@angular/core'; + +import { FocusMonitor } from '@ptsecurity/cdk/a11y'; + + +export enum Status { + Info, + Success, + Warning, + Error +} + +@Directive({ + selector: '[content-left]' +}) +export class ContentLeft { +} + +@Directive({ + selector: '[content-right]' +}) +export class ContentRight { +} + +const name = 'mc-card'; + +@Component({ + selector: name, + templateUrl: './cards.component.html', + styleUrls: ['./cards.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + inputs: ['color'], + host: { + class: name + } +}) +export class McCard implements OnDestroy { + get tabIndex(): number | null { + return this.readonly ? null : this._tabIndex; + } + + @HostBinding('attr.tabIndex') + @Input() + set tabIndex(value: number | null) { + this._tabIndex = value; + } + + @HostBinding('class.mc-readonly') + @Input() + readonly = false; + + @Input() + selected = false; + + @Output() + selectedChange = new EventEmitter(); + + @Input() + mode: 'color' | 'white' = 'color'; + + @Input() + status: Status = Status.Info; + + @HostBinding('class.mc-card__hover') + hover = false; + + @ContentChild(ContentLeft) + contentLeft: any; + + @ContentChild(ContentRight) + contentRight: any; + + private _tabIndex: number | null = 0; + + constructor(private _elementRef: ElementRef, private _focusMonitor: FocusMonitor) { + this._focusMonitor.monitor(this._elementRef.nativeElement, false); + } + + get statusClass() { + switch (this.status) { + case Status.Error: + return `${name}__error`; + case Status.Info: + return `${name}__info`; + case Status.Success: + return `${name}__success`; + case Status.Warning: + return `${name}__warning`; + default: + return ''; + } + } + + get isWhiteMode() { + return this.mode === 'white'; + } + + ngOnDestroy() { + this._focusMonitor.stopMonitoring(this._elementRef.nativeElement); + } + + focus(): void { + this.hostElement.focus(); + } + + clicked($event: MouseEvent) { + if (!this.readonly) { + $event.stopPropagation(); + this.selectedChange.emit(!this.selected); + } + } + + mouseEnter() { + this.hover = !this.readonly ? true : false; + } + + private get hostElement() { + return this._elementRef.nativeElement; + } +} diff --git a/src/lib/cards/cards.md b/src/lib/cards/cards.md new file mode 100644 index 000000000..0859df146 --- /dev/null +++ b/src/lib/cards/cards.md @@ -0,0 +1,13 @@ +Mosaic buttons are native `