Browse Source

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
master
Wojciech Kwolek 5 months ago
parent
commit
716055cf6a
17 changed files with 1592 additions and 181 deletions
  1. 21
      app/css/accomplishments.css
  2. 29
      app/css/common.css
  3. 89
      app/css/forms.css
  4. 133
      app/css/main.css
  5. 11
      app/templates/_formhelpers.html
  6. 6
      app/templates/_skel.html
  7. 6
      app/templates/auth/login.html
  8. 6
      app/templates/auth/logout.html
  9. 6
      app/templates/auth/register.html
  10. 8
      app/templates/index.html
  11. 13
      app/templates/main/app.html
  12. 11
      app/templates/main/delete.html
  13. 24
      app/templates/main/edit.html
  14. 6
      app/templates/settings.html
  15. 26
      package.json
  16. 16
      postcss.config.js
  17. 1362
      yarn.lock

21
app/css/accomplishments.css

@ -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

@ -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

@ -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;
}

133
app/css/main.css

@ -1,126 +1,21 @@
@tailwind base;
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@tailwind components;
@tailwind utilities;
@import 'accomplishments.css';
@import 'common.css';
@import 'forms.css';
body {
@apply bg-gray-200;
}
form input[type=text],
form input[type=password],
form input[type=number],
form select {
@apply shadow;
@apply appearance-none;
@apply border;
@apply rounded;
@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;
.screencap {
@apply overflow-hidden;
@apply border-2;
@apply border-black;
@apply border-gray-500;
@apply border-solid;
@apply rounded-lg;
@apply shadow-xl;
}

11
app/templates/_formhelpers.html

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

6
app/templates/_skel.html

@ -52,6 +52,12 @@
<span class="pl-1"><a class="text-xs link" href="{{ url_for('auth.logout') }}">Log
out</a></span>
</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 %}
{% endblock %}
</div>

6
app/templates/auth/login.html

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

6
app/templates/auth/logout.html

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

6
app/templates/auth/register.html

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

8
app/templates/index.html

@ -14,15 +14,15 @@
</p>
</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') }}">
</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>
<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">
Sign up</a> or <a href="{{ url_for('auth.login') }}" class="link">log in</a>.
<a href="{{ url_for('auth.register') }}" class="btn btn-blue btn-inline">
Sign up</a> <span class="px-1">or</span> <a href="{{ url_for('auth.login') }}" class="link">log in</a>.
</p>
</div>
{% endblock %}

13
app/templates/main/app.html

@ -1,14 +1,15 @@
{% extends "_skel.html" %}
{% from "_formhelpers.html" import render_field %}
{% block title %}{{ day.pretty }}{% endblock %}
{% block content %}
<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.text(placeholder="What did you accomplish today?", class_="placeholder-black", autofocus=True) }}
<div class="flex mt-1">
{{ form.submit_5(class_="w-1/3 mr-1 green-btn") }}
{{ form.submit_10(class_="w-1/3 mx-1 orange-btn") }}
{{ form.submit_15(class_="w-1/3 ml-1 red-btn") }}
{{ 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-3">
{{ form.submit_5(class_="w-1/3 mr-1 btn btn-green") }}
{{ form.submit_10(class_="w-1/3 mx-1 btn btn-orange") }}
{{ form.submit_15(class_="w-1/3 ml-1 btn btn-red") }}
</div>
</form>
</div>

11
app/templates/main/delete.html

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

24
app/templates/main/edit.html

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

6
app/templates/settings.html

@ -2,13 +2,13 @@
{% block title %}Settings{% endblock %}
{% from "_formhelpers.html" import render_field %}
{% 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 -->
<form method="POST" class="max-w-xs mx-auto auth-form">
<form method="POST" class="form form-narrow">
{{ form.csrf_token }}
{{ 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.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 %}
</form>
</div>

26
package.json

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

16
postcss.config.js

@ -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
View File

Loading…
Cancel
Save