Changer le framework CSS
Par défaut lorsqu’on installe Laravel et qu’on utilise la commande php artisan make:auth on obtient des vues donc le CSS est géré par Bootstrap 3. Même si c’est un excellent framework et sans doute le plus utilisé on peut avoir envie d’autre chose. Dans cet article on va voir comment on peut faire ce changement en conservant les possibilités de Laravel Mix.
Pour vous faciliter la vie le projet complet est téléchargeable ici.
Installation par défaut
Partez d’une nouvelle installation de Laravel :
composer create-project --prefer-dist laravel/laravel laravel5
Puis générez les vue pour l’authentification :
php artisan make:auth
Renseignez le fichier .env pour la connexion à la base puis générez les migrations :
php artisan migrate
Si tout se passe bien vous avez 3 tables :
Ensuite installez les modules avec npm :
npm install
Vous devez obtenir un dossier node_modules bien garni :
Jetez un coup d’œil au fichier package.json pour voir les scripts disponibles et les dépendances chargées :
{ "private": true, "scripts": { "dev": "npm run development", "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch-poll": "npm run watch -- --watch-poll", "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 --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" }, "devDependencies": { "axios": "^0.16.2", "bootstrap-sass": "^3.3.7", "cross-env": "^5.0.1", "jquery": "^3.1.1", "laravel-mix": "^1.0", "lodash": "^4.17.4", "vue": "^2.1.10" } }
Lancez en mode développement pour voir si ça fonctionne :
npm run dev
Vous devez avoir la compilation des assets dans les fichiers public/css/app.css et public/js/app.js comme c’est prévu dans le fichier webpack.mix.js :
mix.js('resources/assets/js/app.js', 'public/js') .sass('resources/assets/sass/app.scss', 'public/css');
Pour mémoire les sources des assets sont ici :
Pour le CSS on voit que dans le fichier resources/assets/sass/app.scss on importe bootstrap :
@import "~bootstrap-sass/assets/stylesheets/bootstrap";
Et qu’on le modifie un peu en chargeant de nouvelles variables :
@import "variables";
Pour le Javascript on voit que dans le fichier resources/assets/js/bootstrap.js on charge bootstrap :
require('bootstrap-sass');
D’autre part on charge aussi lodash, jQuery et Vue.js avec un composant par défaut mais ce n’est pas l’objet du présent article.
Voilà donc un petit état des lieux à l’installation de Laravel et on se retrouve pour les vues de l’authentification avec un aspect typique de Bootstrap :
On va maintenant procéder aux modifications pour utiliser non plus Bootstrap mais Materialize que j’aime bien. Mais les explications restent valables pour n’importe quel framework CSS.
On passe à Materialize
L’intendance
Sur le site de npm on cherche le module :
Et comme on veut ce module uniquement pour le développement on va utiliser cette syntaxe :
npm install materialize-css --save-dev
Si tout va bien le fichier package.json est actualisé :
"devDependencies": { ... "materialize-css": "^0.100.2", ... }
On va maintenant mettre à jour le fichier resources/assets/sass/app.scss pour importer Materialize plutôt que Bootstrap :
// Fonts //@import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600"); @import url("https://fonts.googleapis.com/icon?family=Material+Icons"); // Variables //@import "variables"; // Bootstrap //@import "~bootstrap-sass/assets/stylesheets/bootstrap"; //Materialize @import "~materialize-css/sass/materialize.scss";
Et pour le Javascript ça se passe dans le fichier resources/assets/js/bootstrap.js :
//require('bootstrap-sass'); require('materialize-css');
On va lancer en mode développement avec actualisation automatique :
npm run watch
Si on regarde maintenant la page de login on va lui trouver un salle aspect :
C’est normal parce qu’on a les classes de Bootstrap déclarées et qu’on a plus ce framework disponible. Il nous reste maintenant à modifier les vues avec les classes de Materialize.
Loyout
Voici la nouvelle vue resources/views/layouts/app.blade.php :
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <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> <!-- Styles --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> @yield('css') </head> <body> <div id="app"> @auth <ul id="dropdown1" class="dropdown-content"> <li> <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> Logout </a> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> {{ csrf_field() }} </form> </li> </ul> @endauth <nav> <div class="nav-wrapper"> <a href="{{ url('/') }}" class="brand-logo"> {{ config('app.name', 'Laravel') }}</a> <a href="#" data-activates="mobile-demo" class="button-collapse"><i class="material-icons">menu</i></a> @guest <ul class="right hide-on-med-and-down"> <li><a href="{{ route('login') }}">Login</a></li> <li><a href="{{ route('register') }}">Register</a></li> </ul> <ul class="side-nav" id="mobile-demo"> <li><a href="{{ route('login') }}">Login</a></li> <li><a href="{{ route('register') }}">Register</a></li> </ul> @else <ul class="right hide-on-med-and-down"> <li><a class="dropdown-button" href="#!" data-activates="dropdown1">{{ Auth::user()->name }}<i class="material-icons right">arrow_drop_down</i></a></li> </ul> <ul class="right hide-on-med-and-down"> <li><a class="dropdown-button" href="#!" data-activates="dropdown1">{{ Auth::user()->name }}<i class="material-icons right">arrow_drop_down</i></a></li> </ul> @endguest </div> </nav> @yield('content') </div> <!-- Scripts --> <script src="{{ asset('js/app.js') }}"></script> <script> $(".button-collapse").sideNav() $(".dropdown-button").dropdown() </script> </body> </html>
J’ai changé essentiellement la barre de navigation. Ça commence à changer l’aspect :
Il faut maintenant modifier les autres vues…
Login
Voici la nouvelle vue de login (resources/views/auth/login.blade.php) :
@extends('layouts.app') @section('css') <style> .card { margin-top: 40px; } </style> @endsection @section('content') <div class="container"> <div class="row"> <div class="card"> <form method="POST" action="{{ route('login') }}"> <div class="card-content"> {{ csrf_field() }} <span class="card-title">Login</span> <hr> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">mail</i> <input id="email" type="email" name="email" value="{{ old('email') }}" class="{{ $errors->has('email') ? 'invalid' : '' }}" required autofocus> <label for="email" data-error="{{ $errors->has('email') ? $errors->first('email'): '' }}">E-Mail Address</label> </div> </div> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">lock</i> <input id="password" type="password" name="password" class="{{ $errors->has('password') ? 'invalid' : '' }}" required> <label for="password" data-error="{{ $errors->has('password') ? $errors->first('password'): '' }}">Password</label> </div> </div> <p> <input type="checkbox" id="remember" {{ old('remember') ? 'checked' : '' }}> <label for="remember">Remember Me</label> </p> </div> <div class="card-action"> <button class="btn waves-effect waves-light" type="submit" name="action">Login <i class="material-icons right">lock_open</i> </button> <a class="waves-effect waves-light btn" href="{{ route('password.request') }}">Forgot Your Password?<i class="material-icons right">message</i></a> </div> </form> </div> </div> </div> @endsection
Avec cet aspect :
C’est quand même plus présentable !
Register
Voici la nouvelle vue d’enregistrement (resources/views/auth/register.blade.php) :
@extends('layouts.app') @section('css') <style> .card { margin-top: 40px; } </style> @endsection @section('content') <div class="container"> <div class="row"> <div class="card"> <form method="POST" action="{{ route('register') }}"> <div class="card-content"> {{ csrf_field() }} <span class="card-title">Register</span> <hr> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">person</i> <input id="name" type="text" name="name" value="{{ old('name') }}" class="{{ $errors->has('name') ? 'invalid' : '' }}" required autofocus> <label for="name" data-error="{{ $errors->has('name') ? $errors->first('name'): '' }}">Name</label> </div> </div> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">mail</i> <input id="email" type="email" name="email" value="{{ old('email') }}" class="{{ $errors->has('email') ? 'invalid' : '' }}" required autofocus> <label for="email" data-error="{{ $errors->has('email') ? $errors->first('email'): '' }}">E-Mail Address</label> </div> </div> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">lock</i> <input id="password" type="password" name="password" class="{{ $errors->has('password') ? 'invalid' : '' }}" required> <label for="password" data-error="{{ $errors->has('password') ? $errors->first('password'): '' }}">Password</label> </div> </div> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">lock</i> <input id="password-confirm" type="password" name="password_confirmation" required> <label for="password-confirm">Confirm Password</label> </div> </div> </div> <div class="card-action"> <button class="btn waves-effect waves-light" type="submit" name="action">Register <i class="material-icons right">create</i> </button> </div> </form> </div> </div> </div> @endsection
Avec cet aspect :
Voici la nouvelle vue de demande de renouvellement du mot de passe (resources/views/auth/passwords/email.blade.php) :
@extends('layouts.app') @section('css') <style> .row > .card { margin-top: 40px; } </style> @endsection @section('content') <div class="container"> <div class="row"> <div class="card"> <form method="POST" action="{{ route('password.email') }}"> <div class="card-content"> {{ csrf_field() }} @if (session('status')) <div class="card green darken-1"> <div class="card-content white-text"> {{ session('status') }} </div> </div> @endif <span class="card-title">Reset Password</span> <hr> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">mail</i> <input id="email" type="email" name="email" value="{{ old('email') }}" class="{{ $errors->has('email') ? 'invalid' : '' }}" required autofocus> <label for="email" data-error="{{ $errors->has('email') ? $errors->first('email'): '' }}">E-Mail Address</label> </div> </div> </div> <div class="card-action"> <button class="btn waves-effect waves-light" type="submit" name="action">Send Password Reset Link <i class="material-icons right">lock_open</i> </button> </div> </form> </div> </div> </div> @endsection
Avec cet aspect :
Reset
Voici la nouvelle vue de de renouvellement du mot de passe (resources/views/auth/passwords/reset.blade.php) :
@extends('layouts.app') @section('css') <style> .card { margin-top: 40px; } </style> @endsection @section('content') <div class="container"> <div class="row"> <div class="card"> <form method="POST" action="{{ route('password.request') }}"> <div class="card-content"> {{ csrf_field() }} <input type="hidden" name="token" value="{{ $token }}"> <span class="card-title">Reset Password</span> <hr> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">mail</i> <input id="email" type="email" name="email" value="{{ old('email') }}" class="{{ $errors->has('email') ? 'invalid' : '' }}" required autofocus> <label for="email" data-error="{{ $errors->has('email') ? $errors->first('email'): '' }}">E-Mail Address</label> </div> </div> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">lock</i> <input id="password" type="password" name="password" class="{{ $errors->has('password') ? 'invalid' : '' }}" required> <label for="password" data-error="{{ $errors->has('password') ? $errors->first('password'): '' }}">Password</label> </div> </div> <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">lock</i> <input id="password-confirm" type="password" name="password_confirmation" required> <label for="password-confirm">Confirm Password</label> </div> </div> </div> <div class="card-action"> <button class="btn waves-effect waves-light" type="submit" name="action">Reset Password <i class="material-icons right">lock_open</i> </button> </div> </form> </div> </div> </div> @endsection
Avec cet aspect :
Home
Pour finir voilà le code pour la vue resources/views/home.blade.php :
@extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col s12 m6"> @if (session('status')) <div class="card green darken-1"> <div class="card-content white-text"> {{ session('status') }} </div> </div> @endif <div class="card red lighten-2"> <div class="card-content white-text"> <span class="card-title">Dashboard</span> You are logged in! </div> </div> </div> </div> </div> @endsection
Avec cet aspect :
Vous voyez que le changement de framework CSS n’est pas bien difficile mais il demande un peu d’attention au niveau de Laravel Mix si on veut profiter de cet outil…
2 commentaires
CGO
Bonjour j’ai suivi votre tuto, est impossible d’afficher le menu en version mobile, de plus les onglets « connexion » et « s’inscrire » ne s’affiche pas dans la nav. Une fois connecté le dropdown ne s’affiche pas.
J’ai une erreur sur la fonction sideNav. je ne sais pas comment faire pour corriger ce problème
bestmomo
Bonjour,
Le souci vient du fait que l’article a une année et qu’entre temps Laravel est passé en version 5.7 et Materialize en version 1.0. Il faudrait donc actualiser tout ça…
Edit au 02/02/2019 : nouvel article ici.