Laravel 6 avec paper CSS

J’avais montré comment changer le framework CSS de Laravel 5 avec deux exemples : MDL et Material Design. Depuis ces articles la version 6 a un peu changé les choses. En effet maintenant Laravel est bien séparé de la partie client et il faut installer un package indépendant pour obtenir un peu de code utile côté client. Avec ce package on dispose de 3 presets déjà configurés : Bootstrap, Vue et React. Si on veut autre chose il faut un peu relever ses manches ou alors utiliser un package que quelqu’un aurait déjà créé, c’est le cas par exemple pour UiKit.

Mais il n’est pas si difficile de se faire un code client à son goût, je vous propose dans cet article comment procéder pour mettre Laravel au look de Paper CSS.

Le code complet de cet article est disponible ici.

Installation de Laravel

On va évidemment commencer par installer Laravel :

composer create-project laravel/laravel larapaper --prefer-dist

On arrive déjà à la page standard d’accueil :

On ajoute ensuite le package supplémentaire :

composer require laravel/ui --dev

Ensuite on utilise le preset Bootstrap avec l’authentification :

php artisan ui bootstrap --auth

Et on lance npm :

npm install
npm run dev

Tout cela est classique, on arrive sur la nouvelle page d’accueil avec les liens pour l’authentification :

Créez une base, entrez les bons identifiants dans le fichier .env et lancez les migration :

php artisan migrate

Pour tester toutes les vues on va aussi mettre en place la vérification des emails. Il faut que le modèle User implémente l’interface MustVerifyEmail :

class User extends Authenticatable implements MustVerifyEmail

Et crée les routes nécessaires :

Auth::routes(['verify' => true]);

On va aussi ajouter une route protégée pour les essais :

Route::get('protege', function () {
    return 'affichage de la route protégé';
})->middleware('verified');

Notre Laravel 6 est prêt !

Installation de paper css

On va maintenant ajouter Paper CSS :

npm i -D papercss

On va maintenant intervenir dans les fichiers SCSS :

Actuellement on a :

// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

On importe une police Google, puis des variables pour Bootstrap placées dans le fichier _variables.scss, puis Bootstrap. On va changer ça en supprimant le fichier des variables et en important juste Paper CSS en lui ajoutant quelques règles :

@import "~papercss/src/styles.scss";

main {
    margin-top: 100px;
}

input.is-invalid {
    border: 2px solid $danger;
}

.red {
    color: $danger;
}

.right {
    float: right;
}

On relance la compilation (vous pouvez aussi lancer watch pour ne plus avoir à le faire manuellement) :

npm run dev

On a cassé la belle mise en page, par exemple pour le login :

Au niveau du Javascript, Paper n’en nécessite pas, alors on va supprimer le fichier bootstrap.js et garder le fichier d’entrée vide :

Les layout

On va commencer par recoder le layout principal :

Avec ce code :

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <nav class="border fixed split-nav">
        <div class="nav-brand">
            <h3><a href="{{ url('/') }}">{{ config('app.name', 'Laravel') }}</a></h3>
        </div>
        <div class="collapsible">
            <input id="collapsible1" type="checkbox" name="collapsible1">
            <button>
            <label for="collapsible1">
                <div class="bar1"></div>
                <div class="bar2"></div>
                <div class="bar3"></div>
            </label>
            </button>
            <div class="collapsible-body">
            <ul class="inline">
                <!-- Authentication Links -->
                @guest
                    <li><a href="{{ route('login') }}">@lang('Login')</a></li>
                    @if (Route::has('register'))
                        <li><a href="{{ route('register') }}">@lang('Register')</a></li>
                    @endif
                @else
                    <li>
                        <a class="dropdown-item" href="{{ route('logout') }}"
                            onclick="event.preventDefault();
                                            document.getElementById('logout-form').submit();">
                            @lang('Logout')
                        </a>
                        <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                            @csrf
                        </form>
                    </li>
                @endguest
            </ul>
            </div>
        </div>
    </nav>
    <main>
        @yield('content')
    </main>
</body>
</html>

Le changement essentiel réside dans le code de la barre de navigation. J’ai abandonné le menu déroulant (Paper ne sait pas faire ça) pour l’utilisateur authentifié, mais a priori il connaît déjà son nom. Voici le nouvel aspect de la barre :

Et elle est responsive :

On va créer un second layout pour la carte :

En effet aura besoin de ce composant  pour caser les formulaires. En voici le code :

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row flex-center">
            <div class="sm-10 md-8 col">
                <div class="card">
                    @yield('card-content')
                </div>
            </div>
        </div>
    </div>
@endsection

Des vues partielles

Un saine attitude de codage consiste à se méfier dès qu’on fait des copier-coller, an anglais on appelle ça DRY (Don’t Repeat Yourself). On a pas mal de choses répétitives dans les formulaires de l’authentification et il serait dommage de répéter du code, alors on va créer quelques vues partielles :

On a une vue de base pour les inputs (input.blade.php) :

<div class="form-group">
    <label for="{{ $name }}">@lang($label)</label>
    <input id="{{ $name }}" type="{{ $type }}" class="input-block @error($name) is-invalid @enderror" name="{{ $name }}" value="{{ old($name) }}" required autocomplete="{{ $name }}" autofocus>
    @error($name)
        <span class="red" role="alert">
            <strong>{{ $message }}</strong>
        </span>
    @enderror
</div>

Avec cette vue de base on va créer les inputs pour name, email et password.

Pour name on a :

@include('partials.forms.input', [
    'label' => 'Name',
    'name' => 'name',
    'type' => 'text',    
])

Pour email on a :

@include('partials.forms.input', [
    'label' => 'E-Mail Address',
    'name' => 'email',
    'type' => 'email',    
])

Et pour password :

@include('partials.forms.input', [
    'label' => 'Password',
    'name' => 'password',
    'type' => 'password',    
])

Pour la confimation du password c’est un peu spécial et le codage est distinct :

<div class="form-group">
    <label for="password-confirm">{{ __('Confirm Password') }}</label>
    <input id="password-confirm" type="password" class="input-block" name="password_confirmation" required autocomplete="new-password">
</div>

Enfin pour le bouton de soumission (submit.blade.php) :

<div class="form-group">
    <button type="submit">
        @lang($text)
    </button>
</div>

Avec toutes ces vues partielles le codage des vues sera bien facilité et rendu plus lisible.

Register

La vue pour l’enregistrement :

Grâce aux vues partielles on a un code propre :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">@lang('Register')</div>

    <div class="card-body">
        <form method="POST" action="{{ route('register') }}">
            @csrf

            @include('partials.forms.name')

            @include('partials.forms.email')

            @include('partials.forms.password')

            @include('partials.forms.password-confirm')

            @include('partials.forms.submit', [ 'text' => 'Register', ])
                   
        </form>
    </div>
@endsection

Avec un aspect sympathique :

Pour les erreurs de validation on rougit le contour de l’input et on affiche le texte en rouge au-dessous, de façon classique :

Login

La vue pour le login :

Avec aussi un code léger :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">@lang('Login')</div>

    <div class="card-body">
        <form method="POST" action="{{ route('login') }}">
            @csrf

            @include('partials.forms.email')

            @include('partials.forms.password')

            <div class="form-group">
                <button type="submit">
                    @lang('Login')
                </button>

                @if (Route::has('password.request'))
                    <a class="paper-btn right" href="{{ route('password.request') }}">
                        @lang('Forgot Your Password?')
                    </a>
                @endif
            </div>
        </form>
    </div>
@endsection

Et cet aspect :

Vérification de l’email

Si l’email n’est pas encore vérifié et qu’on va sur une route protégée on appelle cette vue :

On la code aussi à la mode Paper :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">@lang('Verify Your Email Address')</div>

    <div class="card-body row flex-center">
        @if (session('resent'))
            <div class="alert alert-success" role="alert">
                @lang('A fresh verification link has been sent to your email address.')
            </div>
        @endif

        <p>@lang('Before proceeding, please check your email for a verification link.')</p>
        <form method="POST" action="{{ route('verification.resend') }}">
            @csrf
            <button type="submit">@lang('If you did not receive the email click here to request another')</button>.
        </form>
    </div>
@endsection

Avec cet aspect :

Le renouvellement du mot de passe

Demande

La demande de renouvellement du mot de passe se fait avec cette vue :

On la recode également :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">@lang('Reset Password')</div>

    <div class="card-body">
        @if (session('status'))
            <div class="alert alert-success" role="alert">
                {{ session('status') }}
            </div>
        @endif

        <form method="POST" action="{{ route('password.email') }}">
            @csrf

            @include('partials.forms.email')

            @include('partials.forms.submit', [ 'text' => 'Send Password Reset Link', ])

        </form>
    </div>
@endsection

Quand la demande est bien partie on a une alerte :

Le renouvellement

Le renouvellement se fait à partir de cette vue :

Avec ce code :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">@lang('Reset Password')</div>

    <div class="card-body">
        <form method="POST" action="{{ route('password.update') }}">
            @csrf

            <input type="hidden" name="token" value="{{ $token }}">

            @include('partials.forms.email')

            @include('partials.forms.password')

            @include('partials.forms.password-confirm')

            @include('partials.forms.submit', [ 'text' => 'Reset Password', ])

        </form>
    </div>
@endsection

Et cet aspect :

La confirmation du mot de passe

Voilà quelque chose qui n’existait pas avant Laravel 6.2. Imaginez une situation sensible pour laquelle vous voulez que l’utilisateur confirme son mot de passe. Désormais Laravel propose une solution simple pour le faire. Pour protéger les routes on dispose du middleware password.confirm. Par exemple on peut écrire :

Route::get('confirm', function () {
    return 'confirmation password nécessaire';
})->middleware('password.confirm');

Et on va utiliser cette vue :

Avec ce code :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">@lang('Confirm Password')</div>

    <div class="card-body">
        @lang('Please confirm your password before continuing.')
        <hr>

        <form method="POST" action="{{ route('password.confirm') }}">
            @csrf

            @include('partials.forms.password')

            <div class="form-group">
                <button type="submit">
                    @lang('Confirm Password')
                </button>

                @if (Route::has('password.request'))
                    <a class="paper-btn right" href="{{ route('password.request') }}">
                        @lang('Forgot Your Password?')
                    </a>
                @endif
            </div>
        </form>
    </div>
@endsection

Et cet aspect :

La page d’accueil

Il ne nous reste plus qu’à réécrire la page d’accueil :

Avec ce code :

@extends('layouts.card')

@section('card-content')
    <div class="card-header">Dashboard</div>

    <div class="card-body">
        @if (session('status'))
            <div class="alert alert-success" role="alert">
                {{ session('status') }}
            </div>
        @endif

        You are logged in!
    </div>
@endsection

Et on a terminé !

Conclusion

Il est un peu long mais facile de rhabiller Laravel avec un nouveau framework CSS. On pourrait avec le code de cet article créer un package pour automatiser cette tâche qui serait alors très rapide pour les suivants.

Print Friendly, PDF & Email

2 commentaires sur “Laravel 6 avec paper CSS

Laisser un commentaire