Browse Source

Add stepper form

dev
philipp lang 5 years ago
parent
commit
8016c7952f
  1. 105
      assets/css/app.css
  2. 86
      assets/js/app.js
  3. 8
      assets/svg/home.svg
  4. 11
      composer.json
  5. 919
      composer.lock
  6. 47
      package-lock.json
  7. 8
      package.json
  8. 53
      src/Stepper.php
  9. 0
      src/css/app.css
  10. 0
      src/js/app.js
  11. 10
      stepper.php
  12. 76
      views/daten.twig.htm
  13. 68
      views/stepper.twig.htm
  14. 65
      views/vorhaben.twig.htm
  15. 3
      webpack.mix.js

105
assets/css/app.css

@ -0,0 +1,105 @@
:root {
--bg-color: hsl(0.0, 0.0%, 90.2%);
--primary: hsl(82.3, 54.4%, 44.7%);
--secondary: hsl(194.5, 100.0%, 26.9%);
--gray: hsl(37.5, 25.0%, 93.7%);
--container: hsl(37.5, 50.0%, 96.9%);
}
body {
background: var(--bg-color) !important;
}
.stepper {
font-family: sans-serif;
background: var(--bg-color);
& > div {
h2 {
font-size: 2.3rem;
color: var(--secondary);
text-align: center;
}
.radio-grid {
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(3, 1fr);
margin-top: 1rem;
label {
height: 20rem;
position: relative;
input {
visibility: hidden;
z-index: -99;
position: absolute;
}
input + span {
border: 1px var(--primary) solid;
border-radius: 2rem;
overflow: hidden;
cursor: pointer;
background: white;
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
span:first-child {
display: flex;
flex: 1 0 0;
justify-content: center;
align-items: center;
}
span:nth-child(2) {
flex: 0 0 4rem;
background: var(--gray);
display: flex;
width: 100%;
justify-content: center;
align-items: center;
}
}
input:checked + span {
border-color: var(--secondary);
}
svg {
color: var(--secondary);
fill: currentColor;
width: 8rem;
height: 8rem;
}
}
}
.form-container {
background: var(--container);
border-radius: 2rem;
padding: 3rem;
display: grid;
grid-gap: 1rem;
.separator {
background: white;
height: 5px;
}
.form-grid {
display: grid;
grid-gap: 0.5rem;
grid-template-columns: 2fr 3fr 30px;
align-items: center;
input[type="text"] {
font-weight: bold;
border: var(--primary) 1px solid;
text-align: right;
}
.unit {
text-align: right;
}
.comment {
grid-column: 1 / -1;
font-size: 1.5rem;
}
}
}
}
}

86
assets/js/app.js

@ -0,0 +1,86 @@
import 'alpinejs';
import wNumb from 'wnumb';
var units = {
currency: wNumb({
mark: ',',
thousand: '.',
prefix: '',
suffix: '',
decimals: 2
}),
percent: wNumb({
mark: ',',
thousand: '',
prefix: '',
suffix: ' %',
decimals: 2
})
};
window.stepper = {
parts: {
notar: 0.02,
grundsteuer: 0.065,
makler: 0.0357
},
units: units,
step: 2,
greetings: [
{ value: 'frau', label: 'Frau' },
{ value: 'herr', label: 'Herr' },
{ value: 'divers', label: 'Divers' },
],
jobs: [
{ value: 'angestellt', label: 'Angestellte*r' },
],
titles: [
{ value: 'prof', label: 'Prof' },
{ value: 'dr', label: 'Dr' },
],
value: {
kind: 'kauf',
kauf: {
kaufpreis: 300000,
modernisierung: 0,
baukosten: 0,
eigenkapital: 0,
},
bau: {
grundstueckspreis: 300000,
bezahlt: '0',
baukosten: 0,
eigenkapital: 0,
},
anschluss: {
objektwert: 300000,
umschuldung: 50000,
zuskap: 50000
},
wert: 300000,
umschuldung: 0,
kapital_zus: 0,
greeting: null,
title: null,
firstname: '',
lastname: '',
zip: '',
location: '',
phone: '',
email: '',
job: '',
haushalt: '',
einnahme: ''
},
kinds: [
{label: 'Kauf einer Immobilie', value: 'kauf', icon: 'home'},
{label: 'Eigenes Bauvorhaben', value: 'bau', icon: 'home'},
{label: 'Anschlussfinanzierung', value: 'anschluss', icon: 'home'}
],
/* Methods */
svg(icon) {
return `<svg><use xlink:href="${this.sprite}#${icon}"></use></svg>`;
}
};

8
assets/svg/home.svg

@ -0,0 +1,8 @@
<?xml version="1.0" ?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512">
<g>
<path d="m426 495.983h-340c-25.364 0-46-20.635-46-46v-242.02c0-8.836 7.163-16 16-16s16 7.164 16 16v242.02c0 7.72 6.28 14 14 14h340c7.72 0 14-6.28 14-14v-242.02c0-8.836 7.163-16 16-16s16 7.164 16 16v242.02c0 25.364-20.635 46-46 46z"/>
<path d="m496 263.958c-4.095 0-8.189-1.562-11.313-4.687l-198.989-198.987c-16.375-16.376-43.02-16.376-59.396 0l-198.988 198.988c-6.248 6.249-16.379 6.249-22.627 0-6.249-6.248-6.249-16.379 0-22.627l198.988-198.989c28.852-28.852 75.799-28.852 104.65 0l198.988 198.988c6.249 6.249 6.249 16.379 0 22.627-3.123 3.125-7.218 4.687-11.313 4.687z"/>
<path d="m320 495.983h-128c-8.837 0-16-7.164-16-16v-142c0-27.57 22.43-50 50-50h60c27.57 0 50 22.43 50 50v142c0 8.836-7.163 16-16 16zm-112-32h96v-126c0-9.925-8.075-18-18-18h-60c-9.925 0-18 8.075-18 18z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 962 B

11
composer.json

@ -1,12 +1,19 @@
{
"name": "philipp/stepper",
"require": {
"twig/twig": "^3.1"
"twig/twig": "^3.1",
"symfony/var-dumper": "^5.1",
"illuminate/support": "^8.12"
},
"authors": [
{
"name": "Philipp Lang",
"email": "philipp@zoomyboy.de"
}
]
],
"autoload": {
"psr-4": {
"Zoomyboy\\Stepper\\": "./src"
}
}
}

919
composer.lock

File diff suppressed because it is too large

47
package-lock.json

@ -1222,6 +1222,11 @@
"resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz",
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM="
},
"alpinejs": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-2.7.3.tgz",
"integrity": "sha512-IRXnszk68s+FOGFMA1+K3rjLK44NqLShNpSy8aI6J3Ch9gss56FqlU6NVRP+mJes7LeQFCreH410luScVSkdfg=="
},
"ansi-colors": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@ -3254,6 +3259,12 @@
}
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
"extend-shallow": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
@ -5619,6 +5630,37 @@
}
}
},
"postcss-css-variables": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.17.0.tgz",
"integrity": "sha512-/ZpFnJgksNOrQA72b3DKhExYh+0e2P5nEc3aPZ62G7JLmdDjWRFv3k/q4LxV7uzXFnmvkhXRbdVIiH5tKgfFNA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"escape-string-regexp": "^1.0.3",
"extend": "^3.0.1",
"postcss": "^6.0.8"
},
"dependencies": {
"postcss": {
"version": "6.0.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
"integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
"dev": true,
"requires": {
"chalk": "^2.4.1",
"source-map": "^0.6.1",
"supports-color": "^5.4.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"postcss-discard-comments": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz",
@ -8376,6 +8418,11 @@
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wnumb": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/wnumb/-/wnumb-1.2.0.tgz",
"integrity": "sha512-eYut5K/dW7usfk/Mwm6nxBNoTPp/uP7PlXld+hhg7lDtHLdHFnNclywGYM9BRC7Ohd4JhwuHg+vmOUGfd3NhVA=="
},
"worker-farm": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",

8
package.json

@ -9,17 +9,21 @@
"watch": "npm run development -- --watch",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"sprite": "cd assets/svg && svg-sprite -s --symbol-dest=sprite *.svg && mv sprite/svg/sprite.symbol.svg ../public/sprite.svg && rm -R sprite"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"alpinejs": "^2.7.3",
"cross-env": "^7.0.2",
"laravel-mix": "^5.0.7",
"postcss-nested": "^4.0"
"postcss-nested": "^4.0",
"wnumb": "^1.2.0"
},
"devDependencies": {
"postcss-css-variables": "^0.17.0",
"vue-template-compiler": "^2.6.12"
}
}

53
src/Stepper.php

@ -0,0 +1,53 @@
<?php
namespace Zoomyboy\Stepper;
use Twig\Loader\FilesystemLoader;
use Twig\Environment;
use Twig\TwigFilter as Filter;
use Twig\TwigFunction as Func;
class Stepper {
private $twig;
public $url;
public function __construct() {
$loader = new FilesystemLoader(__DIR__.'/../views');
$this->twig = new Environment($loader);
$this->twig->addFilter(new Filter('svg', [$this, 'svgTag']));
$this->twig->addFilter(new Filter('prop', [$this, 'alpineProp']));
$this->twig->addFunction(new Func('cprop', function ($aprop, $modifier) {
return $this->alpineProp($aprop, $modifier);
}));
}
public function init() {
add_action('wp_enqueue_scripts', [ $this, 'enqueue' ]);
add_shortcode('stepper', [ $this, 'handle' ]);
}
public function enqueue() {
wp_enqueue_script('stepper-js', $this->url.'assets/public/app.js');
wp_enqueue_style('stepper-css', $this->url.'assets/public/app.css');
}
public function handle() {
echo $this->twig->render('stepper.twig.htm', [
'sprite' => $this->url.'assets/public/sprite.svg'
]);
}
public function svgTag($e, $class = '') {
return '<svg class="'.$class.'"><use xlink:href="'.$sprite.'#'.$e.'"></use></svg>';
}
public function alpineProp($prop, $modifier) {
return ':value="units.'.$modifier.'.to('.$prop.')"
@focus="$event.target.value = '.$prop.'"
@change="'.$prop.' = units.'.$modifier.'.from($event.target.value)"';
}
}

0
src/css/app.css

0
src/js/app.js

10
stepper.php

@ -12,4 +12,12 @@
* @package Stepper
*/
// Your code starts here.
require_once(__DIR__.'/vendor/autoload.php');
use Zoomyboy\Stepper\Stepper;
$stepper = new Stepper();
$stepper->url = plugin_dir_url(__FILE__);
$stepper->init();

76
views/daten.twig.htm

@ -0,0 +1,76 @@
<div x-show="step == 2">
<h2 class="gradient">Geben Sie Ihre persönlichen Daten ein</h2>
<div class="form-container">
<div class="field-grid">
<label for="greeting">
<span>Anrede</span>
<select x-model="value.greeting" id="greeting">
<template x-for="greeting in greetings">
<option :value="greeting.value" x-text="greeting.label"></option>
</template>
</select>
</label>
<label for="title">
<span>Titel</span>
<select x-model="value.title" id="title">
<template x-for="title in titles">
<option :value="title.value" x-text="title.label"></option>
</template>
</select>
</label>
<label for="firstname">
<span>Vorname</span>
<input type="text" x-model="value.firstname" id="firstname">
</label>
<label for="lastname">
<span>Nachname</span>
<input type="text" x-model="value.lastname" id="lastname">
</label>
<label for="zip">
<span>PLZ</span>
<input type="text" x-model="value.zip" id="zip">
</label>
<label for="location">
<span>Ort</span>
<input type="text" x-model="value.location" id="location">
</label>
<label for="phone">
<span>Telefon</span>
<input type="tel" x-model="value.phone" id="phone">
</label>
<label for="email">
<span>E-Mail-Adresse</span>
<input type="email" x-model="value.email" id="email">
</label>
<label for="job">
<span>Beruf des Hauptverdieners</span>
<select x-model="value.job" id="job">
<template x-for="job in jobs">
<option :value="job.value" x-text="job.label"></option>
</template>
</select>
</label>
<label for="haushalt">
<span>Haushaltsnetto monatlich</span>
<input type="text" x-model="value.haushalt" id="haushalt">
</label>
<label for="einnahme">
<span>Netto-Mieteinnahme monatlich</span>
<input type="text" x-model="value.einnahme" id="einnahme">
</label>
</div>
</div>
</div>

68
views/stepper.twig.htm

@ -0,0 +1,68 @@
<form>
<div class="stepper" x-data="{ ...stepper, sprite: '{{sprite|raw}}' }">
<div x-show="step == 0">
<h2>Was möchten Sie finanzieren</h2>
<div class="radio-grid">
<template x-for="ckind in kinds">
<label>
<input @change="step++" type="radio" name="kind" :value="ckind.value" x-model="value.kind">
<span>
<span x-html="svg(ckind.icon)"></span>
<span x-text="ckind.label"></span>
</span>
</label>
</template>
</div>
</div>
{% include 'vorhaben.twig.htm' with {
'kind': "bau",
bezahlt: true,
second: {label: 'Baukosten', value: 'baukosten'},
main: {label: 'Grundstückspreis', value: 'grundstueckspreis'}
} %}
{% include 'vorhaben.twig.htm' with {
'kind': "kauf",
bezahlt: false,
second: {label: 'Modernisierung', value: 'modernisierung'},
main: {label: 'Kaufpreis', value: 'kaufpreis'}
} %}
{% include 'daten.twig.htm' %}
<div x-show="step == 1 && kind == 'anschluss'">
<h2 class="gradient">Das Vorhaben in Zahlen</h2>
<div class="form-container">
<div class="form-grid">
<label>Objektwert</label>
<input type="text" {{cprop('value.anschluss.objektwert', 'currency') | raw}}>
<span class="unit"></span>
</div>
<div class="separator"></div>
<div class="form-grid">
<label>Umschuldung</label>
<input type="text" {{cprop('value.anschluss.umschuldung', 'currency') | raw}}>
<span class="unit"></span>
<span class="comment">Bitte geben Sie hier die Summe der gleichzeitig umzuschuldenden Darlehen an.</span>
</div>
<div class="separator"></div>
<div class="form-grid">
<label>Zusätzl. Kapital</label>
<input type="text" {{cprop('value.anschluss.zuskap', 'currency') | raw}}>
<span class="unit"></span>
</div>
<div class="separator primary"></div>
<div>
<div>Darlehensbetrag</div>
<div x-text="units.currency.to(value.anschluss.objektwert + value.anschluss.umschuldung + value.anschluss.zuskap)"></div>
<span class="comment">Darlehensbeträge werden auf volle 1.000 Euro gerundet.</span>
</div>
<div class="separator gray"></div>
<button type="button" value="Werte übernehmen und weiter zum letzten Schritt"></button>
</div>
</div>
</div>
</form>

65
views/vorhaben.twig.htm

@ -0,0 +1,65 @@
<div x-show="step == 1 && value.kind == '{{kind}}'">
<h2 class="gradient">Ihr Bauvorhaben</h2>
<div class="form-container">
<div class="form-grid">
<label>{{main.label}}</label>
<input type="text" {{cprop('value.' ~ kind ~ '.' ~ main.value, 'currency') | raw}}>
<span class="unit"></span>
</div>
{% if bezahlt %}
<div class="form-grid">
<label>Bereits bezahlt?</label>
<select name="bezahlt" x-model="value.{{kind}}.bezahlt">
<option value="1">Ja</option>
<option value="0">Nein</option>
</select>
</div>
{% endif %}
<div class="separator"></div>
<div class="form-grid">
<label>{{second.label}}</label>
<input type="text" {{cprop('value.' ~ kind ~ '.' ~ second.value, 'currency') | raw}}>
<span class="unit"></span>
</div>
<div class="separator"></div>
<div class="form-grid">
<label>Notar- & Gerichtskosten</label>
<span>
<span class="result-label" x-text="units.percent.to(parts.notar * 100)"></span>
<span class="result" x-text="units.currency.to(value.{{kind}}.{{main.value}} * parts.notar)"></span>
</span>
<span class="unit"></span>
</div>
<div class="separator primary"></div>
<div class="form-grid">
<label>Grunderwerbsteuer</label>
<span>
<span class="result-label" x-text="units.percent.to(parts.grundsteuer * 100)"></span>
<span class="result" x-text="units.currency.to(value.{{kind}}.{{main.value}} * parts.grundsteuer)"></span>
</span>
<span class="unit"></span>
</div>
<div class="separator"></div>
<div class="form-grid">
<label>Makler <span class="result-label" x-text="units.percent.to(parts.makler * 100)"></span></label>
<input type="text" :value="units.currency.to(value.{{kind}}.{{main.value}} * parts.makler)">
<span class="unit"></span>
<span class="comment">Die vorgeschlagene Maklercourtage gentspricht der regional üblichen Höhe. Sie können diese jedoch bei Bedarf verändern oder auch komplett entfallen lassen.</span>
</div>
<div class="separator"></div>
<div class="form-grid">
<label>Eigenkapital</label>
<input type="text" {{cprop('value.' ~ kind ~ '.eigenkapital', 'currency') | raw}}>
<span class="unit"></span>
</div>
<div class="separator primary"></div>
<div>
<div><span>=</span>Darlehensbetrag</div>
<div x-text="units.currency.to(value.{{kind}}.{{main.value}} * (1+parts.notar+parts.grundsteuer+parts.makler) + value.{{kind}}.{{second.value}} - value.{{kind}}.eigenkapital)"></div>
<span class="comment">Darlehensbeträge werden auf volle 1.000 Euro gerundet.</span>
</div>
<div class="separator gray"></div>
<button @click="step++" type="button">Werte übernehmen und weiter zum letzten Schritt</button>
</div>
</div>

3
webpack.mix.js

@ -13,5 +13,6 @@ let mix = require('laravel-mix');
mix.js('assets/js/app.js', 'assets/public/app.js')
.postCss('assets/css/app.css', 'assets/public/app.css', [
require('postcss-nested')
require('postcss-nested'),
require('postcss-css-variables')
]);

Loading…
Cancel
Save