From 87e41214441c0fc59ad8b3c2e0f6e2384c3a119d Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Wed, 18 Mar 2020 16:33:06 +0100
Subject: [PATCH] UI - Add UiNumericInput component

---
 resources/components/ui/numeric.html        | 11 +++++
 resources/styles/_form.scss                 |  8 ++--
 resources/styles/main.scss                  |  2 +
 resources/ts/components/app.ts              |  2 +
 resources/ts/components/ui/icon.ts          |  4 +-
 resources/ts/components/ui/index.ts         |  1 +
 resources/ts/components/ui/numeric-input.ts | 53 +++++++++++++++++++++
 templates/app.html.twig                     | 41 +++++++++-------
 8 files changed, 102 insertions(+), 20 deletions(-)
 create mode 100644 resources/components/ui/numeric.html
 create mode 100644 resources/ts/components/ui/numeric-input.ts

diff --git a/resources/components/ui/numeric.html b/resources/components/ui/numeric.html
new file mode 100644
index 0000000..c2cb1ce
--- /dev/null
+++ b/resources/components/ui/numeric.html
@@ -0,0 +1,11 @@
+<div class="input-group input-group-sm">
+    <input type="text" class="form-control form-control-sm" :id="id" inputmode="numeric" v-bind="$attrs" :value="value" @blur="update"/>
+    <div class="input-group-append">
+        <button class="btn btn-addon" type="button" @click="increment" :disabled="!canIncrement">
+            <ui-icon icon="increment"/>
+        </button>
+        <button class="btn btn-addon" type="button" @click="decrement" :disabled="!canDecrement">
+            <ui-icon icon="decrement"/>
+        </button>
+    </div>
+</div>
diff --git a/resources/styles/_form.scss b/resources/styles/_form.scss
index 1101da9..7e14acc 100644
--- a/resources/styles/_form.scss
+++ b/resources/styles/_form.scss
@@ -3,17 +3,18 @@ label {
   margin-bottom: 0;
   margin-top: -0.2rem;
   display: block;
+  font-size: .8rem;
 }
 
 .label-sm {
-  font-size: .8rem;
+  font-size: .6rem;
 }
 
 .form-group:last-child {
   margin-bottom: 0;
 }
 
-.form-control, .input-group-text {
+.form-control, .input-group-text, .btn-addon {
   background: rgba($dark, .06);
   border: none;
   border-bottom: 2px solid $dark;
@@ -32,7 +33,8 @@ label {
   }
 }
 
-.input-group-append {
+.input-group-append,
+.input-group-append .btn + .btn {
   margin-left: 0;
 }
 
diff --git a/resources/styles/main.scss b/resources/styles/main.scss
index 58b6f1b..6be0b6c 100644
--- a/resources/styles/main.scss
+++ b/resources/styles/main.scss
@@ -2,9 +2,11 @@ $border-radius: 0;
 $border-radius-lg: $border-radius;
 $border-radius-sm: $border-radius;
 
+
 @import "~bootstrap/scss/functions";
 @import "~bootstrap/scss/variables";
 
+$form-group-margin-bottom: $form-group-margin-bottom / 2;
 $primary: #005ea8;
 
 $custom-control-indicator-checked-bg: $dark;
diff --git a/resources/ts/components/app.ts b/resources/ts/components/app.ts
index 2f74e9f..c8cfd93 100644
--- a/resources/ts/components/app.ts
+++ b/resources/ts/components/app.ts
@@ -30,6 +30,8 @@ export class Application extends Vue {
         }
     };
 
+    private count = 8;
+
     private intervals = { messages: null, departures: null };
 
     get messages() {
diff --git a/resources/ts/components/ui/icon.ts b/resources/ts/components/ui/icon.ts
index 419db53..e2928d5 100644
--- a/resources/ts/components/ui/icon.ts
+++ b/resources/ts/components/ui/icon.ts
@@ -23,7 +23,7 @@ import {
     faTimes,
     faTrashAlt
 } from "@fortawesome/pro-light-svg-icons";
-import { faClock as faClockBold, faCodeCommit, faSpinnerThird } from "@fortawesome/pro-regular-svg-icons";
+import { faClock as faClockBold, faCodeCommit, faMinus, faPlus, faSpinnerThird } from "@fortawesome/pro-regular-svg-icons";
 import { faExclamationTriangle as faSolidExclamationTriangle, faWalking } from "@fortawesome/pro-solid-svg-icons";
 import { fac } from "../../icons";
 import { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText } from "@fortawesome/vue-fontawesome";
@@ -81,6 +81,8 @@ const definitions: Dictionary<Icon> = {
     'map': simple(faMapMarkerAlt),
     'stop': simple(faSign),
     'spinner': simple(faSpinnerThird, { spin: true }),
+    'increment': simple(faPlus, { "fixed-width": true }),
+    'decrement': simple(faMinus, { "fixed-width": true }),
     'departure-warning': stack([
         {icon: faClockBold},
         {icon: faSolidExclamationTriangle, transform: "shrink-5 down-4 right-6"}
diff --git a/resources/ts/components/ui/index.ts b/resources/ts/components/ui/index.ts
index 9647ef5..8814273 100644
--- a/resources/ts/components/ui/index.ts
+++ b/resources/ts/components/ui/index.ts
@@ -1,2 +1,3 @@
 export * from './switch';
 export * from './icon';
+export * from './numeric-input'
diff --git a/resources/ts/components/ui/numeric-input.ts b/resources/ts/components/ui/numeric-input.ts
new file mode 100644
index 0000000..f8ef7e9
--- /dev/null
+++ b/resources/ts/components/ui/numeric-input.ts
@@ -0,0 +1,53 @@
+import Vue from 'vue'
+import { Component, Prop } from 'vue-property-decorator'
+import * as uuid from "uuid";
+
+@Component({
+    template: require('../../../components/ui/numeric.html'),
+    inheritAttrs: false
+})
+export class UiNumericInput extends Vue {
+    @Prop({
+        type: String,
+        default: () => `uuid-${uuid.v4()}`
+    })
+    id: string;
+
+    @Prop(Number)
+    value: number;
+
+    @Prop({ type: Number, default: 1 })
+    step: number;
+
+    @Prop({ type: Number, default: -Infinity })
+    min: number;
+
+    @Prop({ type: Number, default: Infinity })
+    max: number;
+
+    update(ev) {
+        this.$emit('input', this.clamp(Number.parseInt(ev.target.value)));
+    }
+
+    increment() {
+        this.$emit('input', this.clamp(this.value + this.step));
+    }
+
+    decrement() {
+        this.$emit('input', this.clamp(this.value - this.step));
+    }
+
+    clamp(value: number) {
+        return Math.max(Math.min(value, this.max), this.min);
+    }
+
+    get canIncrement(): boolean {
+        return this.max - this.value > Number.EPSILON * 2;
+    }
+
+    get canDecrement(): boolean {
+        return this.value - this.min > Number.EPSILON * 2;
+    }
+}
+
+Vue.component('UiNumericInput', UiNumericInput);
diff --git a/templates/app.html.twig b/templates/app.html.twig
index 0817d50..eb64bd6 100644
--- a/templates/app.html.twig
+++ b/templates/app.html.twig
@@ -73,25 +73,34 @@
                     </button>
                     <portal to="popups">
                         <popper reference="settings-departures" v-if="visibility.departures" arrow placement="left-start" @leave="visibility.departures = false">
-                            <h3 class="popper__heading flex">
-                                <label class="text" for="departures-auto-refresh">
-                                    <ui-icon icon="refresh" fixed-width></ui-icon>
-                                    autoodświeżanie
-                                </label>
-                                <ui-switch id="departures-auto-refresh" v-model="autorefresh.departures.active" class="flex-space-left"></ui-switch>
-                            </h3>
-                            <div class="flex" v-if="autorefresh.departures.active">
-                                <label for="departures-auto-refresh-interval" class="text">
-                                    <span class="sr-only">częstotliwość odświeżania</span>
-                                    co
-                                </label>
-                                <div class="input-group input-group-sm">
-                                    <input type="text" class="form-control form-control-sm form-control-simple" id="departures-auto-refresh-interval" v-model="autorefresh.departures.interval"/>
-                                    <div class="input-group-append">
-                                        <span class="input-group-text" aria-label="sekund">s</span>
+                            <div class="form-group">
+                                <h3 class="popper__heading flex">
+                                    <label class="text" for="departures-auto-refresh-interval">
+                                        <ui-icon icon="refresh" fixed-width></ui-icon>
+                                        autoodświeżanie
+                                    </label>
+                                    <ui-switch id="departures-auto-refresh" v-model="autorefresh.departures.active" class="flex-space-left"></ui-switch>
+                                </h3>
+                                <div class="flex " v-if="autorefresh.departures.active">
+                                    <label for="departures-auto-refresh-interval" class="text">
+                                        <span class="sr-only">częstotliwość odświeżania</span>
+                                        co
+                                    </label>
+                                    <div class="input-group input-group-sm">
+                                        <input type="text" class="form-control form-control-sm form-control-simple" id="departures-auto-refresh-interval" v-model="autorefresh.departures.interval"/>
+                                        <div class="input-group-append">
+                                            <span class="input-group-text" aria-label="sekund">s</span>
+                                        </div>
                                     </div>
                                 </div>
                             </div>
+                            <div class="form-group">
+                                <label class="text" for="departures-count">
+                                    <ui-icon icon="line-bus" fixed-width></ui-icon>
+                                    Liczba wpisów
+                                </label>
+                                <ui-numeric-input id="departures-count" v-model="count" :min="1" :max="16"></ui-numeric-input>
+                            </div>
                         </popper>
                     </portal>
                 </header>