Reformat code
This commit is contained in:
parent
6e27539eca
commit
420cd788c4
9 changed files with 10630 additions and 2646 deletions
|
|
@ -0,0 +1,13 @@
|
|||
<div class="container">
|
||||
<label for="date-selector-1">date selector 1</label>
|
||||
<label for="date-selector-2">date selector 2</label>
|
||||
<input id="date-selector-1" type="range" min="0" [max]="MAX - 1" [(ngModel)]="oneValue" />
|
||||
<input id="date-selector-2" type="range" min="0" [max]="MAX - 1" [(ngModel)]="otherValue" />
|
||||
<div class="value-container">
|
||||
<span
|
||||
*ngFor="let i of drawnLabelsIndices"
|
||||
[innerHTML]="drawnLabels[i]"
|
||||
[ngStyle]="{ transform: getOffset(i) }"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
@import '../../../../styles';
|
||||
|
||||
$height: 70px;
|
||||
$width: 300px;
|
||||
$slider-size: 40px;
|
||||
|
||||
.container {
|
||||
width: $width;
|
||||
height: $height;
|
||||
|
||||
position: relative;
|
||||
|
||||
margin: $slider-size / 2 auto 0 auto;
|
||||
|
||||
label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
|
||||
height: $slider-size;
|
||||
width: $slider-size;
|
||||
border-radius: 1000px;
|
||||
|
||||
background-color: $light-color;
|
||||
transform-origin: center center;
|
||||
transform: translateY(-$slider-size / 2 + $line-height / 2);
|
||||
|
||||
transition: box-shadow $long-animation-time, transform $long-animation-time;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow;
|
||||
transform: translateY(-$slider-size / 2 + $line-height / 2) scale(1.1);
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
-webkit-appearance: none;
|
||||
|
||||
width: 100%;
|
||||
height: $line-height;
|
||||
background-color: $text-color;
|
||||
border-radius: 1000px;
|
||||
}
|
||||
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.value-container {
|
||||
@include small-text();
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { range } from '../../../utils/range';
|
||||
|
||||
@Component({
|
||||
selector: 'app-double-slider',
|
||||
templateUrl: './double-slider.component.html',
|
||||
styleUrls: ['./double-slider.component.scss']
|
||||
})
|
||||
export class DoubleSliderComponent {
|
||||
@Input() labels: string[];
|
||||
|
||||
@Input() set values(values: any[]) {
|
||||
if (values.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._values = values;
|
||||
this.calculateLabels();
|
||||
this.emitValue();
|
||||
}
|
||||
|
||||
get values(): any[] {
|
||||
return this._values;
|
||||
}
|
||||
|
||||
get oneValue(): number {
|
||||
return this._oneValue;
|
||||
}
|
||||
|
||||
set oneValue(value: number) {
|
||||
this._oneValue = value;
|
||||
this.emitValue();
|
||||
}
|
||||
|
||||
get otherValue(): number {
|
||||
return this._otherValue;
|
||||
}
|
||||
|
||||
set otherValue(value: number) {
|
||||
this._otherValue = value;
|
||||
this.emitValue();
|
||||
}
|
||||
|
||||
private _values: any[];
|
||||
|
||||
@Output() lowerBound: EventEmitter<any> = new EventEmitter();
|
||||
@Output() upperBound: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
drawnLabels: string[];
|
||||
|
||||
readonly MAX = 100;
|
||||
|
||||
private _oneValue = 0;
|
||||
|
||||
private _otherValue: number = this.MAX - 1;
|
||||
|
||||
drawnLabelsIndices: Iterable<number>;
|
||||
|
||||
private calculateLabels() {
|
||||
const labelCount = 6;
|
||||
const jumpLength = Math.round(this.labels.length / labelCount);
|
||||
this.drawnLabels = this.labels.filter((_, index) => index % jumpLength === 0);
|
||||
this.drawnLabelsIndices = range({ max: this.drawnLabels.length });
|
||||
}
|
||||
|
||||
private indexFromValue(value: number): number {
|
||||
return Math.floor((value / this.MAX) * this.values.length);
|
||||
}
|
||||
|
||||
getOffset(index: number): string {
|
||||
const labelIndex = index / this.drawnLabels.length;
|
||||
const slider1Index = this.oneValue / this.MAX - 0.1;
|
||||
const slider2Index = this.otherValue / this.MAX - 0.1;
|
||||
|
||||
const dist = (a, b) => Math.abs(a - b);
|
||||
|
||||
const labelSliderDistance = Math.min(dist(labelIndex, slider1Index), dist(labelIndex, slider2Index));
|
||||
|
||||
const ACTIVE_ZONE = 0.2;
|
||||
const BASE_TRANSFORM = 'translateX(-50%) rotate(-45deg) translateY(100%)';
|
||||
|
||||
if (labelSliderDistance > ACTIVE_ZONE) {
|
||||
return BASE_TRANSFORM;
|
||||
}
|
||||
|
||||
return `translateY(${Math.pow((ACTIVE_ZONE - labelSliderDistance) / ACTIVE_ZONE, 1) * 30}px) ${BASE_TRANSFORM}`;
|
||||
}
|
||||
|
||||
private emitValue() {
|
||||
if (this.oneValue < this.otherValue) {
|
||||
this.lowerBound.emit(this.values[this.indexFromValue(this.oneValue)]);
|
||||
this.upperBound.emit(this.values[this.indexFromValue(this.otherValue)]);
|
||||
} else {
|
||||
this.lowerBound.emit(this.values[this.indexFromValue(this.otherValue)]);
|
||||
this.upperBound.emit(this.values[this.indexFromValue(this.oneValue)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<div class="select-add {{ onlyShadowBorder ? 'shadow-border' : '' }}">
|
||||
<div #top class="top" (click)="toggle()">
|
||||
<p [innerHTML]="selected ? selected : placeholder"></p>
|
||||
<img src="assets/arrow.svg" [className]="isOpen ? 'upside-down' : ''" alt="arrow" />
|
||||
</div>
|
||||
|
||||
<div class="bottom-container">
|
||||
<div #bottom class="bottom {{ isOpen ? 'open' : '' }}">
|
||||
<p *ngFor="let option of otherOptions" [innerHTML]="option" (click)="select(option)"></p>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
*ngIf="options.length <= maxItemCount"
|
||||
placeholder="Add a value…"
|
||||
[(ngModel)]="newOption"
|
||||
(keyup)="handleKeys($event)"
|
||||
/>
|
||||
|
||||
<button *ngIf="options.length <= maxItemCount" (click)="addNewOption()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="background {{ isOpen || alwaysDropShadow ? 'active' : '' }}"
|
||||
[ngStyle]="{ height: backgroundHeight }"
|
||||
></div>
|
||||
</div>
|
||||
117
src/app/components/shared/select-add/select-add.component.scss
Normal file
117
src/app/components/shared/select-add/select-add.component.scss
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
@import '../../../../styles';
|
||||
|
||||
$inner-padding: var(--medium-padding);
|
||||
.select-add {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.top,
|
||||
.bottom {
|
||||
padding: $inner-padding;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
@include sub-title-text();
|
||||
}
|
||||
|
||||
img {
|
||||
@include square(16px);
|
||||
transition: transform $long-animation-time;
|
||||
|
||||
&.upside-down {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
|
||||
position: absolute;
|
||||
overflow-y: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
.bottom {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
pointer-events: all;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
|
||||
padding-top: 0;
|
||||
|
||||
@include inner-spacing($inner-padding);
|
||||
|
||||
transform: translateY(-100%);
|
||||
visibility: hidden;
|
||||
|
||||
&.open {
|
||||
visibility: visible;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
transition: transform $long-animation-time;
|
||||
|
||||
p {
|
||||
@include sub-title-text();
|
||||
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@include card();
|
||||
|
||||
z-index: 3;
|
||||
|
||||
transition: box-shadow $long-animation-time, height $long-animation-time;
|
||||
|
||||
&.active {
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.background {
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
}
|
||||
|
||||
&.shadow-border {
|
||||
.background.active {
|
||||
box-shadow: $shadow-border;
|
||||
}
|
||||
}
|
||||
|
||||
&.shadow-border:hover {
|
||||
.background {
|
||||
box-shadow: $shadow-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/app/components/shared/select-add/select-add.component.ts
Normal file
66
src/app/components/shared/select-add/select-add.component.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-select-add',
|
||||
templateUrl: './select-add.component.html',
|
||||
styleUrls: ['./select-add.component.scss']
|
||||
})
|
||||
export class SelectAddComponent {
|
||||
@Input() placeholder = 'Add a new value…';
|
||||
@Input() maxItemCount = 7;
|
||||
@Input() options: string[];
|
||||
@Input() alwaysDropShadow = false;
|
||||
@Input() onlyShadowBorder = false;
|
||||
|
||||
@Input() set default(value: string) {
|
||||
this.selected = value;
|
||||
if (value) {
|
||||
this.value.emit(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Output() value: EventEmitter<string> = new EventEmitter();
|
||||
|
||||
@ViewChild('top') top: ElementRef;
|
||||
@ViewChild('bottom') bottom: ElementRef;
|
||||
|
||||
selected: string;
|
||||
newOption: string;
|
||||
isOpen = false;
|
||||
|
||||
get otherOptions(): string[] {
|
||||
return this.options.filter(a => a !== this.selected);
|
||||
}
|
||||
|
||||
handleKeys(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
this.addNewOption();
|
||||
}
|
||||
}
|
||||
|
||||
get backgroundHeight(): string {
|
||||
if (this.isOpen && this.top && this.bottom) {
|
||||
const topHeight = this.top.nativeElement.clientHeight;
|
||||
const bottomHeight = this.bottom.nativeElement.clientHeight;
|
||||
return `${topHeight + bottomHeight}px`;
|
||||
}
|
||||
return `100%`;
|
||||
}
|
||||
|
||||
addNewOption() {
|
||||
if (this.newOption) {
|
||||
this.select(this.newOption);
|
||||
this.newOption = '';
|
||||
}
|
||||
}
|
||||
|
||||
select(option: string) {
|
||||
this.selected = option;
|
||||
this.value.emit(this.selected);
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
}
|
||||
5
src/app/components/shared/toggle/toggle.component.html
Normal file
5
src/app/components/shared/toggle/toggle.component.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<span [className]="!on ? 'active' : ''" (click)="on = false" [innerText]="beforeText"></span>
|
||||
|
||||
<input type="checkbox" [(ngModel)]="on" [className]="on ? 'on' : ''" />
|
||||
|
||||
<span [className]="on ? 'active' : ''" (click)="on = true" [innerText]="afterText"></span>
|
||||
66
src/app/components/shared/toggle/toggle.component.scss
Normal file
66
src/app/components/shared/toggle/toggle.component.scss
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
@import '../../../../styles';
|
||||
|
||||
:host {
|
||||
$size: 30px;
|
||||
|
||||
@include center-child();
|
||||
@include inner-spacing(var(--medium-padding), $horizontal: true);
|
||||
|
||||
span {
|
||||
@include medium-text();
|
||||
max-width: 3 * $size;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
text-align: right;
|
||||
}
|
||||
&:last-of-type {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
-webkit-appearance: none;
|
||||
|
||||
width: 2 * $size;
|
||||
height: $size;
|
||||
|
||||
border-radius: 1000px;
|
||||
box-shadow: $shadow-border;
|
||||
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
|
||||
@include square($size);
|
||||
|
||||
border-radius: 1000px;
|
||||
background-color: $text-color;
|
||||
|
||||
transition: box-shadow $long-animation-time, left $long-animation-time, transform $long-animation-time;
|
||||
}
|
||||
|
||||
&:hover:after {
|
||||
box-shadow: $shadow;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
&.on:hover:after {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
&.on:after {
|
||||
left: $size;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/app/components/shared/toggle/toggle.component.ts
Normal file
27
src/app/components/shared/toggle/toggle.component.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toggle',
|
||||
templateUrl: './toggle.component.html',
|
||||
styleUrls: ['./toggle.component.scss']
|
||||
})
|
||||
export class ToggleComponent {
|
||||
@Input() beforeText: string;
|
||||
@Input() afterText: string;
|
||||
|
||||
@Output() value: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
@Input() set default(value: boolean) {
|
||||
this.on = value;
|
||||
}
|
||||
|
||||
private _on = false;
|
||||
set on(value: boolean) {
|
||||
this._on = value;
|
||||
this.value.emit(value);
|
||||
}
|
||||
|
||||
get on(): boolean {
|
||||
return this._on;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue