mirror of
https://github.com/valitydev/dashboard.git
synced 2024-11-06 10:35:21 +00:00
FE-848: Float panel (#30)
This commit is contained in:
parent
387da3ad4d
commit
94c4c8240c
6
.vsc-templates/README.md
Normal file
6
.vsc-templates/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# VSC scaffolding
|
||||
|
||||
- [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=alfnielsen.vsc-scaffolding)
|
||||
|
||||
- [Documentation](http://vsc-base.org/)
|
||||
- [Source code](https://github.com/alfnielsen/vsc-base)
|
67
.vsc-templates/component.vsc-template.ts
Normal file
67
.vsc-templates/component.vsc-template.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import * as vsc from 'vsc-base';
|
||||
|
||||
const PREFIX = 'dsh';
|
||||
|
||||
function getComponentName(name: string) {
|
||||
return `${vsc.toPascalCase(name)}Component`;
|
||||
}
|
||||
|
||||
function getComponentPath(name: string) {
|
||||
return `${vsc.toKebabCase(name)}.component`;
|
||||
}
|
||||
|
||||
export function Template(path: string, templatePath: string): vsc.vscTemplate {
|
||||
return {
|
||||
userInputs: [
|
||||
{
|
||||
title: 'Component name',
|
||||
argumentName: 'name',
|
||||
defaultValue: ''
|
||||
}
|
||||
],
|
||||
template: [
|
||||
{
|
||||
type: 'folder',
|
||||
name: inputs => vsc.toKebabCase(inputs.name),
|
||||
children: [
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getComponentPath(inputs.name)}.html`,
|
||||
content: inputs => ``
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getComponentPath(inputs.name)}.scss`,
|
||||
content: inputs => `
|
||||
.${PREFIX}-${vsc.toKebabCase(inputs.name)} {
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getComponentPath(inputs.name)}.ts`,
|
||||
content: inputs => `
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${PREFIX}-${vsc.toKebabCase(inputs.name)}',
|
||||
templateUrl: '${getComponentPath(inputs.name)}.html',
|
||||
styleUrls: ['${getComponentPath(inputs.name)}.scss']
|
||||
})
|
||||
export class ${getComponentName(inputs.name)} {
|
||||
constructor() {}
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `index.ts`,
|
||||
content: inputs => `
|
||||
export * from './${getComponentPath(inputs.name)}'
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
92
.vsc-templates/module-with-component.vsc-template.ts
Normal file
92
.vsc-templates/module-with-component.vsc-template.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import * as vsc from 'vsc-base';
|
||||
|
||||
const PREFIX = 'dsh';
|
||||
|
||||
function getModuletName(name: string) {
|
||||
return `${vsc.toPascalCase(name)}Module`;
|
||||
}
|
||||
|
||||
function getModulePath(name: string) {
|
||||
return `${vsc.toKebabCase(name)}.module`;
|
||||
}
|
||||
|
||||
function getComponentName(name: string) {
|
||||
return `${vsc.toPascalCase(name)}Component`;
|
||||
}
|
||||
|
||||
function getComponentPath(name: string) {
|
||||
return `${vsc.toKebabCase(name)}.component`;
|
||||
}
|
||||
|
||||
export function Template(path: string, templatePath: string): vsc.vscTemplate {
|
||||
return {
|
||||
userInputs: [
|
||||
{
|
||||
title: 'Component name',
|
||||
argumentName: 'name',
|
||||
defaultValue: ''
|
||||
}
|
||||
],
|
||||
template: [
|
||||
{
|
||||
type: 'folder',
|
||||
name: inputs => vsc.toKebabCase(inputs.name),
|
||||
children: [
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getModulePath(inputs.name)}.ts`,
|
||||
content: inputs => `
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { ${getComponentName(inputs.name)} } from './${getComponentPath(inputs.name)}';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [${getComponentName(inputs.name)}],
|
||||
declarations: [${getComponentName(inputs.name)}]
|
||||
})
|
||||
export class ${getModuletName(inputs.name)} {}
|
||||
`
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getComponentPath(inputs.name)}.html`,
|
||||
content: inputs => ``
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getComponentPath(inputs.name)}.scss`,
|
||||
content: inputs => `
|
||||
.${PREFIX}-${vsc.toKebabCase(inputs.name)} {
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `${getComponentPath(inputs.name)}.ts`,
|
||||
content: inputs => `
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${PREFIX}-${vsc.toKebabCase(inputs.name)}',
|
||||
templateUrl: '${getComponentPath(inputs.name)}.html',
|
||||
styleUrls: ['${getComponentPath(inputs.name)}.scss']
|
||||
})
|
||||
export class ${getComponentName(inputs.name)} {
|
||||
constructor() {}
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: inputs => `index.ts`,
|
||||
content: inputs => `
|
||||
export * from './${getModulePath(inputs.name)}';
|
||||
export * from './${getComponentPath(inputs.name)}'
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
79
package-lock.json
generated
79
package-lock.json
generated
@ -1224,6 +1224,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/child-process-promise": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/child-process-promise/-/child-process-promise-2.2.1.tgz",
|
||||
"integrity": "sha512-xZ4kkF82YkmqPCERqV9Tj0bVQj3Tk36BqGlNgxv5XhifgDRhwAqp+of+sccksdpZRbbPsNwMOkmUqOnLgxKtGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/d3": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.7.2.tgz",
|
||||
@ -1484,6 +1493,15 @@
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz",
|
||||
"integrity": "sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/geojson": {
|
||||
"version": "7946.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
||||
@ -2909,6 +2927,29 @@
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true
|
||||
},
|
||||
"child-process-promise": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz",
|
||||
"integrity": "sha1-RzChHvYQ+tRQuPIjx50x172tgHQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^4.0.2",
|
||||
"node-version": "^1.0.0",
|
||||
"promise-polyfill": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-spawn": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
|
||||
"integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
||||
@ -3459,6 +3500,11 @@
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz",
|
||||
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
|
||||
},
|
||||
"css-element-queries": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.2.0.tgz",
|
||||
"integrity": "sha512-4gaxpioSFueMcp9yj1TJFCLjfooGv38y6ZdwFUS3GuS+9NIVijdeiExXKwSIHoQDADfpgnaYSTzmJs+bV+Hehg=="
|
||||
},
|
||||
"css-parse": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
|
||||
@ -8523,6 +8569,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-version": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz",
|
||||
"integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
@ -9398,6 +9450,12 @@
|
||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
||||
"dev": true
|
||||
},
|
||||
"promise-polyfill": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz",
|
||||
"integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=",
|
||||
"dev": true
|
||||
},
|
||||
"promise-retry": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
|
||||
@ -12106,6 +12164,27 @@
|
||||
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
|
||||
"dev": true
|
||||
},
|
||||
"vsc-base": {
|
||||
"version": "0.8.24",
|
||||
"resolved": "https://registry.npmjs.org/vsc-base/-/vsc-base-0.8.24.tgz",
|
||||
"integrity": "sha512-I71mp/uQBmZ/HCJSqPgeymkNTGLwSonrkVA4Z33F8aedqAnckM4445mxwTj0PZp+l8EzznG3em6z/OUUp3zOOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/child-process-promise": "^2.2.1",
|
||||
"@types/fs-extra": "^5.1.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"fs-extra": "^7.0.1",
|
||||
"typescript": "^3.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz",
|
||||
"integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"watchpack": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
|
||||
|
@ -30,6 +30,7 @@
|
||||
"@angular/router": "~7.2.14",
|
||||
"angular2-text-mask": "^9.0.0",
|
||||
"core-js": "^2.5.4",
|
||||
"css-element-queries": "^1.2.0",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-scale": "^2.1.2",
|
||||
"d3-shape": "^1.2.2",
|
||||
@ -70,6 +71,7 @@
|
||||
"quicktype": "^15.0.187",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.16.0",
|
||||
"typescript": "~3.2.4"
|
||||
"typescript": "~3.2.4",
|
||||
"vsc-base": "^0.8.24"
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
<button routerLink="/analytics">Графики</button>
|
||||
<button routerLink="/table">Таблица</button>
|
||||
<button routerLink="/buttons">Кнопки</button>
|
||||
<button routerLink="/operations">Операции</button>
|
||||
<button routerLink="/inputs">Поля ввода</button>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Directive, Input, HostListener, ViewContainerRef, ElementRef, OnDestroy } from '@angular/core';
|
||||
import { Directive, Input, HostListener, ViewContainerRef, ElementRef, OnDestroy, Renderer2 } from '@angular/core';
|
||||
import { TemplatePortal } from '@angular/cdk/portal';
|
||||
import { OverlayRef, OverlayConfig, Overlay, FlexibleConnectedPositionStrategy } from '@angular/cdk/overlay';
|
||||
import get from 'lodash.get';
|
||||
@ -17,12 +17,15 @@ export class DropdownTriggerDirective implements OnDestroy {
|
||||
@Input('dshDropdownTriggerFor')
|
||||
dropdown: DropdownComponent;
|
||||
|
||||
private removeWindowListenersFns: (() => void)[] = [];
|
||||
|
||||
private _overlayRef: OverlayRef;
|
||||
|
||||
constructor(
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private overlay: Overlay,
|
||||
private origin: ElementRef<HTMLElement>
|
||||
private origin: ElementRef<HTMLElement>,
|
||||
private renderer: Renderer2
|
||||
) {}
|
||||
|
||||
private get dropdownEl(): HTMLElement {
|
||||
@ -42,6 +45,7 @@ export class DropdownTriggerDirective implements OnDestroy {
|
||||
this._overlayRef.dispose();
|
||||
this._overlayRef = null;
|
||||
}
|
||||
this.removeWindowListeners();
|
||||
}
|
||||
|
||||
@HostListener('click')
|
||||
@ -55,15 +59,13 @@ export class DropdownTriggerDirective implements OnDestroy {
|
||||
this.overlayRef.attach(portal);
|
||||
this.dropdown.state = State.open;
|
||||
this.updatePosition();
|
||||
window.addEventListener('mousedown', this.backdropClickHandler);
|
||||
window.addEventListener('keyup', this.keypressHandler);
|
||||
this.addWindowListeners();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dropdown.state = State.closed;
|
||||
window.removeEventListener('mousedown', this.backdropClickHandler);
|
||||
window.removeEventListener('keyup', this.keypressHandler);
|
||||
this.removeWindowListeners();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
@ -76,6 +78,20 @@ export class DropdownTriggerDirective implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private addWindowListeners() {
|
||||
this.removeWindowListenersFns.push(
|
||||
this.renderer.listen(window, 'mousedown', this.backdropClickHandler),
|
||||
this.renderer.listen(window, 'keyup', this.keypressHandler)
|
||||
);
|
||||
}
|
||||
|
||||
private removeWindowListeners() {
|
||||
let unlisten: () => void;
|
||||
while ((unlisten = this.removeWindowListenersFns.pop())) {
|
||||
unlisten();
|
||||
}
|
||||
}
|
||||
|
||||
private getPortal(): TemplatePortal {
|
||||
return new TemplatePortal(this.dropdown.templateRef, this.viewContainerRef);
|
||||
}
|
||||
|
@ -1,9 +1,25 @@
|
||||
@import '~@angular/material/theming';
|
||||
@import '../../../styles/shadow';
|
||||
|
||||
@mixin dsh-card-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.card {
|
||||
.dsh-card {
|
||||
@include dsh-shadow($theme);
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dsh-card-typography($config) {
|
||||
.dsh-card {
|
||||
font-family: mat-font-family($config);
|
||||
}
|
||||
|
||||
.dsh-card-header .dsh-card-title {
|
||||
font-size: mat-font-size($config, title);
|
||||
}
|
||||
|
||||
.dsh-card-content {
|
||||
font-size: mat-font-size($config, body-1);
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
<ng-content></ng-content>
|
@ -1,4 +0,0 @@
|
||||
:host {
|
||||
display: block;
|
||||
margin: 30px 20px;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card-content',
|
||||
templateUrl: './card-content.component.html',
|
||||
styleUrls: ['./card-content.component.scss']
|
||||
})
|
||||
export class CardContentComponent {}
|
@ -1 +0,0 @@
|
||||
<ng-content></ng-content>
|
@ -1,4 +0,0 @@
|
||||
:host {
|
||||
display: block;
|
||||
margin: 30px 20px;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card-header',
|
||||
templateUrl: './card-header.component.html',
|
||||
styleUrls: ['./card-header.component.scss']
|
||||
})
|
||||
export class CardHeaderComponent {}
|
@ -1 +0,0 @@
|
||||
<ng-content></ng-content>
|
@ -1,5 +0,0 @@
|
||||
:host {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card-title',
|
||||
templateUrl: './card-title.component.html',
|
||||
styleUrls: ['./card-title.component.scss']
|
||||
})
|
||||
export class CardTitleComponent {
|
||||
constructor() {}
|
||||
}
|
@ -1 +0,0 @@
|
||||
<div class="card"><ng-content></ng-content></div>
|
@ -1,7 +1,34 @@
|
||||
.card {
|
||||
$dsh-card-padding: 20px !default;
|
||||
$dsh-card-border-radius: 4px !default;
|
||||
|
||||
%dsh-card-section-base {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 8px 20px 0 rgba(174, 188, 230, 0.24);
|
||||
// fix of margin problem in child blocks
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dsh-card {
|
||||
@extend %dsh-card-section-base;
|
||||
position: relative;
|
||||
padding: $dsh-card-padding;
|
||||
border-radius: $dsh-card-border-radius;
|
||||
}
|
||||
|
||||
.dsh-card-content {
|
||||
@extend %dsh-card-section-base;
|
||||
}
|
||||
|
||||
.dsh-card-actions {
|
||||
@extend %dsh-card-section-base;
|
||||
margin-left: -$dsh-card-padding / 2;
|
||||
margin-right: -$dsh-card-padding / 2;
|
||||
margin-bottom: -$dsh-card-padding / 2;
|
||||
padding-top: $dsh-card-padding;
|
||||
}
|
||||
|
||||
.dsh-card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.dsh-card-title {
|
||||
margin-bottom: $dsh-card-padding;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,54 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewEncapsulation, ChangeDetectionStrategy, HostBinding, Directive } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card',
|
||||
templateUrl: './card.component.html',
|
||||
styleUrls: ['./card.component.scss']
|
||||
styleUrls: ['card.component.scss'],
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CardComponent {}
|
||||
export class CardComponent {
|
||||
@HostBinding('class.dsh-card') class = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card-header',
|
||||
template: `
|
||||
<ng-content select="dsh-card-title, [dsh-card-title], [dshCardTitle]"></ng-content>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CardHeaderComponent {
|
||||
@HostBinding('class.dsh-card-header') class = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card-content',
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
`
|
||||
})
|
||||
export class CardContentComponent {
|
||||
@HostBinding('class.dsh-card-content') class = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-card-actions',
|
||||
exportAs: 'dshCardActions',
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
`
|
||||
})
|
||||
export class CardActionsComponent {
|
||||
@HostBinding('class.dsh-card-actions') class = true;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: `dsh-card-title, [dsh-card-title], [dshCardTitle]`
|
||||
})
|
||||
export class CardTitleDirective {
|
||||
@HostBinding('class.dsh-card-title') class = true;
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CardComponent } from './card.component';
|
||||
import { CardContentComponent } from './card-content/card-content.component';
|
||||
import { CardHeaderComponent } from './card-header/card-header.component';
|
||||
import { CardTitleComponent } from './card-header/card-title/card-title.component';
|
||||
import {
|
||||
CardComponent,
|
||||
CardContentComponent,
|
||||
CardTitleDirective,
|
||||
CardHeaderComponent,
|
||||
CardActionsComponent
|
||||
} from './card.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [CardComponent, CardContentComponent, CardHeaderComponent, CardTitleComponent],
|
||||
exports: [CardComponent, CardContentComponent, CardHeaderComponent, CardTitleComponent],
|
||||
providers: []
|
||||
imports: [CommonModule],
|
||||
declarations: [CardComponent, CardContentComponent, CardTitleDirective, CardHeaderComponent, CardActionsComponent],
|
||||
exports: [CardComponent, CardContentComponent, CardTitleDirective, CardHeaderComponent, CardActionsComponent]
|
||||
})
|
||||
export class CardModule {}
|
||||
|
22
src/app/layout/float-panel/_float-panel-theme.scss
Normal file
22
src/app/layout/float-panel/_float-panel-theme.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@import '~@angular/material/theming';
|
||||
@import '../../../styles/shadow';
|
||||
|
||||
@mixin dsh-float-panel-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.dsh-float-panel {
|
||||
&-card {
|
||||
@include dsh-shadow($theme);
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dsh-float-panel-typography($config) {
|
||||
.dsh-float-panel {
|
||||
&-template-wrapper {
|
||||
font-family: mat-font-family($config);
|
||||
font-size: mat-font-size($config, body-1);
|
||||
}
|
||||
}
|
||||
}
|
14
src/app/layout/float-panel/animations/expand-animation.ts
Normal file
14
src/app/layout/float-panel/animations/expand-animation.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||
|
||||
export enum ExpandState {
|
||||
expanded = 'expanded',
|
||||
collapsed = 'collapsed'
|
||||
}
|
||||
|
||||
const animation = animate('150ms ease');
|
||||
|
||||
export const expandAnimation = trigger('expand', [
|
||||
state(ExpandState.expanded, style({ height: '{{height}}px' }), { params: { height: 0 } }),
|
||||
transition(`${ExpandState.collapsed} <=> ${ExpandState.expanded}`, [animation]),
|
||||
transition(`${ExpandState.expanded} => void`, [animation])
|
||||
]);
|
6
src/app/layout/float-panel/animations/hide-animation.ts
Normal file
6
src/app/layout/float-panel/animations/hide-animation.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { trigger, style, transition, animate } from '@angular/animations';
|
||||
|
||||
export const hideAnimation = trigger('hide', [
|
||||
transition(':enter', [style({ opacity: 0 }), animate('.25s ease', style({ opacity: 1 }))]),
|
||||
transition(':leave', [style({ opacity: 1 }), animate('.25s ease', style({ opacity: 0 }))])
|
||||
]);
|
42
src/app/layout/float-panel/float-panel-overlay.service.ts
Normal file
42
src/app/layout/float-panel/float-panel-overlay.service.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Injectable, ElementRef } from '@angular/core';
|
||||
import { OverlayRef, Overlay, OverlayConfig } from '@angular/cdk/overlay';
|
||||
import { TemplatePortal } from '@angular/cdk/portal';
|
||||
|
||||
@Injectable()
|
||||
export class FloatPanelOverlayService {
|
||||
private overlayRef: OverlayRef;
|
||||
|
||||
constructor(private overlay: Overlay) {}
|
||||
|
||||
attach(templatePortal: TemplatePortal, elementRef: ElementRef, config: { width: number }) {
|
||||
this.detach();
|
||||
this.overlayRef = this.overlay.create(this.createConfig(elementRef, config));
|
||||
this.overlayRef.attach(templatePortal);
|
||||
}
|
||||
|
||||
detach() {
|
||||
if (this.overlayRef && this.overlayRef.hasAttached()) {
|
||||
this.overlayRef.detach();
|
||||
}
|
||||
}
|
||||
|
||||
updateSize({ width }: { width: number }) {
|
||||
if (this.overlayRef) {
|
||||
this.overlayRef.updateSize({ width: `${width}px` });
|
||||
}
|
||||
}
|
||||
|
||||
private createConfig(elementRef: ElementRef, { width }: { width: number }): OverlayConfig {
|
||||
const positionStrategy = this.overlay
|
||||
.position()
|
||||
.connectedTo(elementRef, { originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'top' });
|
||||
|
||||
const overlayConfig = new OverlayConfig({
|
||||
width: `${width}px`,
|
||||
positionStrategy,
|
||||
scrollStrategy: this.overlay.scrollStrategies.reposition()
|
||||
});
|
||||
|
||||
return overlayConfig;
|
||||
}
|
||||
}
|
53
src/app/layout/float-panel/float-panel.component.html
Normal file
53
src/app/layout/float-panel/float-panel.component.html
Normal file
@ -0,0 +1,53 @@
|
||||
<div class="dsh-float-panel">
|
||||
<div
|
||||
#substrate
|
||||
(dshResized)="onSubstrateResize($event)"
|
||||
[ngStyle]="{ height: pinned ? 0 : cardHeight.base + 'px' }"
|
||||
></div>
|
||||
<div [ngStyle]="{ height: cardHeight.current + 'px' }" class="dsh-float-panel-body-extender"></div>
|
||||
<ng-container #container [ngTemplateOutlet]="template" *ngIf="pinned"></ng-container>
|
||||
</div>
|
||||
<ng-template #template>
|
||||
<div class="dsh-float-panel-template-wrapper">
|
||||
<div
|
||||
class="dsh-float-panel-card"
|
||||
(dshResized)="setCardHeight($event.height)"
|
||||
[ngClass]="{ 'dsh-float-panel-card-expanded': expanded }"
|
||||
>
|
||||
<div class="dsh-float-panel-base" fxLayout="row" fxLayoutGap="20px" fxLayoutAlign=" center">
|
||||
<div fxFlex><ng-content></ng-content></div>
|
||||
<div @hide *ngIf="!expanded" class="dsh-float-panel-base-action">
|
||||
<button dsh-icon-button (click)="expandToggle()">
|
||||
<mat-icon svgIcon="user"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="dsh-float-panel-more"
|
||||
[@expand]="expandTrigger"
|
||||
(@expand.start)="expandStart($event)"
|
||||
(@expand.done)="expandDone($event)"
|
||||
*ngIf="expanded"
|
||||
>
|
||||
<div (dshResized)="setMoreContentHeight($event.height)">
|
||||
<div fxLayout="column" fxLayoutGap="20px">
|
||||
<div class="dsh-float-panel-more-content">
|
||||
<ng-container *ngTemplateOutlet="floatPanelMore?.templateRef"></ng-container>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="20px" fxLayoutAlign="space-between">
|
||||
<button dsh-icon-button (click)="pinToggle()">
|
||||
<mat-icon svgIcon="notification"></mat-icon>
|
||||
</button>
|
||||
<div>
|
||||
<ng-container *ngTemplateOutlet="floatPanelActions?.templateRef"></ng-container>
|
||||
</div>
|
||||
<button dsh-icon-button (click)="expandToggle()">
|
||||
<mat-icon svgIcon="user"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
51
src/app/layout/float-panel/float-panel.component.scss
Normal file
51
src/app/layout/float-panel/float-panel.component.scss
Normal file
@ -0,0 +1,51 @@
|
||||
$base-height: 51.313px;
|
||||
$card-padding: 20px !default;
|
||||
$actions-padding: 10px !default;
|
||||
$card-border-radius: 4px !default;
|
||||
$card-without-actions-padding: $card-padding - $actions-padding;
|
||||
|
||||
.dsh-float-panel {
|
||||
position: relative;
|
||||
|
||||
&-body-extender {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin-bottom: $card-padding;
|
||||
}
|
||||
|
||||
&-template-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-card {
|
||||
width: 100%;
|
||||
border-radius: $card-border-radius;
|
||||
padding: $card-padding $actions-padding $card-padding $actions-padding;
|
||||
transition: padding 150ms ease;
|
||||
|
||||
&-expanded {
|
||||
padding-bottom: $actions-padding;
|
||||
}
|
||||
}
|
||||
|
||||
&-base {
|
||||
min-height: $base-height;
|
||||
margin: 0 $card-without-actions-padding;
|
||||
|
||||
&-action {
|
||||
margin-right: -$card-without-actions-padding;
|
||||
}
|
||||
}
|
||||
|
||||
&-more {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-content {
|
||||
padding: 0 $card-without-actions-padding;
|
||||
}
|
||||
}
|
||||
}
|
148
src/app/layout/float-panel/float-panel.component.ts
Normal file
148
src/app/layout/float-panel/float-panel.component.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import {
|
||||
Component,
|
||||
ContentChild,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
Input,
|
||||
TemplateRef,
|
||||
AfterViewInit,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewContainerRef,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { TemplatePortal } from '@angular/cdk/portal';
|
||||
import { AnimationEvent } from '@angular/animations';
|
||||
|
||||
import { FloatPanelMoreTemplateComponent } from './templates/float-panel-more-template.component';
|
||||
import { FloatPanelActionsTemplateComponent } from './templates/float-panel-actions-template.component';
|
||||
import { expandAnimation, ExpandState } from './animations/expand-animation';
|
||||
import { hideAnimation } from './animations/hide-animation';
|
||||
import { FloatPanelOverlayService } from './float-panel-overlay.service';
|
||||
import { ResizedEvent } from '../../resized';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-float-panel',
|
||||
templateUrl: 'float-panel.component.html',
|
||||
styleUrls: ['float-panel.component.scss'],
|
||||
animations: [expandAnimation, hideAnimation],
|
||||
providers: [FloatPanelOverlayService]
|
||||
})
|
||||
export class FloatPanelComponent implements AfterViewInit, OnDestroy {
|
||||
private _expanded = false;
|
||||
@Input()
|
||||
get expanded() {
|
||||
return this._expanded;
|
||||
}
|
||||
set expanded(expanded) {
|
||||
this.expandedChange.emit((this._expanded = expanded !== false));
|
||||
}
|
||||
@Output() expandedChange = new EventEmitter<boolean>();
|
||||
|
||||
private _pinned = false;
|
||||
@Input()
|
||||
get pinned() {
|
||||
return this._pinned;
|
||||
}
|
||||
set pinned(pinned) {
|
||||
this.pinnedChange.emit((this._pinned = pinned !== false));
|
||||
}
|
||||
@Output() pinnedChange = new EventEmitter<boolean>();
|
||||
|
||||
@ContentChild(FloatPanelMoreTemplateComponent) floatPanelMore: FloatPanelMoreTemplateComponent;
|
||||
|
||||
@ContentChild(FloatPanelActionsTemplateComponent) floatPanelActions: FloatPanelActionsTemplateComponent;
|
||||
|
||||
@ViewChild('template') templateRef: TemplateRef<{}>;
|
||||
|
||||
@ViewChild('substrate') private substrate: ElementRef<HTMLDivElement>;
|
||||
|
||||
expandTrigger: { value: ExpandState; params: { height: number } } | ExpandState = ExpandState.collapsed;
|
||||
|
||||
cardHeight = {
|
||||
base: 0,
|
||||
current: 0
|
||||
};
|
||||
|
||||
private isExpanding = false;
|
||||
|
||||
constructor(
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private floatPanelOverlayService: FloatPanelOverlayService,
|
||||
private ref: ChangeDetectorRef
|
||||
) {
|
||||
this.expandedChange.subscribe(() => this.resetExpandTriggerManage());
|
||||
this.pinnedChange.subscribe(() => this.overlayAttachManage());
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.overlayAttachManage();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.detach();
|
||||
}
|
||||
|
||||
onSubstrateResize({ width }: ResizedEvent) {
|
||||
this.floatPanelOverlayService.updateSize({ width });
|
||||
this.ref.detectChanges();
|
||||
}
|
||||
|
||||
expandStart(e: AnimationEvent) {
|
||||
this.isExpanding = true;
|
||||
}
|
||||
|
||||
expandDone(e: AnimationEvent) {
|
||||
this.isExpanding = false;
|
||||
}
|
||||
|
||||
expandToggle() {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
pinToggle() {
|
||||
this.pinned = !this.pinned;
|
||||
}
|
||||
|
||||
setMoreContentHeight(height: number) {
|
||||
if (height !== 0) {
|
||||
this.expandTrigger = { value: ExpandState.expanded, params: { height } };
|
||||
this.ref.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
setCardHeight(height: number) {
|
||||
if (!this.expanded && !this.isExpanding && height !== 0) {
|
||||
this.cardHeight.base = height;
|
||||
}
|
||||
this.cardHeight.current = height;
|
||||
this.ref.detectChanges();
|
||||
}
|
||||
|
||||
private attach() {
|
||||
this.floatPanelOverlayService.attach(
|
||||
new TemplatePortal(this.templateRef, this.viewContainerRef),
|
||||
this.substrate,
|
||||
{ width: this.substrate.nativeElement.clientWidth }
|
||||
);
|
||||
}
|
||||
|
||||
private detach() {
|
||||
this.floatPanelOverlayService.detach();
|
||||
}
|
||||
|
||||
private resetExpandTriggerManage() {
|
||||
if (!this.expanded) {
|
||||
this.expandTrigger = ExpandState.collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private overlayAttachManage() {
|
||||
if (this.pinned) {
|
||||
this.detach();
|
||||
} else {
|
||||
this.attach();
|
||||
}
|
||||
}
|
||||
}
|
18
src/app/layout/float-panel/float-panel.module.ts
Normal file
18
src/app/layout/float-panel/float-panel.module.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
|
||||
import { FloatPanelComponent } from './float-panel.component';
|
||||
import { FloatPanelMoreTemplateComponent } from './templates/float-panel-more-template.component';
|
||||
import { FloatPanelActionsTemplateComponent } from './templates/float-panel-actions-template.component';
|
||||
import { ButtonModule } from '../../button';
|
||||
import { ResizedModule } from '../../resized';
|
||||
|
||||
@NgModule({
|
||||
imports: [MatIconModule, FlexLayoutModule, CommonModule, ButtonModule, OverlayModule, ResizedModule],
|
||||
declarations: [FloatPanelComponent, FloatPanelMoreTemplateComponent, FloatPanelActionsTemplateComponent],
|
||||
exports: [FloatPanelComponent, FloatPanelMoreTemplateComponent, FloatPanelActionsTemplateComponent]
|
||||
})
|
||||
export class FloatPanelModule {}
|
104
src/app/layout/float-panel/float-panel.spec.ts
Normal file
104
src/app/layout/float-panel/float-panel.spec.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { Component, Type, Provider, ViewChild } from '@angular/core';
|
||||
import { ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { OverlayContainer, FullscreenOverlayContainer } from '@angular/cdk/overlay';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { FloatPanelModule } from './float-panel.module';
|
||||
import { FloatPanelComponent } from './float-panel.component';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-float-panel [pinned]="pinned" [expanded]="expanded" #floatPanel>
|
||||
Базовый фильтр
|
||||
<dsh-float-panel-actions>
|
||||
<button>Сбросить</button>
|
||||
</dsh-float-panel-actions>
|
||||
<dsh-float-panel-more>
|
||||
Фильтр
|
||||
</dsh-float-panel-more>
|
||||
</dsh-float-panel>
|
||||
`
|
||||
})
|
||||
class SimpleFloatPanelComponent {
|
||||
@ViewChild('floatPanel') floatPanel: FloatPanelComponent;
|
||||
pinned = true;
|
||||
expanded = true;
|
||||
}
|
||||
|
||||
const WRAPPER = By.css('.dsh-float-panel-more');
|
||||
|
||||
describe('FloatPanelComponent', () => {
|
||||
let overlayContainer: OverlayContainer;
|
||||
let overlayContainerElement: HTMLElement;
|
||||
|
||||
function createComponent<T>(
|
||||
component: Type<T>,
|
||||
providers: Provider[] = [],
|
||||
declarations: any[] = []
|
||||
): ComponentFixture<T> {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [FloatPanelModule, NoopAnimationsModule],
|
||||
declarations: [component, ...declarations],
|
||||
providers
|
||||
}).compileComponents();
|
||||
|
||||
inject([OverlayContainer], (oc: FullscreenOverlayContainer) => {
|
||||
overlayContainer = oc;
|
||||
overlayContainerElement = oc.getContainerElement();
|
||||
})();
|
||||
|
||||
return TestBed.createComponent<T>(component);
|
||||
}
|
||||
|
||||
afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => {
|
||||
currentOverlayContainer.ngOnDestroy();
|
||||
overlayContainer.ngOnDestroy();
|
||||
}));
|
||||
|
||||
describe('Pin/unpin', () => {
|
||||
it('should pinned', () => {
|
||||
createComponent(SimpleFloatPanelComponent);
|
||||
expect(overlayContainerElement.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should float', () => {
|
||||
const fixture = createComponent(SimpleFloatPanelComponent);
|
||||
fixture.componentInstance.pinned = false;
|
||||
fixture.detectChanges();
|
||||
expect(overlayContainerElement.textContent).not.toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pinned expand/collapse', () => {
|
||||
it('should expanded', () => {
|
||||
const fixture = createComponent(SimpleFloatPanelComponent);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(WRAPPER)).toBeDefined();
|
||||
});
|
||||
|
||||
it('should collapsed', () => {
|
||||
const fixture = createComponent(SimpleFloatPanelComponent);
|
||||
fixture.componentInstance.expanded = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(WRAPPER)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Float expand/collapse', () => {
|
||||
it('should expanded', () => {
|
||||
const fixture = createComponent(SimpleFloatPanelComponent);
|
||||
fixture.componentInstance.pinned = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(WRAPPER)).toBeDefined();
|
||||
});
|
||||
|
||||
it('should collapsed', () => {
|
||||
const fixture = createComponent(SimpleFloatPanelComponent);
|
||||
fixture.componentInstance.pinned = false;
|
||||
fixture.componentInstance.expanded = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(WRAPPER)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
1
src/app/layout/float-panel/index.ts
Normal file
1
src/app/layout/float-panel/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './float-panel.module';
|
@ -0,0 +1,11 @@
|
||||
import { Component, TemplateRef, ViewChild } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-float-panel-actions',
|
||||
template: `
|
||||
<ng-template><ng-content></ng-content></ng-template>
|
||||
`
|
||||
})
|
||||
export class FloatPanelActionsTemplateComponent {
|
||||
@ViewChild(TemplateRef) templateRef: TemplateRef<{}>;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { Component, TemplateRef, ViewChild } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-float-panel-more',
|
||||
template: `
|
||||
<ng-template><ng-content></ng-content></ng-template>
|
||||
`
|
||||
})
|
||||
export class FloatPanelMoreTemplateComponent {
|
||||
@ViewChild(TemplateRef) templateRef: TemplateRef<{}>;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { CardModule } from './card';
|
||||
import { FloatPanelModule } from './float-panel';
|
||||
|
||||
@NgModule({
|
||||
imports: [CardModule],
|
||||
exports: [CardModule]
|
||||
imports: [CardModule, FloatPanelModule],
|
||||
exports: [CardModule, FloatPanelModule]
|
||||
})
|
||||
export class LayoutModule {}
|
||||
|
2
src/app/resized/index.ts
Normal file
2
src/app/resized/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './resized.module';
|
||||
export * from './resized-event';
|
9
src/app/resized/resized-event.ts
Normal file
9
src/app/resized/resized-event.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
export interface ResizedEvent {
|
||||
readonly element: ElementRef;
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
readonly oldWidth?: number;
|
||||
readonly oldHeight?: number;
|
||||
}
|
46
src/app/resized/resized.directive.ts
Normal file
46
src/app/resized/resized.directive.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Directive, ElementRef, EventEmitter, OnInit, Output, OnDestroy } from '@angular/core';
|
||||
import { ResizeSensor } from 'css-element-queries';
|
||||
|
||||
import { ResizedEvent } from './resized-event';
|
||||
|
||||
@Directive({
|
||||
selector: '[dshResized]'
|
||||
})
|
||||
export class ResizedDirective implements OnInit, OnDestroy {
|
||||
@Output() readonly dshResized = new EventEmitter<ResizedEvent>();
|
||||
private currentEvent: ResizedEvent;
|
||||
private resizeSensor: ResizeSensor;
|
||||
|
||||
constructor(private readonly element: ElementRef) {
|
||||
this.currentEvent = {
|
||||
element: this.element,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.resizeSensor = new ResizeSensor(this.element.nativeElement, () => this.resize());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.resizeSensor.detach();
|
||||
}
|
||||
|
||||
private resize() {
|
||||
const width = this.element.nativeElement.clientWidth;
|
||||
const height = this.element.nativeElement.clientHeight;
|
||||
if (width === this.currentEvent.width && height === this.currentEvent.height) {
|
||||
return;
|
||||
}
|
||||
const event = {
|
||||
element: this.element,
|
||||
width,
|
||||
height,
|
||||
oldWidth: this.currentEvent.width,
|
||||
oldHeight: this.currentEvent.height
|
||||
};
|
||||
this.currentEvent = event;
|
||||
this.dshResized.emit(event);
|
||||
}
|
||||
}
|
11
src/app/resized/resized.module.ts
Normal file
11
src/app/resized/resized.module.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ResizedDirective } from './resized.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [ResizedDirective],
|
||||
exports: [ResizedDirective]
|
||||
})
|
||||
export class ResizedModule {}
|
10
src/app/resized/resized.spec.ts
Normal file
10
src/app/resized/resized.spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
import { ResizedDirective } from './resized.directive';
|
||||
|
||||
describe('ResizedDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new ResizedDirective({} as ElementRef);
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
62
src/app/sections/operations/operations.component.html
Normal file
62
src/app/sections/operations/operations.component.html
Normal file
@ -0,0 +1,62 @@
|
||||
<h1 class="mat-headline">Заявка на подключение</h1>
|
||||
<div fxLayout="column" fxLayoutGap="20px">
|
||||
<dsh-float-panel>
|
||||
<mat-form-field style="margin: -3.5px 0 -22.313px 0">
|
||||
<mat-label>Form field</mat-label>
|
||||
<input matInput placeholder="Favorite food" />
|
||||
</mat-form-field>
|
||||
<dsh-float-panel-actions>
|
||||
<button dsh-button>
|
||||
Сбросить параметры поиска
|
||||
</button>
|
||||
</dsh-float-panel-actions>
|
||||
<dsh-float-panel-more>
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
Остальной фильтр<br />
|
||||
</dsh-float-panel-more>
|
||||
</dsh-float-panel>
|
||||
<dsh-card>
|
||||
<dsh-card-content>
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
Последнее обновление: Только что<br />
|
||||
</dsh-card-content>
|
||||
</dsh-card>
|
||||
</div>
|
7
src/app/sections/operations/operations.component.ts
Normal file
7
src/app/sections/operations/operations.component.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'operations.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class OperationsComponent {}
|
13
src/app/sections/operations/operations.module.ts
Normal file
13
src/app/sections/operations/operations.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { MatFormFieldModule, MatInputModule } from '@angular/material';
|
||||
|
||||
import { OperationsComponent } from './operations.component';
|
||||
import { LayoutModule } from '../../layout';
|
||||
import { ButtonModule } from '../../button';
|
||||
|
||||
@NgModule({
|
||||
imports: [LayoutModule, FlexLayoutModule, ButtonModule, MatFormFieldModule, MatInputModule],
|
||||
declarations: [OperationsComponent]
|
||||
})
|
||||
export class OperationsModule {}
|
@ -7,6 +7,7 @@ import { AnalyticsComponent } from './analytics';
|
||||
import { TableComponent } from './table';
|
||||
import { routes as onboargindRoutes } from './onboarding';
|
||||
import { ButtonsComponent } from './buttons';
|
||||
import { OperationsComponent } from './operations/operations.component';
|
||||
import { InputsComponent } from './inputs/inputs.component';
|
||||
|
||||
const routes: Routes = [
|
||||
@ -27,6 +28,10 @@ const routes: Routes = [
|
||||
path: 'buttons',
|
||||
component: ButtonsComponent
|
||||
},
|
||||
{
|
||||
path: 'operations',
|
||||
component: OperationsComponent
|
||||
},
|
||||
{
|
||||
path: 'inputs',
|
||||
component: InputsComponent
|
||||
|
@ -7,6 +7,7 @@ import { PageNotFoundModule } from './page-not-found';
|
||||
import { TableModule } from './table';
|
||||
import { OnboardingModule } from './onboarding';
|
||||
import { ButtonsModule } from './buttons';
|
||||
import { OperationsModule } from './operations/operations.module';
|
||||
import { InputsModule } from './inputs/inputs.module';
|
||||
|
||||
@NgModule({
|
||||
@ -18,6 +19,7 @@ import { InputsModule } from './inputs/inputs.module';
|
||||
TableModule,
|
||||
OnboardingModule,
|
||||
ButtonsModule,
|
||||
OperationsModule,
|
||||
InputsModule
|
||||
],
|
||||
declarations: [],
|
||||
|
12
src/styles/_shadow.scss
Normal file
12
src/styles/_shadow.scss
Normal file
@ -0,0 +1,12 @@
|
||||
@function get-shadow($color, $opacity) {
|
||||
$color: rgba($color, $opacity);
|
||||
|
||||
@return '0px 8px 20px 0px #{$color}';
|
||||
}
|
||||
|
||||
@mixin dsh-shadow($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$shadow-color: mat-color($primary, 100);
|
||||
|
||||
box-shadow: #{get-shadow($shadow-color, 0.24)};
|
||||
}
|
@ -2,12 +2,16 @@
|
||||
@import '../app/button/button-theme';
|
||||
@import '../app/button-toggle/button-toggle-theme';
|
||||
@import '../app/state-nav/state-nav-theme';
|
||||
@import '../app/layout/card/card-theme';
|
||||
@import '../app/layout/float-panel/float-panel-theme';
|
||||
@import '../app/status/status-theme';
|
||||
|
||||
@mixin dsh-typography($config) {
|
||||
@include dsh-button-typography($config);
|
||||
@include dsh-state-nav-typography($config);
|
||||
@include dsh-card-typography($config);
|
||||
@include dsh-button-toggle-typography($config);
|
||||
@include dsh-float-panel-typography($config);
|
||||
@include dsh-status-typography($config);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
@import '../app/table/table-theme';
|
||||
@import '../app/charts/bar-chart/bar-chart-theme';
|
||||
@import '../app/layout/card/card-theme';
|
||||
@import '../app/layout/float-panel/float-panel-theme';
|
||||
@import '../app/charts/linear-chart/linear-chart-theme';
|
||||
@import '../app/state-nav/state-nav-theme';
|
||||
@import '../app/charts/legend-tooltip/legend-tooltip-theme';
|
||||
@ -24,6 +25,7 @@
|
||||
@include dsh-state-nav-theme($theme);
|
||||
@include dsh-legend-tooltip-theme($theme);
|
||||
@include dsh-button-theme($theme);
|
||||
@include dsh-float-panel-theme($theme);
|
||||
@include dsh-button-toggle-theme($theme);
|
||||
@include dsh-status-theme($theme);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user