CSS (forms) refactoring (#13)

CSS (forms) refactoring

* All forms now use the render_field helper.
* CSS is now compiled using PostCSS.
* Common form styles got extracted to more reusable & flexible classes.

Closes #12.

Co-authored-by: Wojciech Kwolek <wojciech@kwolek.xyz>
Reviewed-on: https://git.r23s.eu/wojciech/doneth.at-backend/pulls/13
This commit is contained in:
Wojciech Kwolek 2020-09-27 17:50:12 +00:00
parent b1a34784ae
commit 716055cf6a
17 changed files with 1592 additions and 181 deletions

View File

@ -0,0 +1,21 @@
.accomplishment .text {
@apply flex-grow;
}
.accomplishment .difficulty {
@apply flex-shrink-0 pl-1 font-bold text-right;
min-width: 3em;
}
.difficulty-easy {
@apply text-green-700;
}
.difficulty-medium {
@apply text-orange-700;
}
.difficulty-hard {
@apply text-red-700;
}

29
app/css/common.css Normal file
View File

@ -0,0 +1,29 @@
.section {
@apply w-full;
@apply max-w-lg;
@apply m-auto;
&.section-wider {
@apply max-w-xl;
}
}
.card {
@apply px-8;
@apply pt-6;
@apply pb-6;
@apply my-4;
@apply bg-white;
@apply rounded;
@apply shadow-md;
}
.link {
@apply text-blue-700;
@apply underline;
&:hover {
@apply text-blue-500;
}
}

89
app/css/forms.css Normal file
View File

@ -0,0 +1,89 @@
.form {
@apply w-full;
@apply mx-auto;
&.form-narrow {
@apply max-w-xs;
}
}
.btn, input:not([type=submit]), select {
@apply shadow;
@apply appearance-none;
@apply outline-none;
@apply rounded;
@apply py-2;
@apply px-3;
@apply block;
@apply text-gray-700;
@apply leading-tight;
@apply transition-all;
@apply duration-75;
&:focus {
@apply shadow-outline;
}
}
input:not([type=submit]), select {
@apply w-full;
/* rounded borders look weird on buttons in firefox, so we only apply that here */
@apply border-gray-300;
@apply border;
@apply mx-auto;
}
.input[type=submit] {
@apply bg-none;
}
.btn, input[type="submit"] {
@apply border-none;
&.btn-blue {
@apply text-white;
@apply bg-blue-600;
&:hover { @apply bg-blue-800; }
&:focus { @apply bg-blue-900; }
}
&.btn-green {
@apply text-white;
@apply bg-green-500;
&:hover { @apply bg-green-600; }
&:focus { @apply bg-green-700; }
}
&.btn-orange {
@apply text-white;
@apply bg-orange-500;
&:hover { @apply bg-orange-600; }
&:focus { @apply bg-orange-700; }
}
&.btn-red {
@apply text-white;
@apply bg-red-500;
&:hover { @apply bg-red-600; }
&:focus { @apply bg-red-700; }
}
&.btn-wide {
@apply px-6;
}
&.btn-inline {
@apply inline-block;
}
&.btn-center { @apply mx-auto; }
}
.field.error input {
@apply border-red-500;
}

View File

@ -1,126 +1,21 @@
@tailwind base; @import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@tailwind components; @import 'accomplishments.css';
@import 'common.css';
@tailwind utilities; @import 'forms.css';
body { body {
@apply bg-gray-200; @apply bg-gray-200;
} }
form input[type=text], .screencap {
form input[type=password], @apply overflow-hidden;
form input[type=number], @apply border-2;
form select { @apply border-black;
@apply shadow; @apply border-gray-500;
@apply appearance-none; @apply border-solid;
@apply border; @apply rounded-lg;
@apply rounded; @apply shadow-xl;
@apply w-full;
@apply py-2;
@apply px-3;
@apply text-gray-700;
@apply leading-tight;
}
form input:focus,
form select:focus {
@apply outline-none;
@apply shadow-outline;
}
form div.error input {
@apply border-red-500;
}
form div.error ul.errors {
@apply pt-1 pl-1;
}
form div.error ul.errors li {
@apply text-xs italic text-red-500;
}
form input[type=text]:focus,
form input[type=password]:focus {
@apply outline-none;
@apply shadow-outline;
}
form input[type=submit] {
@apply px-4 py-2 mt-2 font-bold rounded;
}
form.auth-form input[type=submit] {
@apply w-full text-white bg-blue-500;
}
.green-btn {
@apply text-white bg-green-500;
}
.accomplishment .text {
@apply flex-grow;
}
.accomplishment .difficulty {
@apply flex-shrink-0 pl-1 font-bold text-right;
min-width: 3em;
}
.difficulty-easy {
@apply text-green-700;
}
.difficulty-medium {
@apply text-orange-700;
}
.difficulty-hard {
@apply text-red-700;
}
.green-btn:hover {
@apply bg-green-700;
}
.orange-btn {
@apply text-white bg-orange-500;
}
.orange-btn:hover {
@apply bg-orange-700;
}
.red-btn {
@apply text-white bg-red-500;
}
.red-btn:hover {
@apply bg-red-700;
}
form.auth-form input[type=submit]:hover {
@apply bg-blue-700;
}
form input[type=submit]:focus {
@apply shadow-outline outline-none;
}
.link {
@apply text-blue-700 underline;
}
.link:hover {
@apply text-blue-500;
}
.card {
@apply px-8 pt-6 pb-6 mt-4 mb-4 bg-white rounded shadow-md;
}
.auth-form.card {
@apply pb-4;
} }

View File

@ -1,8 +1,9 @@
{% macro render_field(field, label=True, wrapper_class="", description="") %} {% macro render_field(field, label=True, mb="mb-4", wrapper_class="", description="") %}
<div class="mb-4 {% if field.errors %}error{% endif %} {{ wrapper_class }}"> <div class="field {% if field.errors %}error{% endif %} {% if mb %} {{ mb }} {% endif %} {{ wrapper_class }}">
<label for="{{ field.id }}" {% if label %}
class="block text-sm font-bold text-gray-700">{% if label %}{{ field.label }}{% endif %}</label> <label for="{{ field.id }}" class="block text-sm font-bold text-gray-700">{{ field.label }}</label>
<div class="mb-2 text-xs text-gray-700">{{ description }}</div> <div class="mb-2 text-xs text-gray-700">{{ description }}</div>
{% endif %}
{% if field.type == "SelectField" %} {% if field.type == "SelectField" %}
<div class="relative"> <div class="relative">
{{ field(**kwargs)|safe }} {{ field(**kwargs)|safe }}
@ -15,7 +16,7 @@
{{ field(**kwargs)|safe }} {{ field(**kwargs)|safe }}
{% endif %} {% endif %}
{% if field.errors %} {% if field.errors %}
<ul class="errors"> <ul class="pl-1 mt-2 text-xs text-red-700 errors">
{% for error in field.errors %} {% for error in field.errors %}
<li>{{ error }}</li> <li>{{ error }}</li>
{% endfor %} {% endfor %}

View File

@ -52,6 +52,12 @@
<span class="pl-1"><a class="text-xs link" href="{{ url_for('auth.logout') }}">Log <span class="pl-1"><a class="text-xs link" href="{{ url_for('auth.logout') }}">Log
out</a></span> out</a></span>
</p> </p>
{% else %}
<p>
<span class="pr-1"><a class="text-xs link" href="{{ url_for('main.index') }}">Home</a></span>
<span class="px-1"><a class="text-xs link" href="{{ url_for('auth.login') }}">Log in</a></span>
<span class="pl-1"><a class="text-xs link" href="{{ url_for('auth.register') }}">Register</a></span>
</p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</div> </div>

View File

@ -2,12 +2,12 @@
{% block title %}Log in{% endblock %} {% block title %}Log in{% endblock %}
{% from "_formhelpers.html" import render_field %} {% from "_formhelpers.html" import render_field %}
{% block content %} {% block content %}
<div class="w-full max-w-lg mx-auto card"> <div class="section card">
<form method="POST" class="w-full max-w-xs mx-auto auth-form"> <form method="POST" class="form form-narrow">
{{ form.csrf_token }} {{ form.csrf_token }}
{{ render_field(form.username) }} {{ render_field(form.username) }}
{{ render_field(form.password) }} {{ render_field(form.password) }}
{{ render_field(form.submit, False) }} {{ render_field(form.submit, False, class_="btn btn-blue btn-wide btn-center") }}
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,11 +2,11 @@
{% block title %}Log out{% endblock %} {% block title %}Log out{% endblock %}
{% from "_formhelpers.html" import render_field %} {% from "_formhelpers.html" import render_field %}
{% block content %} {% block content %}
<div class="w-full max-w-lg mx-auto card"> <div class="section card">
<form method="POST" class="w-full max-w-xs mx-auto auth-form"> <form method="POST" class="form-narrow form">
<h3 class="text-center">Are you sure you want to log out?</h3> <h3 class="text-center">Are you sure you want to log out?</h3>
{{ form.csrf_token }} {{ form.csrf_token }}
{{ render_field(form.submit, False) }} {{ render_field(form.submit, False, mb="mb-2", class="mt-3 btn btn-red btn-wide btn-center") }}
<p class="text-xs text-center"> <p class="text-xs text-center">
<a class="link" href="{{ url_for('main.index') }}">cancel</a> <a class="link" href="{{ url_for('main.index') }}">cancel</a>
</p> </p>

View File

@ -2,14 +2,14 @@
{% block title %}Register{% endblock %} {% block title %}Register{% endblock %}
{% from "_formhelpers.html" import render_field %} {% from "_formhelpers.html" import render_field %}
{% block content %} {% block content %}
<div class="w-full max-w-lg mx-auto card"> <div class="section card">
<form method="POST" class="w-full max-w-xs mx-auto auth-form"> <form method="POST" class="form form-narrow">
{{ form.csrf_token }} {{ form.csrf_token }}
{{ render_field(form.username) }} {{ render_field(form.username) }}
{{ render_field(form.tz) }} {{ render_field(form.tz) }}
{{ render_field(form.password) }} {{ render_field(form.password) }}
{{ render_field(form.confirm) }} {{ render_field(form.confirm) }}
{{ render_field(form.submit, False) }} {{ render_field(form.submit, False, class_="btn btn-blue btn-wide btn-center") }}
</form> </form>
</div> </div>

View File

@ -14,15 +14,15 @@
</p> </p>
</div> </div>
<div class="max-w-xl m-auto overflow-hidden border-2 border-black border-gray-500 border-solid rounded-lg shadow-xl"> <div class="screencap section section-wider">
<img src="{{ static_url_for('static', filename='screencap.png') }}"> <img src="{{ static_url_for('static', filename='screencap.png') }}">
</div> </div>
<div class="max-w-lg m-auto text-center card"> <div class="text-center section card">
<h2 class="mb-4 text-2xl">Interested?</h2> <h2 class="mb-4 text-2xl">Interested?</h2>
<p class="mb-4"> <p class="mb-4">
<a href="{{ url_for('auth.register') }}" class="px-4 py-2 text-white bg-blue-700 rounded hover:bg-blue-500"> <a href="{{ url_for('auth.register') }}" class="btn btn-blue btn-inline">
Sign up</a> or <a href="{{ url_for('auth.login') }}" class="link">log in</a>. Sign up</a> <span class="px-1">or</span> <a href="{{ url_for('auth.login') }}" class="link">log in</a>.
</p> </p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,14 +1,15 @@
{% extends "_skel.html" %} {% extends "_skel.html" %}
{% from "_formhelpers.html" import render_field %}
{% block title %}{{ day.pretty }}{% endblock %} {% block title %}{{ day.pretty }}{% endblock %}
{% block content %} {% block content %}
<div class="max-w-xl mx-auto card"> <div class="max-w-xl mx-auto card">
<form method="POST" action="{{ url_for('main.index') }}"> <form method="POST" action="{{ url_for('main.index') }}" class="form">
{{ form.csrf_token }} {{ form.csrf_token }}
{{ form.text(placeholder="What did you accomplish today?", class_="placeholder-black", autofocus=True) }} {{ render_field(form.text, label=False, mb=False, placeholder="What did you accomplish today?", class_="placeholder-black w-full", autofocus=True) }}
<div class="flex mt-1"> <div class="flex mt-3">
{{ form.submit_5(class_="w-1/3 mr-1 green-btn") }} {{ form.submit_5(class_="w-1/3 mr-1 btn btn-green") }}
{{ form.submit_10(class_="w-1/3 mx-1 orange-btn") }} {{ form.submit_10(class_="w-1/3 mx-1 btn btn-orange") }}
{{ form.submit_15(class_="w-1/3 ml-1 red-btn") }} {{ form.submit_15(class_="w-1/3 ml-1 btn btn-red") }}
</div> </div>
</form> </form>
</div> </div>

View File

@ -1,7 +1,8 @@
{% extends "_skel.html" %} {% extends "_skel.html" %}
{% from "_formhelpers.html" import render_field %}
{% block title %}Delete accomplishment{% endblock %} {% block title %}Delete accomplishment{% endblock %}
{% block content %} {% block content %}
<div class="max-w-xl mx-auto text-center card"> <div class="text-center section card">
<p class="italic"> <p class="italic">
{{ accomplishment.text }} {{ accomplishment.text }}
</p> </p>
@ -9,12 +10,12 @@
XP) XP)
</p> </p>
</div> </div>
<div class="max-w-lg mx-auto text-center card"> <div class="text-center section card">
<h3 class="text-lg">Are you sure you want to remove this accomplishment?</h3> <h3 class="text-lg">Are you sure you want to remove this accomplishment?</h3>
<form class="mt-2" method="POST"> <form class="mt-3" method="POST">
{{ form.csrf_token }} {{ form.csrf_token }}
{{ form.submit(class_="hover:bg-red-500 bg-red-700 text-white") }} {{ render_field(form.submit, label=False, mb="mb-2", class_="btn btn-red btn-wide btn-center") }}
<a href="{{ cancel }}" class="block mt-2 text-sm text-blue-700 hover:text-blue-500">cancel</a> <a href="{{ cancel }}" class="block text-xs link">cancel</a>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -3,20 +3,22 @@
{% from "_formhelpers.html" import render_field %} {% from "_formhelpers.html" import render_field %}
{% block content %} {% block content %}
{% if day %} {% if day %}
<div class="w-full max-w-lg mx-auto card"> <div class="section card">
<div class="text-xl text-center"> <div class="text-xl text-center">
You're adding an accomplishment made on {{ day.pretty }}. You're adding an accomplishment made on {{ day.pretty }}.
</div> </div>
</div> </div>
{% endif %} {% endif %}
<form method="POST" class="w-full max-w-lg mx-auto card" {% if day %}action="{{ url_for('main.add_day', day=day.url) }}" <div class="section card">
{% endif %}> <form method="POST" class="form form-narrow" {% if day %}action="{{ url_for('main.add_day', day=day.url) }}"
{{ form.csrf_token }} {% endif %}>
{{ render_field(form.text) }} {{ form.csrf_token }}
{{ render_field(form.difficulty) }} {{ render_field(form.text) }}
<div class="text-center"> {{ render_field(form.difficulty, mb="mb-6") }}
{{ render_field(form.submit, False, class_="bg-blue-700 text-white hover:bg-blue-500") }} <div class="text-center">
<a href="{{ cancel }}" class="block mt-2 text-sm text-blue-700 hover:text-blue-500">cancel</a> {{ render_field(form.submit, False, mb="mb-1", class_="btn btn-blue btn-center btn-wide") }}
</div> <a href="{{ cancel }}" class="text-xs link">cancel</a>
</form> </div>
</form>
</div>
{% endblock %} {% endblock %}

View File

@ -2,13 +2,13 @@
{% block title %}Settings{% endblock %} {% block title %}Settings{% endblock %}
{% from "_formhelpers.html" import render_field %} {% from "_formhelpers.html" import render_field %}
{% block content %} {% block content %}
<div class="w-full max-w-lg mx-auto card"> <div class="section card">
<!-- TODO: this shouldn't use the auth-form class, that class should be generalized --> <!-- TODO: this shouldn't use the auth-form class, that class should be generalized -->
<form method="POST" class="max-w-xs mx-auto auth-form"> <form method="POST" class="form form-narrow">
{{ form.csrf_token }} {{ form.csrf_token }}
{{ render_field(form.timezone) }} {{ render_field(form.timezone) }}
{{ render_field(form.start_of_day, description="This is useful if you often work after midnight. If you set it to e.g. 2, all accomplishments made before 2 AM will be considered to be made on the previous day.") }} {{ render_field(form.start_of_day, description="This is useful if you often work after midnight. If you set it to e.g. 2, all accomplishments made before 2 AM will be considered to be made on the previous day.") }}
{{ render_field(form.submit, False) }} {{ render_field(form.submit, False, class_="btn btn-blue btn-wide btn-center") }}
{% if success %}<p class="text-center text-green-800">Saved successfuly.</p>{% endif %} {% if success %}<p class="text-center text-green-800">Saved successfuly.</p>{% endif %}
</form> </form>
</div> </div>

View File

@ -1,13 +1,17 @@
{ {
"dependencies": { "dependencies": {
"tailwindcss": "^1.7.5" "cssnano": "^4.1.10",
}, "postcss-cli": "^8.0.0",
"scripts": { "postcss-import": "^12.0.1",
"build": "tailwindcss build app/css/main.css -o app/static/style.css" "postcss-nested": "4.2.3",
}, "tailwindcss": "^1.7.5"
"name": "donethat-backend", },
"version": "0.0.1", "scripts": {
"main": "index.js", "build": "postcss app/css/main.css --output app/static/style.css"
"author": "Wojciech Kwolek <me@irth.pl>", },
"license": "MIT" "name": "donethat-backend",
"version": "0.0.1",
"main": "index.js",
"author": "Wojciech Kwolek <me@irth.pl>",
"license": "MIT"
} }

16
postcss.config.js Normal file
View File

@ -0,0 +1,16 @@
const plugins = [
require('postcss-import'),
require('tailwindcss'),
require('postcss-nested'),
require('autoprefixer')
]
if (process.env.NODE_ENV == "production") {
plugins.push(require('cssnano')({
preset: 'default',
}))
}
module.exports = {
plugins
}

1362
yarn.lock

File diff suppressed because it is too large Load Diff