Btn glow effect module separation. Material like btn styles. (#31)

This commit is contained in:
Ildar Galeev 2019-06-03 12:24:25 +03:00 committed by GitHub
parent c03232c1de
commit f25fc8a6d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 326 additions and 245 deletions

View File

@ -0,0 +1,61 @@
// Basic button standards
$dsh-button-min-width: 64px !default;
$dsh-button-margin: 0 !default;
$dsh-button-line-height: 36px !default;
$dsh-button-padding: 0 16px !default;
$dsh-button-border-radius: 25px !default;
// Stroked button padding is 2px less horizontally than default button's padding.
$dsh-stroked-button-line-height: $dsh-button-line-height - 4;
$dsh-stroked-button-border-width: 1px;
// Mixin overriding default button styles like the gray background, the border, and the outline.
@mixin dsh-button-reset {
user-select: none;
cursor: pointer;
outline: none;
border: none;
-webkit-tap-highlight-color: transparent;
// The `outline: none` from above works on all browsers, however Firefox also
// adds a special `focus-inner` which we have to disable explicitly. See:
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Firefox
&::-moz-focus-inner {
border: 0;
}
}
// Applies base styles to all button types.
@mixin dsh-button-base {
box-sizing: border-box;
position: relative;
// Reset browser <button> styles.
@include dsh-button-reset();
// Make anchors render like buttons.
display: inline-block;
white-space: nowrap;
text-decoration: none;
vertical-align: baseline;
text-align: center;
// Sizing.
margin: $dsh-button-margin;
min-width: $dsh-button-min-width;
line-height: $dsh-button-line-height;
padding: $dsh-button-padding;
border-radius: $dsh-button-border-radius;
// Explicitly set the default overflow to `visible`. It is already set
// on most browsers except on IE11 where it defaults to `hidden`.
overflow: visible;
&[disabled] {
cursor: default;
}
&::-moz-focus-inner {
border: 0;
}
}

View File

@ -14,11 +14,39 @@
color: $text-color;
}
@mixin focused-background($palette, $color) {
background: mat-color($palette, $color);
@mixin focused-background($color) {
&.focused {
background: $color;
}
}
@mixin dsh-glow-theme($theme) {
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
.dsh-glow {
@include glow-effect(map-get($background, hover), 5%);
&.dsh-primary {
@include glow-effect(mat-color($primary, 400), 4%);
}
&.dsh-accent {
@include glow-effect(mat-color($accent, 300), 10%);
}
&.dsh-warn {
@include glow-effect(mat-color($warn, 400), 5%);
}
}
}
@mixin dsh-button-theme($theme) {
@include dsh-glow-theme($theme);
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
@ -29,6 +57,7 @@
&-button,
&-stroked-button {
background: none;
color: map-get($foreground, text);
&:focus:not(:disabled) {
outline: none;
@ -37,49 +66,24 @@
&-button {
color: map-get($foreground, base);
&:hover {
transition: background 500ms ease-in-out; // TODO need to animation config
background: map-get($background, hover);
}
&-glow {
@include glow-effect(map-get($background, hover), 5%);
&:hover {
background: map-get($background, hover);
}
&.dsh-primary {
@include button(map-get($background, card), mat-color($primary, 400));
.dsh-button-glow {
@include glow-effect(mat-color($primary, 400), 4%);
}
&.focused {
@include focused-background($primary, 300);
}
@include focused-background(mat-color($primary, 300));
}
&.dsh-accent {
@include button(map-get($background, card), mat-color($accent, 300));
.dsh-button-glow {
@include glow-effect(mat-color($accent, 300), 10%);
}
&.focused {
@include focused-background($accent, 200);
}
@include focused-background(mat-color($accent, 200));
}
&.dsh-warn {
@include button(map-get($background, card), mat-color($warn, 400));
.dsh-button-glow {
@include glow-effect(mat-color($warn, 400), 5%);
}
&.focused {
@include focused-background($warn, 300);
}
@include focused-background(mat-color($warn, 300));
}
&:disabled {
@ -90,26 +94,17 @@
&-stroked-button {
&.dsh-primary {
@include stroked-button(mat-color($primary, 400), mat-color($primary, 400));
&.focused {
background: rgba(mat-color($primary, 300), 0.1);
}
@include focused-background(rgba(mat-color($primary, 300), 0.1));
}
&.dsh-accent {
@include stroked-button(mat-color($accent, 300), mat-color($accent, 300));
&.focused {
background: rgba(mat-color($accent, 200), 0.1);
}
@include focused-background(rgba(mat-color($accent, 200), 0.1));
}
&.dsh-warn {
@include stroked-button(mat-color($warn, 400), mat-color($warn, 400));
&.focused {
background: rgba(mat-color($warn, 300), 0.1);
}
@include focused-background(rgba(mat-color($warn, 300), 0.1));
}
&:disabled {

View File

@ -1,11 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DshButtonComponent } from './button';
@NgModule({
imports: [CommonModule],
exports: [DshButtonComponent],
declarations: [DshButtonComponent]
})
export class DshButtonModule {}

View File

@ -0,0 +1,2 @@
<span class="dsh-button-content"><ng-content></ng-content></span>
<dsh-glow *ngIf="glowAllowed" [color]="color" [target]="button"></dsh-glow>

View File

@ -0,0 +1,22 @@
@import './button-base';
.dsh {
&-button,
&-stroked-button {
@include dsh-button-base();
&:active:not(:disabled) {
transform: translateY(1px);
}
}
&-stroked-button {
border: $dsh-stroked-button-border-width solid currentColor;
line-height: $dsh-stroked-button-line-height;
}
&-button-content {
position: relative;
z-index: 1;
}
}

View File

@ -1,7 +1,7 @@
import { async, TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { DshButtonModule } from './index';
import { ButtonModule } from './index';
import { ThemePalette } from '@angular/material/core';
/** Test component that contains an DshButton. */
@ -33,7 +33,7 @@ class TestAppComponent {
describe('DshButton', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [DshButtonModule],
imports: [ButtonModule],
declarations: [TestAppComponent]
});

View File

@ -0,0 +1,83 @@
import {
Component,
ElementRef,
OnChanges,
Renderer2,
SimpleChanges,
ViewEncapsulation,
ChangeDetectionStrategy
} from '@angular/core';
import { mixinDisabled, CanDisableCtor } from '@angular/material/core';
import { Platform } from '@angular/cdk/platform';
import { FocusManager } from './focus-manager';
import { ColorManager } from './color-manager';
const BUTTON_HOST_ATTRIBUTES = ['dsh-button', 'dsh-stroked-button'];
// Boilerplate for applying mixins to MatButton.
class MatButtonBase {
constructor(public _elementRef: ElementRef) {}
}
const _MatButtonMixinBase: CanDisableCtor & typeof MatButtonBase = mixinDisabled(MatButtonBase);
@Component({
/* tslint:disable */
selector: `button[dsh-button], button[dsh-stroked-button]`,
host: {
'[attr.disabled]': 'disabled || null'
},
inputs: ['disabled', 'color'],
/* tslint:enable */
exportAs: 'dshButton',
templateUrl: 'button.component.html',
styleUrls: ['button.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonComponent extends _MatButtonMixinBase implements OnChanges {
glowAllowed = false;
private button: HTMLButtonElement;
private colorManager: ColorManager;
constructor(elementRef: ElementRef, private renderer: Renderer2, private platform: Platform) {
super(elementRef);
const button = elementRef.nativeElement as HTMLButtonElement;
this.button = button;
// For each of the variant selectors that is prevent in the button's host
// attributes, add the correct corresponding class.
for (const attr of BUTTON_HOST_ATTRIBUTES) {
if (this.hasHostAttributes(attr)) {
button.classList.add(attr);
}
}
this.colorManager = new ColorManager(renderer, button);
new FocusManager(this.renderer).register(this.button);
this.glowAllowed = this.initGlowAvailability();
}
ngOnChanges({ color, disabled }: SimpleChanges) {
if (color && color.previousValue !== color.currentValue) {
this.colorManager.set(color.currentValue);
this.colorManager.remove(color.previousValue);
}
if (disabled && typeof disabled.currentValue === 'boolean') {
this.glowAllowed = !disabled.currentValue;
}
}
private initGlowAvailability(): boolean {
return !this.platform.ANDROID && !this.platform.IOS && !this.isStrokedButton() && !this.button.disabled;
}
private isStrokedButton(): boolean {
return this.button.classList.contains('dsh-stroked-button');
}
private hasHostAttributes(...attributes: string[]): boolean {
return attributes.some(attribute => this.button.hasAttribute(attribute));
}
}

View File

@ -1,4 +0,0 @@
<span class="dsh-button-text"><ng-content></ng-content></span>
<div class="dsh-button-glow-wrapper">
<span class="dsh-button-glow"></span>
</div>

View File

@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { PlatformModule } from '@angular/cdk/platform';
import { ButtonComponent } from './button.component';
import { GlowComponent } from './glow/glow.component';
@NgModule({
imports: [CommonModule, PlatformModule],
exports: [ButtonComponent],
declarations: [ButtonComponent, GlowComponent]
})
export class ButtonModule {}

View File

@ -1,84 +0,0 @@
$base-button-border-radius: 25px;
$base-button-border: 2px;
$button-padding: 10px 25px;
$stroked-button-padding: 8px 25px;
@keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.dsh {
&-button,
&-stroked-button {
display: inline-block;
position: relative;
border: $base-button-border;
border-radius: $base-button-border-radius;
&:active:not(:disabled) {
transform: translateY(1px);
}
&:not(:disabled) {
cursor: pointer;
}
}
&-stroked-button {
padding: $stroked-button-padding;
}
&-button {
padding: $button-padding;
&-text {
position: relative;
z-index: 1;
}
&-glow {
display: block;
position: absolute;
height: 160px;
width: 160px;
top: -80px;
left: -80px;
opacity: 0;
border-radius: 100%;
z-index: 0;
transition: opactity 0.5s;
animation: hide 0.5s;
&.show {
animation: show 0.5s;
opacity: 1;
}
&-wrapper {
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
border-radius: 25px;
overflow: hidden;
z-index: 0;
}
}
}
}

View File

@ -1,77 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
HostBinding,
Input,
OnChanges,
OnInit,
Renderer2,
SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import { ThemePalette } from '@angular/material/core';
import { GlowManager } from './glow-manager';
import { FocusManager } from './focus-manager';
const BUTTON_HOST_ATTRIBUTES = ['dsh-button', 'dsh-stroked-button'];
@Component({
selector: `dsh-button, button[dsh-button], dsh-stroked-button, button[dsh-stroked-button]`,
exportAs: 'dshButton',
templateUrl: 'button.html',
styleUrls: ['button.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DshButtonComponent implements OnInit, OnChanges {
@HostBinding('attr.role') disabled;
@Input() color: ThemePalette;
private button: HTMLButtonElement;
constructor(elementRef: ElementRef, private renderer: Renderer2) {
const button = elementRef.nativeElement as HTMLButtonElement;
this.button = button;
// For each of the variant selectors that is prevent in the button's host
// attributes, add the correct corresponding class.
for (const attr of BUTTON_HOST_ATTRIBUTES) {
if (this.hasHostAttributes(attr)) {
button.classList.add(attr);
}
}
}
ngOnChanges(changes: SimpleChanges) {
const color = changes.color;
if (color.previousValue !== color.currentValue) {
this.setColor(color.currentValue);
this.removeColor(color.previousValue);
}
}
ngOnInit() {
if (this.isGlowAllowed()) {
new GlowManager(this.renderer).register(this.button);
}
new FocusManager(this.renderer).register(this.button);
}
private setColor(color: ThemePalette) {
this.renderer.addClass(this.button, `dsh-${color}`);
}
private removeColor(color: ThemePalette) {
this.renderer.removeClass(this.button, `dsh-${color}`);
}
private isGlowAllowed(): boolean {
return !this.button.classList.contains('dsh-stroked-button');
}
private hasHostAttributes(...attributes: string[]): boolean {
return attributes.some(attribute => this.button.hasAttribute(attribute));
}
}

View File

@ -0,0 +1,14 @@
import { Renderer2 } from '@angular/core';
import { ThemePalette } from '@angular/material';
export class ColorManager {
constructor(private renderer: Renderer2, private target: HTMLElement, private preffix = 'dsh') {}
set(color: ThemePalette) {
this.renderer.addClass(this.target, `${this.preffix}-${color}`);
}
remove(color: ThemePalette) {
this.renderer.removeClass(this.target, `${this.preffix}-${color}`);
}
}

View File

@ -8,6 +8,7 @@ export class FocusManager {
this.renderer.listen(t, 'mousedown', this.drawFocus.bind(this, t));
this.renderer.listen(t, 'focusout', this.hideFocus.bind(this, t));
this.renderer.listen(t, 'mouseup', this.hideFocus.bind(this, t));
this.renderer.listen(t, 'mouseleave', this.hideFocus.bind(this, t));
}
private drawFocus(el: HTMLButtonElement) {

View File

@ -1,15 +1,9 @@
import { Renderer2 } from '@angular/core';
export class GlowManager {
private glowEl: HTMLElement;
constructor(private renderer: Renderer2) {}
constructor(private renderer: Renderer2, private glowEl: HTMLElement) {}
register(t: HTMLButtonElement) {
this.glowEl = t.querySelector('.dsh-button-glow');
if (!this.glowEl) {
throw new Error('Glow element not found');
}
this.renderer.listen(t, 'mouseenter', this.showGlow.bind(this));
this.renderer.listen(t, 'mouseleave', this.hideGlow.bind(this));
this.renderer.listen(t, 'mousemove', this.moveGlow.bind(this, t));

View File

@ -0,0 +1 @@
<span class="dsh-glow"></span>

View File

@ -0,0 +1,41 @@
@import '../button-base';
$dsh-glow-height: $dsh-button-line-height;
$dsh-glow-border-radius: $dsh-button-border-radius;
@keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
:host {
position: absolute;
height: $dsh-glow-height;
width: 100%;
left: 0;
top: 0;
border-radius: $dsh-glow-border-radius;
overflow: hidden;
z-index: 0;
}
.dsh-glow {
display: block;
position: absolute;
height: 160px;
width: 160px;
top: -80px;
left: -80px;
opacity: 0;
border-radius: 100%;
z-index: 0;
&.show {
animation: show 0.5s;
opacity: 1;
}
}

View File

@ -0,0 +1,34 @@
import { Component, Input, OnInit, Renderer2, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { ThemePalette } from '@angular/material';
import { GlowManager } from './glow-manager';
import { ColorManager } from '../color-manager';
@Component({
selector: 'dsh-glow',
templateUrl: 'glow.component.html',
styleUrls: ['glow.component.scss']
})
export class GlowComponent implements OnInit, OnChanges {
@Input() target: HTMLButtonElement;
@Input() color: ThemePalette;
private glowEl: HTMLElement;
private colorManager: ColorManager;
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
ngOnChanges({ color }: SimpleChanges) {
if (this.glowEl && color && color.previousValue !== color.currentValue) {
this.colorManager.set(color.currentValue);
this.colorManager.remove(color.previousValue);
}
}
ngOnInit() {
this.glowEl = this.elementRef.nativeElement.querySelector('.dsh-glow');
this.colorManager = new ColorManager(this.renderer, this.glowEl);
new GlowManager(this.renderer, this.glowEl).register(this.target);
this.colorManager.set(this.color);
}
}

View File

@ -1 +1,2 @@
export * from './public-api';
export * from './button.module';
export * from './button.component';

View File

@ -1,2 +0,0 @@
export * from './button-module';
export * from './button';

View File

@ -8,6 +8,6 @@
<button dsh-button color="warn">Warn</button>
<button dsh-stroked-button color="warn">Warn</button>
<button dsh-button [disabled]="true" color="accent">Disabled</button>
<button dsh-stroked-button disabled color="accent">Disabled</button>
<button dsh-stroked-button color="accent" disabled>Disabled</button>
</dsh-card-content>
</dsh-card>

View File

@ -2,11 +2,11 @@ import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ButtonsComponent } from './buttons.component';
import { DshButtonModule } from '../../button';
import { ButtonModule } from '../../button';
import { CardModule } from '../../layout/card';
@NgModule({
declarations: [ButtonsComponent],
imports: [FlexLayoutModule, DshButtonModule, CardModule]
imports: [FlexLayoutModule, ButtonModule, CardModule]
})
export class ButtonsModule {}

View File

@ -19,7 +19,7 @@ import { OnboardingService } from './onboarding.service';
import { LegalEntityComponent } from './legal-entity/legal-entity.component';
import { LayoutComponent } from './layout/layout.component';
import { StateNavModule } from '../../state-nav/state-nav.module';
import { DshButtonModule } from '../../button';
import { ButtonModule } from '../../button';
@NgModule({
imports: [
@ -36,7 +36,7 @@ import { DshButtonModule } from '../../button';
MatSelectModule,
MatDatepickerModule,
StateNavModule,
DshButtonModule
ButtonModule
],
declarations: [OnboardingComponent, LegalEntityComponent, LayoutComponent],
entryComponents: [],

View File

@ -9,7 +9,7 @@
$warn: map-get($theme, warn);
$warn-text: map-get($warn, contrast);
.item {
.dsh-step-nav-item {
color: map-get($foreground, text);
// The class order directly affects the order of applying styles (for example, both .warn and .active)
@ -29,7 +29,7 @@
}
@mixin dsh-state-nav-typography($config) {
.item {
.dsh-step-nav-item {
font: {
family: mat-font-family($config, body-1);
size: mat-font-size($config, body-1);

View File

@ -5,7 +5,7 @@ $height: 40px;
display: flex;
}
.item {
.dsh-step-nav-item {
display: block;
border: none;
width: 100%;

View File

@ -1,5 +1,5 @@
<li
class="item"
class="dsh-step-nav-item"
(click)="clickHandler($event)"
[ngClass]="{ active: active$ | async, success: color === 'success', warn: color === 'warn' }"
>

View File

@ -7,10 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link
href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i&subset=cyrillic"
rel="stylesheet"
/>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,&subset=cyrillic" rel="stylesheet" />
</head>
<body>
<dsh-root></dsh-root>